文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

炉火纯青,基于JDK和Cglib动态代理,实现AOP核心功能

2024-12-03 02:10

关注

目录

一、前言

为什么,你的代码总是糊到猪圈上?

怎么办,知道你在互联网,不知道你在哪个大厂。知道你在加班,不知道你在和哪个产品争辩。知道你在偷懒,不知道你要摸鱼到几点。知道你在搬砖,不知道你在盖哪个猪圈。

当你特别辛苦夜以继日的完成着,每天、每周、每月重复性的工作时,你能获得的成长是最小,得到的回报也是少的。留着最多的汗、拿着最少的钱

可能你一激动开始看源码,但不知道看完的源码能用到什么地方。看设计模式,看的时候懂,但改自己的代码又下不去手。其实一方面是本身技术栈的知识面不足,另外一方面是自己储备的代码也不够。最终也就导致根本没法把一些列的知识串联起来,就像你看了 HashMap,但也联想不到分库分表组件中的数据散列也会用到了 HashMap 中的扰动函数思想和泊松分布验证、看了Spring 源码,也读不出来 Mybatis 是如何解决只定义 Dao 接口就能使用配置或者注解对数据库进行 CRUD 操作、看来 JDK 的动态代理,也想不到 AOP 是如何设计的。所以成体系学习,加强技术栈知识的完整性,才能更好的用上这些学习到的编码能力。

二、目标

到本章节我们将要从 IOC 的实现,转入到关于 AOP(Aspect Oriented Programming) 内容的开发。在软件行业,AOP 意为:面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能功能的统一维护。其实 AOP 也是 OOP 的延续,在 Spring 框架中是一个非常重要的内容,使用 AOP 可以对业务逻辑的各个部分进行隔离,从而使各模块间的业务逻辑耦合度降低,提高代码的可复用性,同时也能提高开发效率。

关于 AOP 的核心技术实现主要是动态代理的使用,就像你可以给一个接口的实现类,使用代理的方式替换掉这个实现类,使用代理类来处理你需要的逻辑。比如:

  1. @Test 
  2. public void test_proxy_class() { 
  3.     IUserService userService = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserService.class}, (proxy, method, args) -> "你被代理了!"); 
  4.     String result = userService.queryUserInfo(); 
  5.     System.out.println("测试结果:" + result); 

代理类的实现基本都大家都见过,那么有了一个基本的思路后,接下来就需要考虑下怎么给方法做代理呢,而不是代理类。另外怎么去代理所有符合某些规则的所有类中方法呢。如果可以代理掉所有类的方法,就可以做一个方法拦截器,给所有被代理的方法添加上一些自定义处理,比如打印日志、记录耗时、监控异常等。

三、方案

在把 AOP 整个切面设计融合到 Spring 前,我们需要解决两个问题,包括:如何给符合规则的方法做代理,以及做完代理方法的案例后,把类的职责拆分出来。而这两个功能点的实现,都是以切面的思想进行设计和开发。如果不是很清楚 AOP 是啥,你可以把切面理解为用刀切韭菜,一根一根切总是有点慢,那么用手(代理)把韭菜捏成一把,用菜刀或者斧头这样不同的拦截操作来处理。而程序中其实也是一样,只不过韭菜变成了方法,菜刀变成了拦截方法。整体设计结构如下图:

四、实现

