11. 基于JDK、Cglib实现AOP切面

工程结构

lqf-spring-step-11
├── main
│   ├── java
│   │   └── lqf
│   │       └── springframework
│   │           ├── aop
│   │           │   ├── AdvisedSupport.java
│   │           │   ├── ClassFilter.java
│   │           │   ├── MethodMatcher.java
│   │           │   ├── Pointcut.java
│   │           │   ├── TargetSource.java
│   │           │   ├── aspectj
│   │           │   │   └── AspectJExpressionPointcut.java
│   │           │   └── framework
│   │           │       ├── AopProxy.java
│   │           │       ├── Cglib2AopProxy.java
│   │           │       ├── JdkDynamicAopProxy.java
│   │           │       └── ReflectiveMethodInvocation.java
│   │           ├── beans
│   │           │   ├── BeansException.java
│   │           │   ├── PropertyValue.java
│   │           │   ├── PropertyValues.java
│   │           │   └── factory
│   │           │       ├── Aware.java
│   │           │       ├── BeanClassLoaderAware.java
│   │           │       ├── BeanFactory.java
│   │           │       ├── BeanFactoryAware.java
│   │           │       ├── BeanNameAware.java
│   │           │       ├── ConfigurableListableBeanFactory.java
│   │           │       ├── DisposableBean.java
│   │           │       ├── FactoryBean.java
│   │           │       ├── HierarchicalBeanFactory.java
│   │           │       ├── InitializingBean.java
│   │           │       ├── ListableBeanFactory.java
│   │           │       ├── config
│   │           │       │   ├── AutowireCapableBeanFactory.java
│   │           │       │   ├── BeanDefinition.java
│   │           │       │   ├── BeanFactoryPostProcessor.java
│   │           │       │   ├── BeanPostProcessor.java
│   │           │       │   ├── BeanReference.java
│   │           │       │   ├── ConfigurableBeanFactory.java
│   │           │       │   └── SingletonBeanRegistry.java
│   │           │       ├── support
│   │           │       │   ├── AbstractAutowireCapableBeanFactory.java
│   │           │       │   ├── AbstractBeanDefinitionReader.java
│   │           │       │   ├── AbstractBeanFactory.java
│   │           │       │   ├── BeanDefinitionReader.java
│   │           │       │   ├── BeanDefinitionRegistry.java
│   │           │       │   ├── CglibSubclassingInstantiationStrategy.java
│   │           │       │   ├── DefaultListableBeanFactory.java
│   │           │       │   ├── DefaultSingletonBeanRegistry.java
│   │           │       │   ├── DisposableBeanAdapter.java
│   │           │       │   ├── FactoryBeanRegistrySupport.java
│   │           │       │   ├── InstantiationStrategy.java
│   │           │       │   └── SimpleInstantiationStrategy.java
│   │           │       └── xml
│   │           │           └── XmlBeanDefinitionReader.java
│   │           ├── context
│   │           │   ├── ApplicationContext.java
│   │           │   ├── ApplicationContextAware.java
│   │           │   ├── ApplicationEvent.java
│   │           │   ├── ApplicationEventPublisher.java
│   │           │   ├── ApplicationListener.java
│   │           │   ├── ConfigurableApplicationContext.java
│   │           │   ├── event
│   │           │   │   ├── AbstractApplicationEventMulticaster.java
│   │           │   │   ├── ApplicationContextEvent.java
│   │           │   │   ├── ApplicationEventMulticaster.java
│   │           │   │   ├── ContextClosedEvent.java
│   │           │   │   ├── ContextRefreshedEvent.java
│   │           │   │   └── SimpleApplicationEventMulticaster.java
│   │           │   └── support
│   │           │       ├── AbstractApplicationContext.java
│   │           │       ├── AbstractRefreshableApplicationContext.java
│   │           │       ├── AbstractXmlApplicationContext.java
│   │           │       ├── ApplicationContextAwareProcessor.java
│   │           │       └── ClassPathXmlApplicationContext.java
│   │           ├── core
│   │           │   └── io
│   │           │       ├── ClassPathResource.java
│   │           │       ├── DefaultResourceLoader.java
│   │           │       ├── FileSystemResource.java
│   │           │       ├── Resource.java
│   │           │       ├── ResourceLoader.java
│   │           │       └── UrlResource.java
│   │           └── util
│   │               └── ClassUtils.java
│   └── resources
└── test
    ├── java
    │   └── lqf
    │       └── springframework
    │           ├── SpringTest.java
    │           ├── event
    │           │   ├── ContextClosedEventListener.java
    │           │   ├── ContextRefreshedEventListener.java
    │           │   ├── CustomEvent.java
    │           │   └── CustomEventListener.java
    │           └── service
    │               ├── IUserService.java
    │               ├── UserService.java
    │               └── UserServiceInterceptor.java
    └── resources
        ├── important.properties
        ├── spring.xml
        └── springPostProcessor.xml

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

  • 整个类关系图就是 AOP 实现核心逻辑的地方,上面部分是关于方法的匹配实现,下面从 AopProxy 开始是关于方法的代理操作。
  • AspectJExpressionPointcut 的核心功能主要依赖于 aspectj 组件并处理 Pointcut、ClassFilter、MethodMatcher
    接口实现,专门用于处理类和方法的匹配过滤操作。
  • AopProxy 是代理的抽象对象,它的实现主要是基于 JDK 的代理和 Cglib 代理。在前面章节关于对象的实例化
    CglibSubclassingInstantiationStrategy,我们也使用过 Cglib 提供的功能。

