1、什么是动态代理
可能很多小伙伴首次接触动态代理这个名词的时候,或者是在面试过程中被问到动态代理的时候,不能很好的描述出来,动态代理到底是个什么高大上的技术。不方,其实动态代理的使用非常广泛,例如我们平常使用的 Spring
中的 @Transactional
注解,其依赖于 AOP
,而 AOP
的底层实现便是动态代理,看到这里,是不是更有兴趣去了解动态代理了呢?
动态代理:可以分解为"动态"+“代理”。
- 代理:"代理"一词,在我们的生活中也是随处可见的,例如房屋中介,其是对房主的一种代理,房主需要出租其房屋,但是可能没时间去接待租客,给租客介绍房屋信息,带领租客看房,但是房屋中介可以为租客提供这些服务,所以,代理其是对被代理对象的一个功能增强
- 动态:"动态"通常与"静态"相比较,"静态"描述的是事物是固定存在的,"动态"则描述的是事物是随着需求而动态生成的。
所以,静态代理存在一定的局限性,不能很好的满足需求的千变万化,动态代理的出现,就是为了解决这些局限性。
我们先来看看静态代理。
2.、静态代理
在开发中,通常需要为方法添加日志打印,能够记录程序的执行过程,以便后续出现异常问题的时候,能更好的排查定位。
假设我们现在已经完成了系统用户的增加、删除、修改等功能,这些功能在类 UserServiceImpl
中已经实现。
代码示例:
public class UserServiceImpl {
public void add() {
System.out.println("添加用户");
}
public void update() {
System.out.println("修改用户");
}
public void delete() {
System.out.println("删除用户");
}
}
现在,我们需要在UserServiceImpl类中的方法添加日志功能,那么怎么才能更好地去实现这个需求呢?
1)直接在目标方法前后添加日志代码
代码示例:
public class UserServiceImpl {
public void add() {
System.out.println("====== add方法开始 ======");
System.out.println("添加用户");
System.out.println("====== add方法结束 ======");
}
public void update() {
System.out.println("====== update方法开始 ======");
System.out.println("修改用户");
System.out.println("====== update方法结束 ======");
}
public void delete() {
System.out.println("====== delete方法开始 ======");
System.out.println("删除用户");
System.out.println("====== delete方法结束 ======");
}
}
观察上述代码,这种方式的缺点在于:
2)静态代理方式实现
静态代理需要我们将目标类的方法抽取到接口中,代理类和目标类实现同一个接口,既然要实现代理,代理类自然需要在其内部维护目标对象的引用,并通过构造函数为其赋值,然后在代理类的方法中调用目标对象的同名方法,并在调用前后完成功能的增强。
实现步骤:
- 抽取UserService接口
- 创建目标类UserServiceImpl实现UserService接口
- 创建代理类UserServiceProxy实现UserService接口
- 代理类中完成功能的增强
代码实现:
// 目标接口
public interface UserService {
void add();
void update();
void delete();
}
// 目标类
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
}
// 代理类
public class UserServiceProxy implements UserService {
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void add() {
System.out.println("====== add方法开始 ======");
userService.add();
System.out.println("====== add方法结束 ======");
}
@Override
public void update() {
System.out.println("====== update方法开始 ======");
userService.update();
System.out.println("====== update方法结束 ======");
}
@Override
public void delete() {
System.out.println("====== delete方法开始 ======");
userService.delete();
System.out.println("====== delete方法结束 ======");
}
}
观察上述代码,静态代理遵循开闭原则,在不修改目标类的前提下,完成了功能的增强,但是依然存在大量重复的代码,且一个代理类只能代理一个目标类,如果有n个目标类需要被代理,就需要同比增加n个代理类。
那么,有没有办法可以使得我们不需要去定义这么多的代理类,就可以实现对目标类功能的增强?答案是有的:动态代理。
3、JDK动态代理
前面,我们提到静态代理的实现方式:代理类和目标类都实现同一个接口,在代理类中维护目标类对象,并完成对目标类对象方法的增强,这种方式虽然遵循开闭原则,但是代理类和目标类至少是"一对一"的绑定关系,如果需要被代理的目标类个数越多,代理类就会越多,会产生大量重复的代码,也不利于后期的维护。
从静态代理中,我们知道代理类也是接口的一个实现类,代理对象的类型也是属于接口类型,我们来验证一下。
public class Test {
public static void main(String[] args) {
UserServiceProxy userServiceProxy = new UserServiceProxy(new UserServiceImpl());
System.out.println(userServiceProxy instanceof UserService);
}
}
// 打印结果:true
那么,能不能动态生成这些代理对象呢?我们知道类是构造对象的模板,代理类都还不存在,怎么去构造代理对象呢?
除了不存在代理类,还剩下 UserService
接口和 UserServiceImpl
目标类,JDK动态代理的目的就是通过接口来生成代理类以及代理类的对象,我们知道接口是不能直接通过new关键字创建对象的。
那么JDK动态代理是怎么创建出代理类以及代理类对象的呢?
我们先来看看通过 new
关键字创建对象的过程。
UserServiceImpl userService = new UserServiceImpl();
/*
创建对象的过程:
1.执行new指令,如果类未加载,先执行类加载过程。
1.加载:JVM通过ClassLoader将UserServiceImpl.class文件加载到方法区(Method Area),在堆内存中创建代表该类的Class对象。
2.验证
3.准备:为静态变量分配内存并设置类型初始值。
4.解析
5.初始化:为静态变量赋值、执行静态代码块
2.为对象分配内存,将对象的实例字段初始化类型零值。
3.执行构造方法,对对象进行初始化
*/
追踪上述过程,我们得知创建对象,需要先得到该类的Class对象,通过Class对象去创建实例对象。为了验证这一点,我们不妨来看看通过反射的方式创建对象的过程。
public class Test {
@SneakyThrows
public static void main(String[] args) {
// 获取Class对象
Class<UserServiceImpl> userServiceClass = UserServiceImpl.class;
// 获取构造器
Constructor<?>[] constructors = userServiceClass.getConstructors();
for (Constructor<?> constructor : constructors) {
// 通过构造器创建实例对象
System.out.println(constructor.newInstance());
}
}
}
现在,问题回归到接口不能直接new,也没有构造方法,并且不存在代理类的class文件,怎么获得Class对象了。
动态代理关键类
我们先来看看JDK动态代理的实战代码:
- 需要自定义个
CustomInvocationHandler
实现InvocationHandler
接口。 - 利用
Proxy.newProxyInstance
构建实例对象。
// UserService接口
public interface UserService {
void add();
void update();
void delete();
}
// 目标类
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
}
// CustomInvocationHandler
public class CustomInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== 方法开始 ======");
Object result = method.invoke(target, args);
System.out.println("====== 方法结束 ======");
return result;
}
}
public class Test {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
// 关键代码
UserService service = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new CustomInvocationHandler(userService));
service.add();
}
}
从测试代码可以看出,Proxy类是关键。我们来看看Proxy为我们提供的方法:
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
虽然被标注为过时方法,但是从名字上可以得知,其目的是为了获得代理类的Class对象。话不多说,我们来调用一下。
public class Test {
public static void main(String[] args) {
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
System.out.println(proxyClass.getName());
for (Method method : proxyClass.getDeclaredMethods()) {
System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");
}
System.out.println(Arrays.toString(proxyClass.getConstructors()));
}
}
可以看到:
- 获得的Class对象的名称为
$Proxy0
。 - 定义了我们需要的
add();update();delete()
方法。 - 定义了一个有参构造方法
$Proxy0(InvocationHandler handler)
。
虽然没有无参构造方法,我们还是得尝试一下调用一下这个有参的构造方法,需要我们传入一个 java.lang.reflect.InvocationHandler
对象
public class Test {
@SneakyThrows
public static void main(String[] args) {
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
// 获取$Proxy0(InvocationHandler handler)构造方法
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
UserService userService = (UserService) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
System.out.println(method.getDeclaringClass() + "." + method.getName() + "()");
return null;
}
});
userService.add();
}
}
看的出来,**当我们获得代理对象之后,通过代理对象来调用接口方法,都会回调构造时传进来的 InvocationHandler
对象的 invoke(Object proxy, Method method, Object[] args)
方法,**该方法有3个参数:
- Object proxy:代表的是代理对象本身。
- Method method:代表的是被调用的方法的Method对象。
- Object[] args:代表的是被调用方法的参数。
可以猜测,JDK动态代理生成的代理类中,维护了InvocationHandler类的对象变量,并且在实现接口方法时,通过InvocationHandler对象调用了 invoke(Object proxy, Method method, Object[] args)
方法。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
不知道大家没有看到这行代码哈,当添加了这行代码之后,可以将在项目目录下保存动态创建的class文件, com/sun/proxy/$Proxy0.class
。
可以看到生成的代理类 $Proxy0
继承自 Proxy
类,并实现了 UserService
接口,并且在 add()
方法中通过其父类 Proxy
中维护的 InvocationHandler
对象调用 invoke()
方法,这也就成功的解释了前面调用 userService.add()
方法,会回调到invoke()方法。
这时候我们再把代码改造一下,如下:
public class CustomInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== 方法开始 ======");
Object result = method.invoke(target, args);
System.out.println("====== 方法结束 ======");
return result;
}
}
public class Test {
@SneakyThrows
public static void main(String[] args) {
UserServiceImpl target = new UserServiceImpl();
Class<?> proxyClass = Proxy.getProxyClass(UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces());
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
UserService userService = (UserService) constructor.newInstance(new CustomInvocationHandler(target));
userService.add();
}
}
这样就完成了对目标对象功能的增强,前面我们提到过 Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
已经被标注为过时,推荐我们使用 Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法。
动态代理设计思想
好的,到这里,我们来总结一下JDK动态的设计思想:
使用 JDK动态代理
,使得我们免去编写代理类,只需要将增强功能编写在 InvocationHandler
的 invoke
方法中。
4、CGLib动态代理
CGLib代理的目标对象不需要事先任何接口,它是通过动态集成目标对象实现动态代理的。CGLib代理执行代理方法的效率之所以比JDK高,是因为CGLib采用了FastClass机制:为代理类和被代理类各生成一个类,这个类会为代理类或被代理类的方法分配一个index(int类型);这个index当作一个入参,FastClass 就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。FastClass并不是跟代理类一起生成的,而是在第一次执行MethodProxy的invoke()或invokeSuper()方法时产生并放在缓存中的。
5、CGLib和JDK动态代理对比
- JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象。
- JDK动态代理和CGLib代理在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架(字节码操控框架)写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低。
- JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高
6、Spring中的代理选择原则
- 当Bean有实现接口时,Spring就会用JDK动态代理
- 当Bean没有实现接口时,Spring会选择CGLib代理
- Spring可以通过配置强制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas=“true”>