Chapter 07 Spring AOP 基操及源码
Section 01 - AOP基操
新建一个Spring Boot项目spring-aop,添加AOP 相关的依赖
<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类
@RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "Hello AOP"; } } 复制代码
新建一个config包,首先新增一个BeanConfig,将所有的Bean注册到容器中
@Configuration @ComponentScan(value = {"com.citi"}) public class BeanConfig { } 复制代码
再新建一个aop包,增加一个LogAspects类,代码如下
@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; } } 复制代码
通知方法,方法名可以自定义,注解不可以省略 * 前置通知:logStart(),注解@Before * 后置通知:logEnd(),@After * 返回通知:logReturn(),方法正常返回后运行, @AfterReturing * 异常通知:logException(),在方法出现异常后运行, @AfterThrowing * 环绕通知:动态代理,手动执行joinPoint.porceed()(其实就是执行目标方法), @Around 复制代码
pointCut()是切点表达式方法,表明要针对哪些方法进行切面,即方法执行前后输出日志
* 切入点表达式,针对具体方法的 * 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
查看控制台打印,方法执行前后都会有日志输出
AOP即面向切面编程,底层就是动态代理,指程序在运行期间动态的将某段代码切入到指定方法位置进行运行的编程方式
模拟异常情况修改HelloController,增加异常3/0
@RestController public class HelloController { @GetMapping("/hello") public String hello(){ int result = 3 / 0; return "Hello AOP"; } } 复制代码
重启Main方法,打开浏览器输入localhost:8080/hello,查看控制台日志
输出了异常通知
如何输入切面相关的信息,即针对的方法的信息,如方法名,入参等 查看JoinPoint接口源码
可以通过getArgs,getSignature等方法获取相关信息
修改LogAspects类,增加JoinPoint入参,以logBefore()为例
@Before("pointCut()") public void logStart(JoinPoint joinPoint ){ System.out.println("方法名:" + joinPoint.getSignature()); System.out.println("方法参数" + joinPoint.getArgs()); System.out.println("方法调用前的输出"); } 复制代码
重启应用,打开浏览器输入localhost:8080/hello,查看控制台日志
输出了切面的相关信息
Section 02 - AOP 源码
SpringBoot默认开启了AOP配置,所以并不需要在BeanConfig配置类上添加@EnableAspectJAutoProxy,但它是开启AOP的核心,查看该注解源码发现使用@Import注解导入了AspectJAutoProxyRegistrar
@Import(AspectJAutoProxyRegistrar.class) 复制代码
查看AspectJAutoProxyRegistrar源码,该类实现了ImportBeanDefinitionRegistrar接口及registerBeanDefinitions方法,该接口的作用是给容器中自定义注册组件,在IoC容器的测试代码中的CustImportBeanDefinitionRegistrar就是实现了该类,并实现了根据条件往容器中注入Bean的功能,如果容器中存在Product和Category才往容器中注入Order。在registerBeanDefinitions打断点Debug
可以看出一开始registry中有10个Bean,经过下面的代码之后
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); 复制代码
registry中多了一个Bean
Bean Name为"org.springframework.aop.config.internalAutoProxyCreator" 类型为AnnotationAwareAspectJAutoProxyCreator即注解装配模式的Aspect切面自定代理创建器。