代理模式
代理模式顾名思义,就是以你的名义帮你处理一些琐事,例如:用户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 结果返回即可