1. 工程结构

  1. small-spring-step-11 
  2. └── src 
  3.     ├── main 
  4.     │   └── java 
  5.     │       └── cn.bugstack.springframework 
  6.     │           ├── aop 
  7.     │           │   ├── aspectj 
  8.     │           │   │   └── AspectJExpressionPointcut.java 
  9.     │           │   ├── framework  
  10.     │           │   │   ├── AopProxy.java 
  11.     │           │   │   ├── Cglib2AopProxy.java 
  12.     │           │   │   ├── JdkDynamicAopProxy.java 
  13.     │           │   │   └── ReflectiveMethodInvocation.java 
  14.     │           │   ├── AdvisedSupport.java 
  15.     │           │   ├── ClassFilter.java 
  16.     │           │   ├── MethodMatcher.java 
  17.     │           │   ├── Pointcut.java 
  18.     │           │   └── TargetSource.java 
  19.     │           ├── beans 
  20.     │           │   ├── factory 
  21.     │           │   │   ├── config 
  22.     │           │   │   │   ├── AutowireCapableBeanFactory.java 
  23.     │           │   │   │   ├── BeanDefinition.java 
  24.     │           │   │   │   ├── BeanFactoryPostProcessor.java 
  25.     │           │   │   │   ├── BeanPostProcessor.java 
  26.     │           │   │   │   ├── BeanReference.java 
  27.     │           │   │   │   ├── ConfigurableBeanFactory.java 
  28.     │           │   │   │   └── SingletonBeanRegistry.java 
  29.     │           │   │   ├── support 
  30.     │           │   │   │   ├── AbstractAutowireCapableBeanFactory.java 
  31.     │           │   │   │   ├── AbstractBeanDefinitionReader.java 
  32.     │           │   │   │   ├── AbstractBeanFactory.java 
  33.     │           │   │   │   ├── BeanDefinitionReader.java 
  34.     │           │   │   │   ├── BeanDefinitionRegistry.java 
  35.     │           │   │   │   ├── CglibSubclassingInstantiationStrategy.java 
  36.     │           │   │   │   ├── DefaultListableBeanFactory.java 
  37.     │           │   │   │   ├── DefaultSingletonBeanRegistry.java 
  38.     │           │   │   │   ├── DisposableBeanAdapter.java 
  39.     │           │   │   │   ├── FactoryBeanRegistrySupport.java 
  40.     │           │   │   │   ├── InstantiationStrategy.java 
  41.     │           │   │   │   └── SimpleInstantiationStrategy.java   
  42.     │           │   │   ├── support 
  43.     │           │   │   │   └── XmlBeanDefinitionReader.java 
  44.     │           │   │   ├── Aware.java 
  45.     │           │   │   ├── BeanClassLoaderAware.java 
  46.     │           │   │   ├── BeanFactory.java 
  47.     │           │   │   ├── BeanFactoryAware.java 
  48.     │           │   │   ├── BeanNameAware.java 
  49.     │           │   │   ├── ConfigurableListableBeanFactory.java 
  50.     │           │   │   ├── DisposableBean.java 
  51.     │           │   │   ├── FactoryBean.java 
  52.     │           │   │   ├── HierarchicalBeanFactory.java 
  53.     │           │   │   ├── InitializingBean.java 
  54.     │           │   │   └── ListableBeanFactory.java 
  55.     │           │   ├── BeansException.java 
  56.     │           │   ├── PropertyValue.java 
  57.     │           │   └── PropertyValues.java  
  58.     │           ├── context 
  59.     │           │   ├── event 
  60.     │           │   │   ├── AbstractApplicationEventMulticaster.java  
  61.     │           │   │   ├── ApplicationContextEvent.java  
  62.     │           │   │   ├── ApplicationEventMulticaster.java  
  63.     │           │   │   ├── ContextClosedEvent.java  
  64.     │           │   │   ├── ContextRefreshedEvent.java  
  65.     │           │   │   └── SimpleApplicationEventMulticaster.java  
  66.     │           │   ├── support 
  67.     │           │   │   ├── AbstractApplicationContext.java  
  68.     │           │   │   ├── AbstractRefreshableApplicationContext.java  
  69.     │           │   │   ├── AbstractXmlApplicationContext.java  
  70.     │           │   │   ├── ApplicationContextAwareProcessor.java  
  71.     │           │   │   └── ClassPathXmlApplicationContext.java  
  72.     │           │   ├── ApplicationContext.java  
  73.     │           │   ├── ApplicationContextAware.java  
  74.     │           │   ├── ApplicationEvent.java  
  75.     │           │   ├── ApplicationEventPublisher.java  
  76.     │           │   ├── ApplicationListener.java  
  77.     │           │   └── ConfigurableApplicationContext.java 
  78.     │           ├── core.io 
  79.     │           │   ├── ClassPathResource.java  
  80.     │           │   ├── DefaultResourceLoader.java  
  81.     │           │   ├── FileSystemResource.java  
  82.     │           │   ├── Resource.java  
  83.     │           │   ├── ResourceLoader.java  
  84.     │           │   └── UrlResource.java 
  85.     │           └── utils 
  86.     │               └── ClassUtils.java 
  87.     └── test 
  88.         └── java 
  89.             └── cn.bugstack.springframework.test 
  90.                 ├── bean 
  91.                 │   ├── IUserService.java 
  92.                 │   ├── UserService.java 
  93.                 │   └── UserServiceInterceptor.java 
  94.                 └── ApiTest.java 

