前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Framework 源码学习笔记(七)- AOP

Spring Framework 源码学习笔记(七)- AOP

作者头像
RiemannHypothesis
发布2022-08-19 16:02:08
2180
发布2022-08-19 16:02:08
举报
文章被收录于专栏:Elixir

Chapter 07 Spring AOP 基操及源码

Section 01 - AOP基操

新建一个Spring Boot项目spring-aop,添加AOP 相关的依赖

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

新建controller包,增加一个HelloController类

代码语言:javascript
复制
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "Hello AOP";
    }
}

新建一个config包,首先新增一个BeanConfig,将所有的Bean注册到容器中

代码语言:javascript
复制
@Configuration
@ComponentScan(value = {"com.citi"})
public class BeanConfig {
}

再新建一个aop包,增加一个LogAspects类,代码如下

代码语言:javascript
复制
@Component
@Aspect
public class LogAspects {

    @Pointcut("execution(public * com.citi.controller.*.*(..))")
    public void pointCut(){
    }

    @Before("pointCut()")
    public void logStart(){
        System.out.println("方法调用前的输出");
    }

    @After("pointCut()")
    public void logEnd(){
        System.out.println("方法调用后的输出");
    }

    @AfterReturning("pointCut()")
    public void logReturn(){
        System.out.println("方法输出返回后输出");
    }

    @AfterThrowing("pointCut()")
    public void logException(){
        System.out.println("方法抛出异常后输出");
    }

    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知:方法执行前");
        Object o = joinPoint.proceed();
        System.out.println("环绕通知:方法执行后");
        return o;
    }
}
代码语言:javascript
复制
 通知方法,方法名可以自定义,注解不可以省略
* 前置通知:logStart(),注解@Before
* 后置通知:logEnd(),@After
* 返回通知:logReturn(),方法正常返回后运行, @AfterReturing
* 异常通知:logException(),在方法出现异常后运行, @AfterThrowing
* 环绕通知:动态代理,手动执行joinPoint.porceed()(其实就是执行目标方法), @Around

pointCut()是切点表达式方法,表明要针对哪些方法进行切面,即方法执行前后输出日志

代码语言:javascript
复制
* 切入点表达式,针对具体方法的
* public String com.citi.controller.HelloController.hello(int x, int y)
* 针对具体方法(重载方法)的,包含不同的入参,用..表示不同数量的入参
* public String com.citi.controller.HelloController.hello(..)
* 针对某个类下的所有函数,用*代替String
* public * com.citi.controller.HelloController.*.(..)
* 针对某个包下的所有函数的所有方法,每个方法的返回值不同,
* public * com.citi.controller.*.*(..)

执行项目的Main方法,项目默认在8080端口,浏览器打开localhost:8080/hello,浏览器输出Hello AOP

image.png
image.png

查看控制台打印,方法执行前后都会有日志输出

image.png
image.png

AOP即面向切面编程,底层就是动态代理,指程序在运行期间动态的将某段代码切入到指定方法位置进行运行的编程方式

模拟异常情况 修改HelloController,增加异常3/0

代码语言:javascript
复制
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        int result = 3 / 0;
        return "Hello AOP";
    }
}

重启Main方法,打开浏览器输入localhost:8080/hello,查看控制台日志

image.png
image.png

输出了异常通知

如何输入切面相关的信息,即针对的方法的信息,如方法名,入参等 查看JoinPoint接口源码

image.png
image.png

可以通过getArgs,getSignature等方法获取相关信息

修改LogAspects类,增加JoinPoint入参,以logBefore()为例

代码语言:javascript
复制
@Before("pointCut()")
public void logStart(JoinPoint joinPoint ){
    System.out.println("方法名:" + joinPoint.getSignature());
    System.out.println("方法参数" + joinPoint.getArgs());
    System.out.println("方法调用前的输出");
}

重启应用,打开浏览器输入localhost:8080/hello,查看控制台日志

image.png
image.png

输出了切面的相关信息

Section 02 - AOP 源码

SpringBoot默认开启了AOP配置,所以并不需要在BeanConfig配置类上添加@EnableAspectJAutoProxy,但它是开启AOP的核心,查看该注解源码发现使用@Import注解导入了AspectJAutoProxyRegistrar

代码语言:javascript
复制
@Import(AspectJAutoProxyRegistrar.class)

查看AspectJAutoProxyRegistrar源码,该类实现了ImportBeanDefinitionRegistrar接口及registerBeanDefinitions方法,该接口的作用是给容器中自定义注册组件,在IoC容器的测试代码中的CustImportBeanDefinitionRegistrar就是实现了该类,并实现了根据条件往容器中注入Bean的功能,如果容器中存在Product和Category才往容器中注入Order。在registerBeanDefinitions打断点Debug

