Java中的动态代理和静态代理详细解析
1.什么是代理?
生活中的代理是很常见的,比如代购、律师、中介等,他们都有一个共性就是帮助被代理人处理一些前前后后的事情。而被代理人只需要专注做自己要做的那部分事情就可以了。当然这里的被代理人不可能是只有一个,假如只有一个那根本养不活这群代理。
Java中的代理也是类似的,代理可以帮助被代理者完成一些前期的准备工作和后期的善后工作,但是核心的业务逻辑仍然是由被代理者完成。就好比中介找房,你想要什么房中介都可以帮你找,但是钱还是得你出,房还是得你亲自看。
2.为什么要使用代理?
从生活的角度上来说,租房为什么需要中介呢?我们直接找房东不好吗,其实中介他起到的作用是汇集房源,假如我们直接找房东,先不说房东好不好找,房子肯定是有数的,一个房东顶多也就几套房,而一个中介可能顶上好几个房东的房源了。说直白点,租房是我们的目的,在不改变自己的目的情况下,快速的找到房源,那就是通过中介!
从代码角度上来说,在不修改源码的基础上对方法进行加强。当然不是所有代码涉及到增强就需要使用代理,而是很多方法都涉及到了增强,才会统一使用一个代理,举例:我们要给所有的接口添加操作日志,这时候不可能说在每个接口上添加操作日志,这样会导致重复代码一大堆,所以这时候我们从中间抽出来一个代理,被代理类只需要专注于自己的核心代码即可,日志记录交给代理类就可以了,优点:使得代码更加简洁,分工明确。
3静态代理
代理模式分为动态代理和静态代理。两者的差别还是很大的,不过思想都是一样的,起到一个服务中介的作用。
所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,而动态代理是通过某种方式来生成的代理字节码文件。
3.1.静态代理的示例
以下是通过一个买车示例来演示静态代理的思想,实际开发当中我们可能不会这么写,但是更多的是需要理解他的思想!
(1)定义接口,接口当中有一个抽象方法
/** * 买车接口 * @author guo * */ public interface StaticMy { void lawsuit(); }
(2)定义被代理类,实现StaticMy接口
package com.gzl.static1; /** * 被代理方 我 * @author guo * */ public class StaticMyImpl implements StaticMy{ public void lawsuit() { System.out.println("我要买车"); } }
(3)代理类同样也需要实现StaticMy接口,将被代理类通过构造器的方式传入代理类,由代理类对被代理类进行加强
package com.gzl.static1; public class Shop implements StaticMy{ private StaticMyImpl staticMy; public Shop(StaticMyImpl staticMy) { super(); this.staticMy = staticMy; } public void lawsuit() { System.out.println("厂子进车"); this.staticMy.lawsuit(); System.out.println("交车"); } }
(4)测试
package com.gzl.static1; public class Test { public static void main(String[] args) { // 创建被代理类 StaticMyImpl shop = new StaticMyImpl(); // 将被代理类传入代理类当中 Shop shop2 = new Shop(shop); // 由代理类来执行 shop2.lawsuit(); } }
执行结果
4.动态代理
动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。
静态代理是直接在代码中声明好的代理对象,而动态代理中的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理一般有两种实现方式,cglib和jdk。
4.1.cglib和jdk动态代理的区别
- JDK代理使用的是反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- CGLIB代理使用字节码处理框架ASM,对代理对象类的class文件加载进来,通过修改字节码生成子类。
- JDK创建代理对象效率较高,执行效率较低;
- CGLIB创建代理对象效率较低,执行效率高。
- JDK动态代理机制是委托机制,只能对实现接口的类生成代理,通过反射动态实现接口类;
- CGLIB则使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类。
JDK代理是不需要依赖第三方的库,只要JDK环境就可以进行代理,需要满足以下要求:
- 实现InvocationHandler接口,重写invoke()
- 使用Proxy.newProxyInstance()产生代理对象
- 被代理的对象必须要实现接口
CGLib 必须依赖于CGLib的类库,需要满足以下要求:
- 实现MethodInterceptor接口,重写intercept()
- 使用Enhancer对象.create()产生代理对象
4.2.cglib动态代理示例
jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果我们想为普通的类也实现代理功能,我们就需要用到cglib来实现了。
cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对final类进行代理操作。
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy和BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。
(1)cglib并不是java当中自带的,所以使用的话需要引入jar包
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> </dependencies>
(2)创建一个类,写入两个方法
package com.itheima.cglib; /** * 一个生产者 */ public class Producer { /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
(3)动态代理类
在源码当中看到MethodInterceptor就是使用的cglib动态代理
package com.gzl.cglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于子类的动态代理: * 涉及的类:Enhancer * 提供者:第三方cglib库 * 如何创建代理对象: * 使用Enhancer类中的create方法 * 创建代理对象的要求: * 被代理类不能是最终类 * create方法的参数: * Class:字节码 * 它是用于指定被代理对象的字节码。 * * Callback:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 * 我们一般写的都是该接口的子接口实现类:MethodInterceptor */ Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { /** * 执行producer的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); cglibProducer.saleProduct(12000f); } }
执行结果
4.3.JDK动态代理示例
(1)创建一个接口
package com.gzl.proxy; /** * 对生产厂家要求的接口 */ public interface IProducer { /** * 销售 * @param money */ public void saleProduct(float money); /** * 售后 * @param money */ public void afterService(float money); }
(2)实现类
package com.gzl.proxy; /** * 一个生产者 */ public class Producer implements IProducer{ /** * 销售 * @param money */ public void saleProduct(float money){ System.out.println("销售产品,并拿到钱:"+money); } /** * 售后 * @param money */ public void afterService(float money){ System.out.println("提供售后服务,并拿到钱:"+money); } }
(3)代理类
package com.gzl.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模拟一个消费者 */ public class Client { public static void main(String[] args) { final Producer producer = new Producer(); /** * 动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 分类: * 基于接口的动态代理 * 基于子类的动态代理 * 基于接口的动态代理: * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。 * 此接口的实现类都是谁用谁写。 */ IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * @param proxy 代理对象的引用 * @param method 当前执行的方法 * @param args 当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Float money = (Float)args[0]; //2.判断当前方法是不是销售 if("saleProduct".equals(method.getName())) { returnValue = method.invoke(producer, money*0.8f); } return returnValue; } }); proxyProducer.saleProduct(12000f); } }
执行结果
到此这篇关于Java中的动态代理和静态代理详细解析的文章就介绍到这了,更多相关Java动态代理和静态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
详解Java对象序列化为什么要使用SerialversionUID
这篇文章主要介绍了详解Java对象序列化为什么要使用SerialversionUID,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-11-11IDEA配置maven环境的详细教程(Unable to import maven project报错问题的解决)
这篇文章主要介绍了IDEA配置maven环境的详细教程(Unable to import maven project问题的解决),本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-06-06Elasticsearch查询及聚合类DSL语句宝典示例详解
这篇文章主要为大家介绍了Elasticsearch查询及聚合类DSL语句宝典示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-01-01Java.lang.ArrayIndexOutOfBoundsException的报错解决
Java.lang.ArrayIndexOutOfBoundsException是一个常见的错误,通常由于访问超出数组边界的索引值导致,本文就详细的介绍了解决方法,具有一定的参考价值,感兴趣的可以了解一下2024-09-09@ConfigurationProperties遇到的坑及解决
这篇文章主要介绍了解决@ConfigurationProperties遇到的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07
最新评论