代理模式
代理模式顾名思义,就是以你的名义帮你处理一些琐事,例如:用户A 近期准备结婚,他需要预定饭店,布置会场,邀请亲朋,制定菜单等等等等… 这样处理事情就比较麻烦,针对这些琐事就可以找 婚庆公司 来处理,他们可以解决婚礼过程中的大部分事情,这样用户A 只需要面对结婚这件事儿本身就可以,而不需要考虑其他的事情。
上面这个例子中 婚庆公司的定位就是代理,Java 中有些地方也需要使用代理模式进行解决,例如事务,日志等等,我们希望 service 中只需要负责业务逻辑本身而不要掺杂其他的代码,这里就可以用到代理模式
动态代理模式总结起来一句话:可以做到在不修改目标对象的功能前提下,对目标功能扩展
我们需要了解掌握三种代理模式:静态代理,JDK 动态代理,CGLIB 动态代理,我们通过日志的方式进行了解
静态代理模式
我们需要准备 service 接口以及代理类和被代理类:
正常执行添加日志
1 2 3 4 5
| public interface UserService { int insertUser(String username, String password); }
|
1 2 3 4 5 6 7 8 9 10
| public class UserServiceImpl implements UserService { @Override public int insertUser(String username, String password) { int count = 1; return count; } }
|
1 2 3 4 5 6 7
| public class AppTest{ public static void main(String[] args) { UserService us = new UserServiceImpl(); System.out.println(us.insertUser("老八", "秘制小汉堡")); } }
|
当我们运行测试类的 main 方法后发现控制台只输出了数字 1,如果我们想要实现日志功能的话,需要在实现类中添加打印语句,如下所示:
1 2 3 4 5 6 7 8 9 10 11
| public class UserServiceImpl implements UserService { @Override public int insertUser(String username, String password) { int count = 1; System.out.println("用户【" + username + "】插入成功!"); return count; } }
|
日志添加完成!
使用代理模式添加日志
但是按照之前的说法,我们不希望在 service 中掺杂其他的代码,它仅仅负责逻辑即可,所以我们要移除掉实现类中的输出语句,新建一个代理类来完成这部分功能:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class UserServiceProxy implements UserService { private UserService us; public UserServiceProxy( UserService us ){ this.us = us; } @Override public int insertUser(String username, String password) { int count = this.us.insertUser(username, password); System.out.println("用户【" + username + "】插入成功!"); return count; } }
|
1 2 3 4 5 6 7
| public class AppTest{ public static void main(String[] args) { UserService us = new UserServiceProxy( new UserServiceImpl() ); System.out.println(us.insertUser("老八", "秘制小汉堡")); } }
|
创建代理类的对象,将被代理的实例作为参数传过去,让代理类以自己的名义帮自己实现日志功能,这样实现类中只需要负责书写业务逻辑就可以了。这就是 ==静态代理模式==
JDK动态代理
静态代理可以做到在不修改源代码的情况下添加新的功能,但是如果 service 比较多的话静态代理的弊端就出来了,每个 service 都对应一个代理类,这样代码量就大大增加,这里就可以使用 JDK动态代理来解决这个问题:
代码实现
JDK动态代理依靠 java.lang.reflect.Proxy
实现,保持接口和实现类不动,新建代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class LogProxy { public static Object getInstance( Object obj){ ClassLoader classLoader = obj.getClass().getClassLoader(); Class<?>[] classes = obj.getClass().getInterfaces(); InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args)->{ Object result = method.invoke(obj, args); System.out.println("用户【" + Arrays.asList(args) + "】插入成功!"); return result; }; Object obj = Proxy.newProxyInstance(classLoader, classes, invocationHandler); return obj; } }
|
调用 LogProxy.getInstance
传入被代理的对象,通过 Proxy.newProxyInstance
来获取到代理对象,调用这个方法需要传入三个参数:
- 【classLoader】被代理的类的类加载器
- 【classes】被代理的类所实现的所有接口
- 【invocationHandler】是一个支持函数式的接口,直接创建对象即可,当被代理的类调用方法的时候会在这里进行执行,通过 lambda 创建该类的实例需要接受三个形参:
- 【proxy】暂时不用管…
- 【method】被代理的类执行的方法会在这里进行处理
- 【args】被代理类执行方法时传入的参数
- 最后记得将 invoke 执行函数后的结果返回,不然调用者那边会收不到返回结果
JDK动态代理的缺点
我们通过 Proxy.newProxyInstance
传参成功返回了代理对象 ,然后在测试类中成功转换为 UserService
实例进行日志增强,现在我们尝试打印一下转换之前的代理对象的 class 是什么样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class LogProxy { public static Object getInstance(Object obj) { ClassLoader classLoader = obj.getClass().getClassLoader(); Class<?>[] classes = obj.getClass().getInterfaces(); InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> method.invoke(obj, args); Object proxy = Proxy.newProxyInstance(classLoader, classes, invocationHandler); System.out.println(proxy.getClass()); return proxy; } }
|
打印结果为:class com.sun.proxy.$Proxy0
,并不是我们熟悉的类型,那为什么他可以转换为 UserService
类型呢?因为他获取了被代理目标类实现的所有接口:obj.getClass().getInterfaces()
,让我们打印一下代理对象实现的接口是什么样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class LogProxy { public static Object getInstance(Object obj) { ClassLoader classLoader = obj.getClass().getClassLoader(); Class<?>[] classes = obj.getClass().getInterfaces(); InvocationHandler invocationHandler = (Object proxy, Method method, Object[] args) -> method.invoke(obj, args); Object proxy = Proxy.newProxyInstance(classLoader, classes, invocationHandler); System.out.println(Arrays.asList(proxy.getClass().getInterfaces())); return proxy; } }
|
打印结果为:[interface site.hanzhe.proxy.jdkProxy.UserService]
,代理对象实现了和被代理对象一模一样的接口,所以他可以转换为 UserService
,那么如果我们被代理的类没有实现任何接口会怎么样呢?
1 2 3 4 5 6 7 8 9 10 11
| public class UserServiceImpl { public int insertUser(String username, String password) { int count = 1; System.out.println(username + "--" + password); return count; } }
|
被代理类修改完成了,代理类保持不变,在测试类中修改强制转换类型,然后进行测试
1 2 3 4 5 6 7
| public class AppTest{ public static void main(String[] args) { UserServiceImpl us = (UserServiceImpl)LogProxy.getInstance(new UserServiceImpl()); us.insertUser("老八", "秘制小汉堡"); } }
|
系统抛出了 java.lang.ClassCastException
异常,详细信息为 com.sun.proxy.$Proxy0 cannot be cast to site.hanzhe.proxy.jdkProxy.UserServiceImpl
,告诉我们无法强制转换为 UserServiceImpl
类型的对象,这样一来我们就测出了一个问题,JDK动态代理要求 被代理的对象必须实现至少一个接口
CGLIB动态代理
在之前的静态代理中,代理类与被代理类实现了同一个接口,从而达到对每个函数进行代理的目的,而在 JDK动态代理中要求被代理的类必须实现至少一个接口,那么如果我们被代理的类没有实现接口的话如何代理?
CGLIB动态代理模式,以被代理对象作为父类,动态创建一个子类作为代理对象并返回,CGLIB动态代理底层使用了 ASM字节码框架来生成子类,相比较比 Java反射效率要高
想要使用 CGLIB动态代理需要引入 CGLIB 和 ASM 的 jar 包,这里记录一下两个 jar 包所对应的 maven 坐标:
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.0</version> </dependency>
|
代码实现
1 2 3 4 5 6 7 8 9 10 11
| public class UserService { public int insertUser(String username, String password){ int count = 1; System.out.println(username + "--" + password); return count; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LogProxy { public static Object getInstance(Object obj){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(obj.getClass()); enhancer.setCallback( (InvocationHandler)(Object proxy, Method method, Object[] args)->{ Object result = method.invoke(obj, args); System.out.println("用户【" + Arrays.asList(args) + "】插入成功!"); return result; } ); return enhancer.create(); } }
|
代理类代码说明:
- 【Enhancer】CGLIB中的工具类,通过工具类来动态创建一个新的实例
- 【setSuperclass】设置对象继承的父类,该方法需要传入被代理类的 class 对象
- 【setCallback】通过回调的方式实现代理功能,该方法需要传入一个
Callback
对象, 这里使用他下面子接口 InvocationHandler
,也就是 JDK动态代理时手动创建的那个对象,由于类型原因在使用的时候使用了强制类型转换
- 【InvocationHandler】函数式接口的三个参数这里就不解释了,记得将 invoke 结果返回即可