项目背景

  1. 由于项目中会需要进行入参日志打印、处理结果打印、权限控制等业务逻辑处理,而这些业务逻辑功能又是多变的,如果都将这些业务逻辑放在主要逻辑之中,那么随着时间的增加,代码会越来越杂。<br />为了降低代码的耦合度,提高代码逻辑辨识。可以使用代理模式去抽离业务逻辑,将业务逻辑独立出来。<br />但是业务逻辑有时候也不会只有一种,例如某接口即需要入参打印,又需要权限处理,那么这类情况最好将不同的业务逻辑也进行拆分。

构建cglib代理

1. 目标方法

假设目标方法长这样:

  1. public class ToolService {
  2. public void run(String[] args) {
  3. System.out.println("执行目标对象方法;args="+ Arrays.toString(args));
  4. try {
  5. Thread.sleep(100); // 模拟调用时长
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. }

2. 子类代理回调的拦截器

如果使用cglib代理,那么需要先引用cglib的依赖。

  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib</artifactId>
  4. <version>3.2.10</version>
  5. </dependency>

那么创建一回调拦截器,实现 MethodInterceptor接口,用于处理子类代理调用方法时回调的逻辑,也即真正代理的逻辑。

  1. public class ToolProxyCallback implements MethodInterceptor {
  2. // 目标对象
  3. private Object target;
  4. public ToolProxyCallback(Object target) {
  5. this.target = target;
  6. }
  7. @Override
  8. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  9. // 入参日志打印
  10. System.out.println("准备执行目标方法;参数信息为="+args);
  11. // 权限拦截
  12. if(args.length <= 2) { // 假设这里的权限是参数不能小于2个
  13. return null;
  14. }
  15. // 通过反射执行目标方法
  16. Object resultObj = method.invoke(target,args);
  17. // 处理接口打印
  18. System.out.println("目标方法请求结果为;"+resultObj);
  19. return resultObj;
  20. }
  21. }

3. 创建子类代理的工厂

还需要一个工厂来创建子类代理的实例。

  1. public class ToolProxyFactory {
  2. /**
  3. * 获取子类代理实例
  4. * @param target 目标对象
  5. * @param callback 回调接口
  6. * @return
  7. */
  8. public static Object getProxyInstance(Object target, Callback callback) {
  9. // 工具类
  10. Enhancer enhancer = new Enhancer();
  11. // 设置父类
  12. enhancer.setSuperclass(target.getClass());
  13. // 设置回调函数。
  14. enhancer.setCallback(callback);
  15. return enhancer.create(); // 创建子类代理对象
  16. }
  17. }

4. 客户端执行调用

那么调用目标方法就变为这样。

  1. @Test
  2. public void testProjectProxy() {
  3. ToolService service = new ToolService();
  4. Callback toolProxyCallback = new ToolProxyCallback(service);
  5. ToolService serviceProxy = (ToolService)ToolProxyFactory.getProxyInstance(service,toolProxyCallback);
  6. /**
  7. * 使用 ToolProxyFactory 构建出 ToolCglibService的代理子类,通过代理子类去调用接口方法
  8. */
  9. String[] args = new String[]{"-s","test","-n","zhangsan"};
  10. serviceProxy.run(args);
  11. }

这样就可以实现将业务逻辑与核心逻辑抽离开,通过cglib提供的接口实例化出目标对象的子类实例,然后用这个子类实例调用父类的方法,也即我们需要代理的目标方法。

优化一:将代理的职责拆分

但是~这样就ok了嘛?
有木有发现 回调拦截器这个方法 ToolProxyCallback#intercept,存在职责不单一的情况。假设这里的权限拦截有着很复杂的逻辑,又或是这个权限拦截在其他接口需要用到的,那么是不是就需要把这个逻辑给抽离出来呢。
业务小的话是没关系的,但是如果考虑到后续前置/后置逻辑会不断累积,那么就需要将回调拦截器中的业务逻辑按照职责拆分单独出来了。
针对代理拦截的逻辑可以知道,有前置逻辑和后置逻辑需要处理。那么抽象为父类

  1. public abstract class AbstractInterceptor {
  2. /**
  3. * 前置逻辑
  4. * @param target 目标对象
  5. * @param method 目标方法
  6. * @param args 参数
  7. * @throws Exception
  8. */
  9. public abstract void before(Object target, Method method,Object[] args) throws Exception;
  10. /***
  11. * 后置逻辑
  12. * @param target 目标对象
  13. * @param method 目标方法
  14. * @param result 处理结果
  15. * @throws Exception
  16. */
  17. public abstract void after(Object target,Method method,Object result) throws Exception;
  18. }

自定义注解Order,用于拦截器调用的先后顺序;

  1. /**
  2. * order 值越小 表示优先级越高
  3. * -1 为默认值,可表示系统拦截
  4. */
  5. @Target(ElementType.TYPE)
  6. @Retention(RetentionPolicy.RUNTIME)
  7. public @interface Order {
  8. int value() default -1;
  9. }

定义LogInterceptor,继承AbstractInterceptor,用于关键日志打印;

  1. @Order(1)
  2. public class LogInterceptor extends AbstractInterceptor {
  3. private long startTime;
  4. private long endTime;
  5. private static Logger logger = Logger.getLogger(LogInterceptor.class.getName());
  6. @Override
  7. public void before(Object target, Method method,Object[] args) {
  8. startTime = System.currentTimeMillis();
  9. logger.info("代理对象方法执行。参数args="+args);
  10. }
  11. @Override
  12. public void after(Object target,Method method,Object result) {
  13. endTime = System.currentTimeMillis();
  14. logger.info("方法执行结束。耗时:" + (endTime - startTime) + "ms");
  15. }
  16. }

定义AuthInterceptor拦截器,用于接口权限拦截;

  1. @Order(2)
  2. public class AuthInterceptor extends AbstractInterceptor {
  3. @Override
  4. public void before(Object target, Method method, Object[] args) throws Exception {
  5. System.out.println("run start..."+ this.getClass());
  6. }
  7. @Override
  8. public void after(Object target, Method method, Object result) throws Exception {
  9. System.out.println("run end..."+this.getClass());
  10. }
  11. }

修改下前面的回调拦截器的方法,新增interceptorList列表,用于存储拦截器,注意这里是先后顺序存储,先后调用。

  1. public class ToolProxyCallback implements MethodInterceptor {
  2. private Object target;
  3. public ToolProxyCallback(Object target) {
  4. this.target = target;
  5. }
  6. private static List<AbstractInterceptor> interceptorList = new LinkedList<>();
  7. // 注册拦截器
  8. public static void registerInterceptor(AbstractInterceptor interceptor) {
  9. interceptorList.add(interceptor);
  10. }
  11. @Override
  12. public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  13. // 执行拦截器的前置方法
  14. for(AbstractInterceptor interceptor : interceptorList) {
  15. interceptor.before(target,method,args);
  16. }
  17. System.out.println("准备执行目标方法;interceptorList.size="+interceptorList.size());
  18. Object resultObj = method.invoke(target,args);
  19. // 执行拦截器的后置方法
  20. for(AbstractInterceptor interceptor : interceptorList) {
  21. interceptor.after(target,method,resultObj);
  22. }
  23. return resultObj;
  24. }
  25. }