工程源码:公众号「bugstack虫洞栈」,回复:Spring 专栏,获取完整源码

AOP 切点表达式和使用以及基于 JDK 和 CGLIB 的动态代理类关系,如图 12-2

图 12-2

2. 代理方法案例

在实现 AOP 的核心功能之前,我们先做一个代理方法的案例,通过这样一个可以概括代理方法的核心全貌,可以让大家更好的理解后续拆解各个方法,设计成解耦功能的 AOP 实现过程。

单元测试

  1. @Test 
  2. public void test_proxy_method() { 
  3.     // 目标对象(可以替换成任何的目标对象) 
  4.     Object targetObj = new UserService(); 
  5.     // AOP 代理 
  6.     IUserService proxy = (IUserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), targetObj.getClass().getInterfaces(), new InvocationHandler() { 
  7.         // 方法匹配器 
  8.         MethodMatcher methodMatcher = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))"); 
  9.         @Override 
  10.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  11.             if (methodMatcher.matches(method, targetObj.getClass())) { 
  12.                 // 方法拦截器 
  13.                 MethodInterceptor methodInterceptor = invocation -> { 
  14.                     long start = System.currentTimeMillis(); 
  15.                     try { 
  16.                         return invocation.proceed(); 
  17.                     } finally { 
  18.                         System.out.println("监控 - Begin By AOP"); 
  19.                         System.out.println("方法名称:" + invocation.getMethod().getName()); 
  20.                         System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms"); 
  21.                         System.out.println("监控 - End\r\n"); 
  22.                     } 
  23.                 }; 
  24.                 // 反射调用 
  25.                 return methodInterceptor.invoke(new ReflectiveMethodInvocation(targetObj, method, args)); 
  26.             } 
  27.             return method.invoke(targetObj, args); 
  28.         } 
  29.     }); 
  30.     String result = proxy.queryUserInfo(); 
  31.     System.out.println("测试结果:" + result); 

首先整个案例的目标是给一个 UserService 当成目标对象,对类中的所有方法进行拦截添加监控信息打印处理。

从案例中你可以看到有代理的实现 Proxy.newProxyInstance,有方法的匹配 MethodMatcher,有反射的调用 invoke(Object proxy, Method method, Object[] args),也用用户自己拦截方法后的操作。这样一看其实和我们使用的 AOP 就非常类似了,只不过你在使用 AOP 的时候是框架已经提供更好的功能,这里是把所有的核心过程给你展示出来了。

测试结果

  1. 监控 - Begin By AOP 
  2. 方法名称:queryUserInfo 
  3. 方法耗时:86ms 
  4. 监控 - End 
  5.  
  6. 测试结果:小傅哥,100001,深圳 
  7.  
  8. Process finished with exit code 0 

拆解案例

图 12-3

3. 切点表达式

定义接口

