AOP通俗地来讲就是指在程序运行期间动态的将某段代码切入到指定方法的指定位置进行运行的编程方式,
这次我将会来对基于注解式的SpringAOP做源码解析
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>review-java</artifactId> <groupId>review</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>review-spring</artifactId> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.5.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
本次aop要实现的目标是在方法div运行的时候将日志进行打印(在方法之前、方法运行结束、方法出现异常等运行位置进行日志打印)
package aop.annotation.service; public class MathCalculator { /** * 除法 * * @param i * @param j * @return */ public int div(int i, int j) { System.out.println("MathCalculator.div方法被调用"); return i / j; } }
切面类里面的方法需要动态感知MathCalculator.div运行到哪里,然后执行相应的通知方法
给切面类的目标方法标注何时何地运行(即以下通知注解)
LogAspects 里面有通知方法:
1)前置通知:(@Before) logStart:在目标方法div运行之前
2)后置通知:(@Aftre) logEnd:在目标方法div运行结束之后 (无论方法正常结束还是异常结束)
3)返回通知:(@AfterReturning) logReturn:在目标方法div正常返回之后运行
4)异常通知:(@AfterThrowing) logException:在目标方法div出现异常之后运行
5)环绕通知:(@Around) 动态代理,手动推进目标方法运行(joinPoint.proceed())
package aop.annotation.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import java.util.Arrays; /** * @Aspect 此注解告诉spring这是切面 */ @Aspect public class LogAspects { /** * 抽取公共的切入点表达式 * 1、如果在本类引用 直接在注解加方法名 如@Before(commonPointCut()) * 2、如果不是在本类引用 (比如其他的切面要引用这个切入点) 则要用全类名 */ @Pointcut("execution(public int aop.annotation.service.MathCalculator.div(int, int))") public void commonPointCut() { } //@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入) //@Before("public int aop.annotation.service.MathCalculator.div(int, int)") /** * @param joinPoint 可以通过此参数获取方法相关信息 如方法名和参数信息,此参数必须放在第一个参数 */ @Before("commonPointCut()") public void logStart(JoinPoint joinPoint) { System.out.println("" + joinPoint.getSignature().getName() + "除法运行,@Before,参数列表是:{" + Arrays.asList(joinPoint.getArgs()) + "}"); } @After("commonPointCut()") public void logEnd(JoinPoint joinPoint) { System.out.println("除法结束,@After"); } /** * 用returning来接收返回值 * * @param result */ @AfterReturning(value = "commonPointCut()", returning = "result") public void logReturn(Object result) { System.out.println("除法正常返回,@AfterReturning 运行结果:{" + result + "}"); } /** * 用throwing来接收异常 * * @param joinPoint 此参数必须放在第一位 * @param exception */ @AfterThrowing(value = "commonPointCut()", throwing = "exception") public void logException(JoinPoint joinPoint, Exception exception) { System.out.println("" + joinPoint.getSignature().getName() + "除法异常,@AfterException 异常信息:{" + exception.getStackTrace() + "}"); } }
告诉spring哪个是切面类,(在切面类LogAspects加一个注解@Aspect)
在配置类中加 @EnableAspectJAutoProxy 开启基于注解的aop模式
package aop.annotation.config; import aop.annotation.aop.LogAspects; import aop.annotation.service.MathCalculator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 三步: * 1)、将业务逻辑组件和切面类都加入到容器中;告诉spring哪个是切面类(@Aspect) * 2)、在切面类的每一个通知方法上标注通知注解,告诉spring何时何地运行(切入点表达式) * 3)、开启基于注解的aop模式 @EnableAspectJAutoProxy */ @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { @Bean public MathCalculator calculator() { return new MathCalculator(); } @Bean public LogAspects logAspects() { return new LogAspects(); } }
package aop.annotation.test; import aop.annotation.config.MainConfigOfAOP; import aop.annotation.service.MathCalculator; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class IOCTest_AOP { /** * AOP单元测试 */ @Test public void testAop() { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class); MathCalculator mathCalculator = annotationConfigApplicationContext.getBean(MathCalculator.class); mathCalculator.div(1, 0); annotationConfigApplicationContext.close(); } }
本次测试1除以0的异常情况:可见在div运行之前,运行之后,以及出现异常时都打印出相关的信息。
其他情况,大家可以自行测试
aop的效果就是这样,在div运行前后的各个位置进行了处理,把日志的代码与业务逻辑的代码进行解耦。
下一篇将开始进行源码解析,将首先通过源码解析注解@EnableAspectJAutoProxy的作用