添加拦截器,那么调用ToolProxyCallback#registerInterceptor 方法,将拦截器注册到interceptorList即可。

优化二:自动化注册拦截器

后续如果想要新增拦截器,那么只需要继承AbstractInterceptor就行。但是每次添加一个拦截器都要主动注册一下,还要判断下先后顺序,那就有点麻烦。
新增拦截器注解,通过扫描包,将有该注解的拦截器都注册进来,就可以实现自动化注册了。

  1. /**
  2. * 有该注解表示为自定义拦截器
  3. */
  4. @Target(ElementType.TYPE)
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface ToolAspect {
  7. }

通过扫描包,将拦截器统统注册进来。

  1. public class AnnotationScanner {
  2. private static final String packageName = "agent.project";
  3. /**
  4. * 自动注册工具拦截器
  5. */
  6. public static void registerToolInterceptorList() {
  7. List<Class> list = ClassUtil.getAllClassByAnnotation(ToolAspect.class,packageName);
  8. // 从小到大排序
  9. Collections.sort(list, new Comparator<Class>() {
  10. @Override
  11. public int compare(Class clazz1, Class clazz2) {
  12. Order order1 = (Order)clazz1.getAnnotation(Order.class);
  13. Order order2 = (Order)clazz2.getAnnotation(Order.class);
  14. return order1.value() - order2.value();
  15. }
  16. });
  17. for(Class clazz : list) {
  18. AbstractInterceptor interceptor = null;
  19. try {
  20. interceptor = (AbstractInterceptor)clazz.newInstance();
  21. } catch (InstantiationException e) {
  22. e.printStackTrace();
  23. } catch (IllegalAccessException e) {
  24. e.printStackTrace();
  25. }
  26. // 注册拦截器
  27. ToolProxyCallback.registerInterceptor(interceptor);
  28. }
  29. }
  30. }

还记得前面的代理工厂吗 ToolProxyFactory。由于我们每次获取子类代理实例的时候都会使用这个类,那么将自动注册的调用放在这里就合适了。

  1. public class ToolProxyFactory {
  2. static {
  3. AnnotationScanner.registerToolInterceptorList(); // 注册拦截器
  4. }
  5. /**
  6. * 获取子类代理实例
  7. * @param target 目标对象
  8. * @param callback 回调接口
  9. * @return
  10. */
  11. public static Object getProxyInstance(Object target, Callback callback) {
  12. // 工具类
  13. Enhancer enhancer = new Enhancer();
  14. // 设置父类
  15. enhancer.setSuperclass(target.getClass());
  16. // 设置回调函数。
  17. enhancer.setCallback(callback);
  18. return enhancer.create(); // 创建子类代理对象
  19. }
  20. }

通过上述的优化一、二,实现了将代理职责拆分,同时自动化注册拦截器。在这基础上,客户端调用的方法不变,依旧通过ToolProxyFactory#getProxyInstance方法获取到子类代理,然后执行目标方法。

再扩展优化思考

针对现有的实现架构,留下几个Q:

  • 自动化注册拦截器会为所有目标方法实现了所有拦截器,但并不是每个目标方法所需要的拦截器都是相同的,那么就需要为目标方法实现特定的拦截器;
  • 获取子类代理实例的逻辑略为繁琐,需要认识的类有点多,能不能实现自动代理;(通过注解或者其它方式)
  • 能不能搞成即可使用cglib实现,又可自动切换为接口代理实现;(根据判断目标方法有没目标接口?)
  • 对比下Spring Aop有何区别与相同之处;

https://juejin.cn/post/6997012989068984350