AOP:面向切面编程【底层使用动态代理实现】,就是在运行期间动态的将某段代码切入到方法的指定位置进行运行的编程方式
基本使用
- 使用AOP功能需要引入spring的aop以及aspects相关包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.6.RELEASE</version> </dependency>
- 定义逻辑类
public class TestService { public int service(int i, int j){ System.out.println(".........service........"); return i/j; } }
- 定义切面类
@Aspect//声明该类是切面类 public class MyAspectJ { /** * @Before * 前置通知:在目标方法(service)运行之前运行 */ @Before("execution(public int com.enjoy.study.cap13.TestService.service(int,int))") public void logStart(){ System.out.println("@Before目标方法执行前"); } /** * @After * 后置通知:在目标方法(service)运行结束之后运行,无论正常或异常结束 */ @After("execution(public int com.enjoy.study.cap13.TestService.service(int,int))") public void logEnd(){ System.out.println("@After目标方法执行后"); } /** * @AfterReturning * 返回通知:在目标方法(service)正常返回之后运行 */ @AfterReturning("execution(public int com.enjoy.study.cap13.TestService.service(int,int))") public void logReturn() { System.out.println("@AfterReturning目标方法执行后返回"); } /** * @AfterThrowing * 异常通知:在目标方法(service)出现异常后运行 */ @AfterThrowing("execution(public int com.enjoy.study.cap13.TestService.service(int,int))") public void logThrowing(){ System.out.println("@AfterThrowing目标方法执行出现异常"); } /** * @Around * 环绕通知:动态代理,手动执行目标方法运行joinPoint.procced(),最底层通知,手动指定执行目标方法, * 执行之前相当于前置通知, 执行之后相当于返回通知,其实就是通过反射执行目标对象的连接点处的方法 * @param joinPoint : 用于环绕通知,通过procced()来执行目标方法 * @return * @throws Throwable */ @Around("execution(public int com.enjoy.study.cap13.TestService.service(int,int))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("@Around目标方法执行前..........."); Object object = joinPoint.proceed(); System.out.println("@Around目标方法执行后..........."); return object; } }
- 配置类
@Configuration @EnableAspectJAutoProxy//用于开启spring注解的AOP模式,使得ioc容器可以辨别Aspect类 public class AopMainConfig { @Bean public MyAspectJ myAspectJ(){ return new MyAspectJ(); } @Bean public TestService testService(){ return new TestService(); } }
-
测试
public class TestAspectJ { @Test public void testM(){ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopMainConfig.class); TestService service = context.getBean(TestService.class); int result = service.service(4, 2); System.out.println("result = " + result); } }
- 结果
@Around目标方法执行前........... @Before目标方法执行前 .........service........ @Around目标方法执行后........... @After目标方法执行后 @AfterReturning目标方法执行后返回 result = 2
知识点
- execution:用于匹配方法执行的连接点
- ProceedingJoinPoint:应用于环绕通知,使用proceed()执行目标方法
- @Aspect:声明当前类是切面类
- @EnableAspectJAutoProxy:在配置类上使用该注解开启Spring基于注解的AOP模式,使得Spring的IOC容器可以识别哪个bean是Aspect切面bean
优化
对于上面的切面类,每个通知的execution都一样,是冗余的,导致重复写了多次,可以抽取公共切入方法
@Pointcut("execution(public int com.enjoy.study.cap13.TestService.service(int,int))")
public void pointCut(){}
本类可以直接引用,其他类可以通过路径+方法引用
@Aspect
public class MyAspectJ {
@Pointcut("execution(public int com.enjoy.study.cap13.TestService.service(int,int))")
public void pointCut(){}
/**
* @Before
* 前置通知:在目标方法(service)运行之前运行
*/
@Before("pointCut()")
public void logStart(){
System.out.println("@Before目标方法执行前");
}
/**
* @After
* 后置通知:在目标方法(service)运行结束之后运行,无论正常或异常结束
*/
@After("com.enjoy.study.cap13.MyAspectJ.pointCut()")
public void logEnd(){
System.out.println("@After目标方法执行后");
}
/**
* @AfterReturning
* 返回通知:在目标方法(service)正常返回之后运行
*/
@AfterReturning("pointCut()")
public void logReturn() {
System.out.println("@AfterReturning目标方法执行后返回");
}
/**
* @AfterThrowing
* 异常通知:在目标方法(service)出现异常后运行
*/
@AfterThrowing("pointCut()")
public void logThrowing(){
System.out.println("@AfterThrowing目标方法执行出现异常");
}
/**
* @Around
* 环绕通知:动态代理,手动执行目标方法运行joinPoint.procced(),最底层通知,手动指定执行目标方法,
* 执行之前相当于前置通知, 执行之后相当于返回通知,其实就是通过反射执行目标对象的连接点处的方法
* @param joinPoint : 用于环绕通知,通过procced()来执行目标方法
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around目标方法执行前...........");
Object object = joinPoint.proceed();
System.out.println("@Around目标方法执行后...........");
return object;
}
}
我们还可以在切面类中得到方法名、方法的参数、结果、异常等信息
@Aspect
public class MyAspectJ {
@Pointcut("execution(public int com.enjoy.study.cap13.TestService.service(int,int))")
public void pointCut(){}
/**
* @Before
* 前置通知:在目标方法(service)运行之前运行
* @param joinPoint:应用于每个通知
*/
@Before(value = "pointCut()")
public void logStart(JoinPoint joinPoint){
System.out.println("@Before目标方法执行前,方法名:"+joinPoint.getSignature().getName()+
"方法参数:"+ Arrays.asList(joinPoint.getArgs()));
}
/**
* @After
* 后置通知:在目标方法(service)运行结束之后运行,无论正常或异常结束
*/
@After("com.enjoy.study.cap13.MyAspectJ.pointCut()")
public void logEnd(){
System.out.println("@After目标方法执行后");
}
/**
* @AfterReturning
* 返回通知:在目标方法(service)正常返回之后运行
*/
@AfterReturning(value = "pointCut()",returning = "result")
public void logReturn(Object result) {
System.out.println("@AfterReturning目标方法执行后返回结果:"+result);
}
/**
* @AfterThrowing
* 异常通知:在目标方法(service)出现异常后运行
*/
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void logThrowing(Exception ex){
System.out.println("@AfterThrowing目标方法执行出现异常信息:"+ex);
}
/**
* @Around
* 环绕通知:动态代理,手动执行目标方法运行joinPoint.procced(),最底层通知,手动指定执行目标方法,
* 执行之前相当于前置通知, 执行之后相当于返回通知,其实就是通过反射执行目标对象的连接点处的方法
* @param joinPoint : 用于环绕通知,通过procced()来执行目标方法
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("@Around目标方法执行前...........");
Object object = joinPoint.proceed();
System.out.println("@Around目标方法执行后...........");
return object;
}
}
结果
@Around目标方法执行前...........
@Before目标方法执行前,方法名:service方法参数:[4, 2]
.........service........
@Around目标方法执行后...........
@After目标方法执行后
@AfterReturning目标方法执行后返回结果:2
result = 2
如果有异常会打印异常信息,例如将i/j中的j设置为0
@AfterThrowing目标方法执行出现异常信息:java.lang.ArithmeticException: / by zero