一、aop介绍
AOP的原理是面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是一种动态的抽象;主要使用场景是事务管理、日志打印、监控、统一封装接口返回对象;
以Spring Boot项目为例(Maven),使用AOP需要引入jar包:
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
二、Aspect
2.1 Aspect的原理及使用场景
原理:通过定义切点,然后在代码编译阶段或者运行阶段对该切点进行逻辑增强(前置增强、后置增强、环绕增强等);
一般可适用的场景有:
日志逻辑:统一对切点方法出/入参进行打印;
异常处理逻辑:捕获切点方法的异常信息,进行异常数据封装返回;
出入参逻辑:对入参进行解密,对出参进行加密;对入参进行格式解析;
报警处理逻辑:对切点方法的特定返回结果或异常,进行拦截报警、用户触达等特定操作;
前置增强:执行指定切点方法之前,先执行增强逻辑;
后置增强:执行指定切点方法之后,再执行增强逻辑;
环绕增强:可以再执行指定切点方法之前或之后,分别执行不同的增强逻辑。
2.2 Aspect的具体代码示例及说明
@Component
@Aspect
public class DemoAspect {
private static Logger logger = (Logger) LoggerFactory.getLogger(DemoAspect.class);
/**
* 本项目中事例实现的是对请求Controller接口的调用的增强(环绕增强)
* 增加了入参拦截打印、出参拦截打印、异常的捕获处理等;
*/
/**
* 增强点/切点
*/
@Pointcut("execution(* *.*.*.controller.*.*(..))")
private void pointFun(){
logger.info("执行切入点的逻辑...");
}
/**
* 环绕增强
* @param joinPoint
*/
@Around("pointFun()")
private Object aroundAspect(ProceedingJoinPoint joinPoint){
logger.info("进入环绕通知~");
Object resultObj = null;
Object[] args = joinPoint.getArgs();
try {
logger.info("方法执行前处理,相当于Before..");
logger.info("方法入参args:" + JsonUtil.convertToString(args));
logger.info("开始执行原方法:");
resultObj = joinPoint.proceed(args);
logger.info("原方法执行完毕。");
logger.info("方法返回值resultObj:" +resultObj);
logger.info("方法执行后处理,相当于After..");
} catch (Exception e) {
logger.info("方法异常处理...");
//TODO 异常信息报送到预警(报警)平台;
} catch (Throwable e) {
logger.info("方法异常处理...");
//TODO 异常信息报送到预警(报警)平台;
} finally {
logger.info("方法最终处理。finally.");
}
return resultObj;
}
}
说明:
1、DemoAspect 类增加@Aspect注解说明,表明该类为AOP切面类;
2、DemoAspect()方法增加@Pointcut注解并引入execution表达式,表明针对controller包下的所有类和方法都需要增强;
3、该案例中使用的是环绕增强,还有其他方式的增强请参考下方的注解说明:
切面(Aspect):一般是指被@Aspect修饰的类,代表着某一具体功能的AOP逻辑。
切入点(Pointcut):选择哪些增强的方法,上述体现的是@pointcut注解和execution表达式
通知(Advice):对目标方法的增强
>>>环绕通知(@Around):内部执行连接点(方法),对其进行增强
>>>前置通知(@Before):在执行连接点前执行
>>>后置通知(@After):在执行连接点后执行
>>>返回通知(@AfterReturning):在连接点返回后执行
>>>异常通知(@AfterThrowing):在连接点爆出异常后执行
连接点(JoinPoint):就是那些被切入点选中的方法
execution表达式: 看图讲解
说明:
访问修饰符、异常类型可以省略,其余都是必填的;
方法参数,.. 代表所有参数;
类路径中,.. 代表多层路径,包括当前包的类和子包的类;
2.3 代码测试
@RestController
@RequestMapping("/demoController")
public class DemoForAspectController {
/**
* 没有返回值 void
*/
@RequestMapping("/getVoid")
public void getVoid(){
System.out.println("getVoid");
}
/**
* 返回bean对象
* @return
*/
@RequestMapping("/getObj")
public DemoBean getObj(){
System.out.println("getObj");
DemoBean bean = new DemoBean();
bean.setId(1);
bean.setName("虎子");
bean.setBirthday(new Date());
return bean;
}
/**
* 返回bean对象
* @return
*/
@RequestMapping("/getObjByParam")
public DemoBean getObjByParam(String name){
System.out.println("getObjByParam");
DemoBean bean = new DemoBean();
bean.setId(2);
bean.setName(name);
bean.setBirthday(new Date());
return bean;
}
/**
* 返回Result对象,不用再ResponseAdvice转换
* @return
*/
@RequestMapping("/getResult")
public Result getResult(){
System.out.println("getResult");
return Result.failed();
}
/**
* 抛异常的方法--> DemoAspect可以触发到try..catch的异常捕获里面;
* @return
*/
@RequestMapping("/getException")
public String getException(){
if(true){
throw new RuntimeException("这是错误描述");
}
return "返回数据";
}
}
使用Postman测试工具请求:http://localhost:8080/projectName/demoController/getObjByParam?name=小虎子
日志打印如下:
2023-07-12 10:42:02.037 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 进入环绕通知~
2023-07-12 10:42:02.037 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法执行前处理,相当于Before..
2023-07-12 10:42:02.056 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法入参args:["小虎子"]
2023-07-12 10:42:02.056 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 开始执行原方法:
getObjByParam
2023-07-12 10:42:02.060 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 原方法执行完毕。
2023-07-12 10:42:02.090 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法返回值resultObj:{"id":1,"name":"小虎子","birthday":1689129722060}
2023-07-12 10:42:02.090 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法执行后处理,相当于After..
2023-07-12 10:42:02.090 INFO 48260 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法最终处理。finally.
使用Postman测试工具请求:http://localhost:8080/projectName/demoController/getException
日志打印如下:
2023-07-12 10:34:46.355 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 进入环绕通知~
2023-07-12 10:34:46.355 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法执行前处理,相当于Before..
2023-07-12 10:34:46.356 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法入参args:[]
2023-07-12 10:34:46.356 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 开始执行原方法:
2023-07-12 10:34:46.356 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法异常处理...
2023-07-12 10:34:46.356 INFO 40484 --- [nio-8080-exec-1] c.e.w.service.aop.aspect.DemoAspect : 方法最终处理。finally.