设计

在把 AOP 整个切面设计融合到 Spring 前,我们需要解决两个问题,包括:如何给符合规则的方法做代理
以及做完代理方法的案例后,把类的职责拆分出来

而这两个功能点的实现,都是以切面的思想进行设计和开发。如果不是很清楚 AOP 是啥,你可以把切面理解为用刀切韭菜,一根一根切总是有点慢,
那么用手(代理)把韭菜捏成一把,用菜刀或者斧头这样不同的拦截操作来处理。而程序中其实也是一样,只不过韭菜变成了方法,菜刀变成了拦截方法。
整体设计结构如下图:

  • 就像在使用 Spring 的 AOP 一样,只处理一些需要被拦截的方法。在拦截方法后,执行你对方法的扩展操作。
  • 那么我们就需要先来实现一个可以代理方法的 Proxy,其实代理方法主要是使用到方法拦截器类处理方法的调用
    MethodInterceptor#invoke,而不是直接使用 invoke 方法中的入参 Method method 进行 method.invoke(targetObj, args)
    这块是整个使用时的差异。
  • 除了以上的核心功能实现,还需要使用到 org.aspectj.weaver.tools.PointcutParser
    处理拦截表达式 execution(* lqf.springframework.service.IUserService.*(..)),有了方法代理和处理拦截,我们就可以完成设计出一个
    AOP 的雏形了。

实现

切点表达式

  • 定义接口
    • 源码1: lqf.springframework.aop.Pointcut
      • 切入点接口,定义用于获取 ClassFilter、MethodMatcher 的两个类,这两个接口获取都是切点表达式提供的内容。
    • 源码2: lqf.springframework.aop.MethodMatcher
      +
      方法匹配,找到表达式范围内匹配下的目标类和方法。在上文的案例中有所体现:methodMatcher.matches(method, targetObj.getClass())
  • 实现切点表达式类
    • 源码: lqf.springframework.aop.aspectj.AspectJExpressionPointcut
      • AspectJExpressionPointcut 类是 AspectJ 表达式的实现类,主要是用于处理切点表达式的解析和匹配操作。
      • 在 AspectJExpressionPointcut 类中,主要是使用到了 AspectJ 提供的 PointcutParser 处理切点表达式,然后再通过
        PointcutExpression#matches 进行匹配操作。

包装切面通知信息

源码: lqf.springframework.aop.AdvisedSupport

  • AdvisedSupport,主要是用于把代理、拦截、匹配的各项属性包装到一个类中,方便在 Proxy 实现类进行使用。
  • TargetSource,是一个目标对象,在目标对象类中提供 Object 入参属性,以及获取目标类 TargetClass 信息。
  • MethodInterceptor,是一个具体拦截方法实现类,由用户自己实现 MethodInterceptor#invoke 方法,做具体的处理。
  • MethodMatcher,是一个匹配方法的操作,这个对象由 AspectJExpressionPointcut 提供服务。

代理抽象实现(JDK&Cglib)

定义接口 源码1: lqf.springframework.aop.framework.AopProxy

  • 定义一个标准接口,用于获取代理类。因为具体实现代理的方式可以有 JDK 方式,也可以是 Cglib 方式,所以定义接口会更加方便管理实现类。

源码2: lqf.springframework.aop.framework.JdkDynamicAopProxy

  • 基于 JDK 实现的代理类,需要实现接口 AopProxy、InvocationHandler,这样就可以把代理对象 getProxy 和反射调用方法 invoke
    分开处理了。
  • getProxy 方法中的是代理一个对象的操作,需要提供入参 ClassLoader、AdvisedSupport、和当前这个类 this,因为这个类提供了
    invoke 方法。
  • invoke 方法中主要处理匹配的方法后,使用用户自己提供的方法拦截实现,做反射调用 methodInterceptor.invoke 。
  • 这里还有一个 ReflectiveMethodInvocation,其他它就是一个入参的包装信息,提供了入参对象:目标对象、方法、入参。

源码3: lqf.springframework.aop.framework.Cglib2AopProxy

  • 基于 Cglib 使用 Enhancer 代理的类可以在运行期间为接口使用底层 ASM 字节码增强技术处理对象的代理对象生成,因此被代理类不需要实现任何接口。
  • 关于扩展进去的用户拦截方法,主要是在 Enhancer#setCallback 中处理,用户自己的新增的拦截处理。这里可以看到
    DynamicAdvisedInterceptor#intercept 匹配方法后做了相应的反射操作。

总结

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

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

    返回目录