cn.bugstack.springframework.aop.Pointcut

  1. public interface Pointcut { 
  2.  
  3.      
  4.     ClassFilter getClassFilter(); 
  5.  
  6.      
  7.     MethodMatcher getMethodMatcher(); 
  8.  

cn.bugstack.springframework.aop.ClassFilter

  1. public interface ClassFilter { 
  2.  
  3.      
  4.     boolean matches(Class clazz); 
  5.  

cn.bugstack.springframework.aop.MethodMatcher

  1. public interface MethodMatcher { 
  2.  
  3.      
  4.     boolean matches(Method method, Class targetClass); 
  5.      

实现切点表达式类

  1. public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher { 
  2.  
  3.     private static final Set SUPPORTED_PRIMITIVES = new HashSet(); 
  4.  
  5.     static { 
  6.         SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION); 
  7.     } 
  8.  
  9.     private final PointcutExpression pointcutExpression; 
  10.  
  11.     public AspectJExpressionPointcut(String expression) { 
  12.         PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, this.getClass().getClassLoader()); 
  13.         pointcutExpression = pointcutParser.parsePointcutExpression(expression); 
  14.     } 
  15.  
  16.     @Override 
  17.     public boolean matches(Class clazz) { 
  18.         return pointcutExpression.couldMatchJoinPointsInType(clazz); 
  19.     } 
  20.  
  21.     @Override 
  22.     public boolean matches(Method method, Class targetClass) { 
  23.         return pointcutExpression.matchesMethodExecution(method).alwaysMatches(); 
  24.     } 
  25.  
  26.     @Override 
  27.     public ClassFilter getClassFilter() { 
  28.         return this; 
  29.     } 
  30.  
  31.     @Override 
  32.     public MethodMatcher getMethodMatcher() { 
  33.         return this; 
  34.     } 
  35.  

匹配验证

  1. @Test 
  2. public void test_aop() throws NoSuchMethodException { 
  3.     AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.UserService.*(..))"); 
  4.     Class clazz = UserService.class; 
  5.     Method method = clazz.getDeclaredMethod("queryUserInfo");    
  6.  
  7.     System.out.println(pointcut.matches(clazz)); 
  8.     System.out.println(pointcut.matches(method, clazz));           
  9.      
  10.     // truetrue 

这里单独提供出来一个匹配方法的验证测试,可以看看你拦截的方法与对应的对象是否匹配。

4. 包装切面通知信息

cn.bugstack.springframework.aop.AdvisedSupport

  1. public class AdvisedSupport { 
  2.  
  3.     // 被代理的目标对象 
  4.     private TargetSource targetSource; 
  5.     // 方法拦截器 
  6.     private MethodInterceptor methodInterceptor; 
  7.     // 方法匹配器(检查目标方法是否符合通知条件) 
  8.     private MethodMatcher methodMatcher; 
  9.      
  10.     // ...get/set 

5. 代理抽象实现(JDK&Cglib)

定义接口

cn.bugstack.springframework.aop.framework

  1. public interface AopProxy { 
  2.  
  3.     Object getProxy(); 
  4.  

cn.bugstack.springframework.aop.framework.JdkDynamicAopProxy

  1. public class JdkDynamicAopProxy implements AopProxy, InvocationHandler { 
  2.  
  3.     private final AdvisedSupport advised; 
  4.  
  5.     public JdkDynamicAopProxy(AdvisedSupport advised) { 
  6.         this.advised = advised; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object getProxy() { 
  11.         return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), advised.getTargetSource().getTargetClass(), this); 
  12.     } 
  13.  
  14.     @Override 
  15.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  16.         if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { 
  17.             MethodInterceptor methodInterceptor = advised.getMethodInterceptor(); 
  18.             return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args)); 
  19.         } 
  20.         return method.invoke(advised.getTargetSource().getTarget(), args); 
  21.     } 
  22.  

cn.bugstack.springframework.aop.framework.Cglib2AopProxy

  1. public class Cglib2AopProxy implements AopProxy { 
  2.  
  3.     private final AdvisedSupport advised; 
  4.  
  5.     public Cglib2AopProxy(AdvisedSupport advised) { 
  6.         this.advised = advised; 
  7.     } 
  8.  
  9.     @Override 
  10.     public Object getProxy() { 
  11.         Enhancer enhancer = new Enhancer(); 
  12.         enhancer.setSuperclass(advised.getTargetSource().getTarget().getClass()); 
  13.         enhancer.setInterfaces(advised.getTargetSource().getTargetClass()); 
  14.         enhancer.setCallback(new DynamicAdvisedInterceptor(advised)); 
  15.         return enhancer.create(); 
  16.     } 
  17.  
  18.     private static class DynamicAdvisedInterceptor implements MethodInterceptor { 
  19.  
  20.         @Override 
  21.         public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 
  22.             CglibMethodInvocation methodInvocation = new CglibMethodInvocation(advised.getTargetSource().getTarget(), method, objects, methodProxy); 
  23.             if (advised.getMethodMatcher().matches(method, advised.getTargetSource().getTarget().getClass())) { 
  24.                 return advised.getMethodInterceptor().invoke(methodInvocation); 
  25.             } 
  26.             return methodInvocation.proceed(); 
  27.         } 
  28.     } 
  29.  
  30.     private static class CglibMethodInvocation extends ReflectiveMethodInvocation { 
  31.  
  32.         @Override 
  33.         public Object proceed() throws Throwable { 
  34.             return this.methodProxy.invoke(this.target, this.arguments); 
  35.         } 
  36.  
  37.     } 
  38.  

基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。

关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到 DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

五、测试

1. 事先准备

  1. public class UserService implements IUserService { 
  2.  
  3.     public String queryUserInfo() { 
  4.         try { 
  5.             Thread.sleep(new Random(1).nextInt(100)); 
  6.         } catch (InterruptedException e) { 
  7.             e.printStackTrace(); 
  8.         } 
  9.         return "小傅哥,100001,深圳"
  10.     } 
  11.  
  12.     public String register(String userName) { 
  13.         try { 
  14.             Thread.sleep(new Random(1).nextInt(100)); 
  15.         } catch (InterruptedException e) { 
  16.             e.printStackTrace(); 
  17.         } 
  18.         return "注册用户:" + userName + " success!"
  19.     } 
  20.  

2. 自定义拦截方法

  1. public class UserServiceInterceptor implements MethodInterceptor { 
  2.  
  3.     @Override 
  4.     public Object invoke(MethodInvocation invocation) throws Throwable { 
  5.         long start = System.currentTimeMillis(); 
  6.         try { 
  7.             return invocation.proceed(); 
  8.         } finally { 
  9.             System.out.println("监控 - Begin By AOP"); 
  10.             System.out.println("方法名称:" + invocation.getMethod()); 
  11.             System.out.println("方法耗时:" + (System.currentTimeMillis() - start) + "ms"); 
  12.             System.out.println("监控 - End\r\n"); 
  13.         } 
  14.     } 
  15.  

3. 单元测试

  1. @Test 
  2. public void test_dynamic() { 
  3.     // 目标对象 
  4.     IUserService userService = new UserService();      
  5.  
  6.     // 组装代理信息 
  7.     AdvisedSupport advisedSupport = new AdvisedSupport(); 
  8.     advisedSupport.setTargetSource(new TargetSource(userService)); 
  9.     advisedSupport.setMethodInterceptor(new UserServiceInterceptor()); 
  10.     advisedSupport.setMethodMatcher(new AspectJExpressionPointcut("execution(* cn.bugstack.springframework.test.bean.IUserService.*(..))")); 
  11.      
  12.     // 代理对象(JdkDynamicAopProxy) 
  13.     IUserService proxy_jdk = (IUserService) new JdkDynamicAopProxy(advisedSupport).getProxy(); 
  14.     // 测试调用 
  15.     System.out.println("测试结果:" + proxy_jdk.queryUserInfo()); 
  16.      
  17.     // 代理对象(Cglib2AopProxy) 
  18.     IUserService proxy_cglib = (IUserService) new Cglib2AopProxy(advisedSupport).getProxy(); 
  19.     // 测试调用 
  20.     System.out.println("测试结果:" + proxy_cglib.register("花花")); 

测试结果

  1. 监控 - Begin By AOP 
  2. 方法名称:public abstract java.lang.String cn.bugstack.springframework.test.bean.IUserService.queryUserInfo() 
  3. 方法耗时:86ms 
  4. 监控 - End 
  5.  
  6. 测试结果:小傅哥,100001,深圳 
  7. 监控 - Begin By AOP 
  8. 方法名称:public java.lang.String cn.bugstack.springframework.test.bean.UserService.register(java.lang.String) 
  9. 方法耗时:97ms 
  10. 监控 - End 
  11.  
  12. 测试结果:注册用户:花花 success! 
  13.  
  14. Process finished with exit code 0 

如 AOP 功能定义一样,我们可以通过这样的代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印。

六、总结

从本文对 Proxy#newProxyInstance、MethodInterceptor#invoke,的使用验证切面核心原理以及再把功能拆解到 Spring 框架实现中,可以看到一个貌似复杂的技术其实核心内容往往没有太多,但因为需要为了满足后续更多的扩展就需要进行职责解耦和包装,通过这样设计模式的使用,以此让调用方能更加简化,自身也可以不断按需扩展。

AOP 的功能实现目前还没有与 Spring 结合,只是对切面技术的一个具体实现,你可以先学习到如何处理代理对象、过滤方法、拦截方法,以及使用 Cglib 和 JDK 代理的区别,其实这与的技术不只是在 Spring 框架中有所体现,在其他各类需要减少人工硬编码的场景下,都会用到。比如RPC、Mybatis、MQ、分布式任务

 

一些核心技术的使用上,都是具有很强的关联性的,它们也不是孤立存在的。而这个能把整个技术栈串联起来的过程,需要你来大量的学习、积累、由点到面的铺设,才能在一个知识点的学习拓展到一个知识面和知识体系的建设。

 

来源:bugstack虫洞栈内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