对动态代理浅层理解
文章目录
一、考察目的
1、考点分析
- 考察你对反射机制的了解和掌握程度。
- 动态代理解决了什么问题,在你业务系统中的应用场景是什么?
- JDK动态代理在设计和实现上与cglib等方式有什么不同,进而如何取舍?
2、典型回答
-
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类 似机制做到的,比如用来包装RPC调用、面向切面的编程(AOP )
-
实现动态代理的方式很多,比如JDK自身提供的动态代理,就是主要利用了上面提到的反射机 制。还有其他的实现方式,比如利用传说中更高性能的字节码操作机制,类似ASM、cglib (基 于 ASM )、Javassist 等。
二、知识拓展
1、反射机制及其演进
基本反射机制
-
反射机制是Java语言提供的一种基础功能,赋予程序在运行时自省(introspect) 的能力,通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属 性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
-
在java.Iang.reflect包下的Class. Field. Method. Constructor类等,这些完全就是我 们去操作类和对象的元数据对应。
反射中的Accessible
-
对于反射提供的AccessibleObject.setAccessible (boolean flag)方法,它的子类也大都重写了这个方法,这里的所谓accessible可以理解成修饰成员 的public, protected, private ,这意味着我们可以在运行时修改成员访问限制!
-
应用场景:
- setAccessible的应用场景非常普遍,遍布我们的日常开发、测试、依赖注入等各种框架中。比 如,在O/R Mapping框架中,我们为—Java实体对象,运行时自动生成setter, getter的逻辑,这里加载或者持久化数据非常必要的,框架通常可以利用反射做这个事情,而不需要手动写类似代码。
- 另一个典型场景就是绕过API访问控制。我们日常开发时可能被迫要调用内部API去做些事 情,比如,自定义的高性能NIO框架需要显式地释放DirectBuffer, 使用反射绕开限制是一种 常见方法.
-
在Java 9以后,Jigsaw项目新增的模块化 系统,出于强封装性的考虑,对反射访问进行了限制。Jigsaw引入了所谓Open的概念,只有 当被反射操作的模块和指定的包对反射调用者模块Open ,才能使用setAccessible ;否则,被 认为是不合法(illegal)操作。如果我们的实体类是定义在模块里面,我们需要在模块描述符中 明确声明:
module MyEntities { //Open for reflection opens cow.mycorp to Java.persistence; }
2、动态代理
基本概念和作用
- 如果熟悉设计模式中的代理模式,我们知道,代理可以看作是对调 用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。其实很多 动态代理场景,我认为也可以看作是装饰器(Decorator)模式的应用。
- 代理可以让调用者与实现者之间解耦。比如进行RPC调用,框架内部的寻址、序列化、反 序列等,对于调用者往往是没有太大意义的,通过代理,可以提供更加友善的界面。
JDK动态代理
在生产系统中,我们可以轻松扩展类似逻辑进行诊断,限流等。
- 用户管理接口
package com.wangxiaoxi.proxy.entity;
//用户管理接口
public interface UserManager {
//新增用户抽象方法
void addUser(String userName,String password);
//删除用户抽象方法
void delUser(String userName);
}
- 用户管理接口实现类
package com.wangxiaoxi.proxy.entity;
//用户管理实现类,实现用户管理接口
public class UserManagerImpl implements UserManager{
//重写新增用户方法
@Override
public void addUser(String userName, String password) {
System.out.println("调用了新增的方法!");
System.out.println("传入参数为 userName: "+userName+" password: "+password);
}
//重写删除用户方法
@Override
public void delUser(String userName) {
System.out.println("调用了删除的方法!");
System.out.println("传入参数为 userName: "+userName);
}
}
- JDK动态代理实现类
package com.wangxiaoxi.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.wangxiaoxi.proxy.entity.UserManager;
import com.wangxiaoxi.proxy.entity.UserManagerImpl;
//JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler {
private Object target ;//需要代理的目标对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,监听开始!"); //扩展逻辑1
System.out.println("------------------------------");
Object result = method.invoke(target, args);
System.out.println("------------------------------");
System.out.println("JDK动态代理,监听结束!"); //扩展逻辑2
return result;
}
//定义获取代理对象方法
private Object getJDKProxy(Object targetObject){
//为目标对象target赋值
this.target = targetObject;
//JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();//实例化JDKProxy对象
UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//通过实现用户管理接口,获取代理对象
user.addUser("admin", "123123");//执行新增方法
}
}
输出结果
JDK动态代理,监听开始!
------------------------------
调用了新增的方法!
传入参数为 userName: admin password: 123123
------------------------------
JDK动态代理,监听结束!
-
问题:
-
从API设计和实现的角度,这种实现仍然有局限性,因为它是以接口为中心的 ,相当于添加了 一种对于被调用者没有太大意义的限制。我们实例化的是Proxy对象,而不是真正的被调用类 型,这在实践中还是可能带来各种不便和能力退化。
-
如果被调用者没有实现接口,而我们还是希望利用动态代理机制,那么可以考虑其他方式,我们 知道Spring AOP支持两种模式的动态代理,JDK Proxy或者cglib ,如果我们选择cglib方 式,你会发现对接口的依赖被克服了。
-
cglib实现动态代理
- cglib动态代理采取的是创建目标类的子类的方式,因为是子类化,我们可以达到近似使用被调 用者本身的效果。在Spring编程中,框架通常会处理这种情况,当然我们也可以显式指定。
JDK Proxy与cglib的对比
原理区别:
- java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- 而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
如何强制使用CGLIB实现AOP?
- 添加CGLIB库,SPRING_HOME/cglib/*.jar
- 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
JDK动态代理和CGLIB字节码生成的区别?
-
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
-
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
优势对比:
- JDK Proxy的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可 靠.
- 平滑进行JDK版本升级,而字节码类库通常需要逬行更新以保证在新版Java上能够使用。
- 代码实现简单。
- 基于类似cglib框架的优势
- 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入 性的实践,类似cglib动态代理就没有这种限制。
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能。
备注:
-
反射机制在现代JDK中,自身已经得到了极大的改进和优化,同时,JDK很多功能也不完全是 反射,同样使用了 ASM逬行字节码操作。
-
动态代理应用非常广泛,虽然最初多是因为RPC等使用进入我们视线,但是动态代理的使用场 景远远不仅如此,它完美符合Spring AOP等切面编程。简单来说它可以看作是对OOP的补充,因为OOP对于跨越不同对 象或类的分散、纠缠逻辑表现力不够,比如在不同模块的特定阶段做一些事情,类似日志、用户 鉴权、全局性异常处理、性能监控,离至事务处理等
-
AOP通过(动态)代理机制可以让开发者从这些繁琐事项中抽身出来,大幅度提高了代码的抽 象程度和复用度 。从逻辑上来说,我们在软件设计和实现中的类似代理,如Facade. Observer 等很多设计目的,都可以通过动态代理优雅地实现。
三、网友互动
网友1:
- 提一些建议:应该从两条线讲这个问题,一条从代理模式,一条从反射机制。不要老担心篇 幅限制讲不清问题,废话砍掉一些,深层次的内在原理多讲些(比如asm),容易自学的扩展知识可以用链接代替
- 代理模式(通过代理静默地解决一些业务无关的问题,比如远程、安全,事务、日志、资源
关闭…让应用开发者可以只关心他的业务)
- 静态代理:事先写好代理类,可以手工编写,也可以用工具生成。缺点是每个业务类 都要对应一个代理类,非常不灵活。
- 动态代理:运行时自动生成代理对象。缺点是生成代理对象和调用代理方法都要额外
花费时间
- JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生 成代理对象。新版本也开始结合ASM机制。
- cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
- Java发射机制的常见应用:动态代理(AOP、RPC)、提供第三方开发者扩展能力(Servlet容器,JDBC连接)、第三方组件创建对象(DI)
四、参考文档
1、极客时间《Java核心技术36讲》第6讲