image.png
image.png

可以看出一开始registry中有10个Bean,经过下面的代码之后

代码语言:javascript
复制
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

registry中多了一个Bean

image.png
image.png

Bean Name为"org.springframework.aop.config.internalAutoProxyCreator" 类型为AnnotationAwareAspectJAutoProxyCreator即注解装配模式的Aspect切面自定代理创建器。

上述源码总结起来其实就是通过@EnableAspectProxy中的AspectAutoProxyRegistrar给容器中注入了一个AnnotationAwareAspectJAutoProxyCreator,这个类的作用就是创建Aspect切面代理,这也就是整个AOP的原理,查看AnnotationAwareAspectJAutoProxyCreator继承关系图

image.png
image.png

该类间接实现类BeanFactoryAware接口,也就拥有了beanFactory的功能,实现了BeanPostProcessor,也就实现了前置后置处理器的功能

AnnotationAwareAspectJAutoProxyCreator创建注册流程 在test包中新建一个测试类BeanConfigTest

代码语言:javascript
复制
@SpringBootTest
public class BeanConfigTest {

    @Test
    public void getBean(){
        ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
        String[] beanNames = context.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }
}

在new AnnotationConfigApplicationContext(BeanConfig.class)这一行打断点,启动Debug模式

1.register()传入配置类BeanConfig,准备开始创建容器

image.png
image.png

2.refresh()刷新容器,进入refresh()方法 3.registerBeanPostProcessors(),注册Bean的后置处理器拦截,处理流程为 1)获取IoC容器中所有的postProcessors

代码语言:javascript
复制
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

2)给容器中增加BeanPostProcessor

代码语言:javascript
复制
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

3)先注册实现了PriorityOrdered接口的BeanPostProcessor 再注册实现类Ordered接口的,最后注册没有实现Ordered接口的

代码语言:javascript
复制
for (String ppName : postProcessorNames) {
   if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
      BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
      priorityOrderedPostProcessors.add(pp);
      if (pp instanceof MergedBeanDefinitionPostProcessor) {
         internalPostProcessors.add(pp);
      }
   }
   else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
      orderedPostProcessorNames.add(ppName);
   }
   else {
      nonOrderedPostProcessorNames.add(ppName);
   }
}
image.png
image.png

Bean Name为org.springframework.aop.config.internalAutoProxyCreator其实就是 AnnotationAwareAspectJAutoProxyCreator

4)再进入getBean()

image.png
image.png
  1. 接下来就是创建Bean的流程了,与创建普通的业务Bean流程一致
image.png
image.png

6) Bean创建完成之后会执行

代码语言:javascript
复制
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));

将Bean注册到容器中

创建增强型业务Bean HelloController流程

在PostProcessor后置处理器创建成功之后,才会创建业务Bean, refresh() --> finishBeanFactoryInitilization()完成BeanFactory的初始化工作,然后开始创建Bean

创建业务Bean流程中的createBean()方法这里返回一个增强代理Bean,创建一个代理对象

image.png
image.png

进入resolveBeforeInstantiation,这里判断是否实现InstantiationAwareBeanPostProcessors

image.png
image.png
image.png
image.png

进入 postProcessBeforeInstantiation,开始创建代理对象

image.png
image.png

createProxy() --> proxyFactory.getProxy(getProxyClassLoader()) --> createAopProxy

AOP两种实现方式

image.png
image.png

接着就是执行doCreateBean() ...., 和创建单实例Bean的流程一致

InstantiationAwareBeanPostProcessor

每一个Bean创建之前都会调用postProcessBeforeInstantiation(),这个方法会判断当前Bean是否存在advisedBeans中,adivsedBeans中保存了所有需要增强的Bean,这里就跟AOP类LogAspect有关

LogAspect执行的方法

image.png
image.png
image.png
image.png

总结

  1. @EnableAspectJAutoProxy 开启AOP功能
  2. @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
  3. AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;
  4. 容器的创建流程: 4-1. registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象 4-2. finishBeanFactoryInitialization() 初始化剩下的单实例bean 4-2-1. 创建业务逻辑组件和切面组件 AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程 4-2-2. 组件创建完之后,判断组件是否需要增强 是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib);
  5. 执行目标方法: 5-1. 代理对象执行目标方法 5-2. CglibAopProxy.intercept(); 5-2-1. 得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor) 5-2-2. 利用拦截器的链式机制,依次进入每一个拦截器进行执行; 5-2-3. 效果:

正常执行:前置通知-》目标方法-》后置通知-》返回通知 出现异常:前置通知-》目标方法-》后置通知-》异常通知

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Chapter 07 Spring AOP 基操及源码
    • Section 01 - AOP基操
      • Section 02 - AOP 源码
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档