AOP
面向切面编程。主要功能是对方法的加强。其实现是居于代理模式使用。
Spring事务就是居于AOP的实现。
首先了解一下相关概念
- 切面(Aspect): 通常是一个类,定义切入点和通知
- 连接点(Join point): 程序执行过程中方法的调用
- 通知(Advice):切面在特定的连接点的增强。
- 前置通知: 连接点执行之前的增强,但不能阻止连接点执行(除非抛出异常)
- 后置通知: 连接点正常执行之后的增强
- 异常通知: 连接点抛出异常的增强
- 执行通知: 连接点执行后(不论是否抛出异常)的增强
- 围绕通知: 在连接点执行前后的增强
- 切入点(Pointcut): 执行切面的匹配点。(特定名称的方法,特定的注解等)
- AOP代理(AOP proxy): AOP框架创建的对象,代理就是目标对象的加强。
- 引用(introduction): 准许目标对象引入新的接口以及相应的实现。
- 目标对象(Target object): 被加强的对象。
- 编织(Weaving):将切面与其他应用程序类型或对象链接,以创建通知的对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。
具体参考:SpringAOP
AOP使用-日志记录
- 创建表记录数据
CREATE TABLE `sys_logger` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`create_date` datetime(0) NOT NULL COMMENT '创建时间',
`modify_date` datetime(0) NOT NULL COMMENT '修改时间',
`describes` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述',
`method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '执行的方法-类全命名.方法',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '传入的参数',
`host` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主机',
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
- 创建实体类
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_logger")
public class Logger extends BaseEntity {
private static final long serialVersionUID = 1L;
private String describes;
private String method;
private String params;
private String host;
private String address;
}
- 切面类(具体业务逻辑替换为日志打印)
@Aspect
@Component
public class RecordLoggerAcpect {
private static final Logger log = LoggerFactory.getLogger(RecordLoggerAcpect.class);
/**
* @Description: 定义切入点
*/
//被注解CustomAopAnnotation表示的方法
@Pointcut("@annotation(bertram.wang.vueweb.annotation.RecordLoggerAnnotation)")
public void pointCut(){}
/**
* @Description: 定义前置通知
*/
@Before("pointCut()")
public void before(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
log.info("【注解:Before】------------------切面 before");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
log.info("【注解:Before】浏览器输入的网址=URL : " + request.getRequestURL().toString());
log.info("【注解:Before】HTTP_METHOD : " + request.getMethod());
log.info("【注解:Before】IP : " + request.getRemoteAddr());
log.info("【注解:Before】执行的业务方法名=CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("【注解:Before】业务方法获得的参数=ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
/**
* @Description: 后置返回通知
*/
@AfterReturning(returning = "ret", pointcut = "pointCut()")
public void afterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
log.info("【注解:AfterReturning】这个会在切面最后的最后打印,方法的返回值 : " + ret);
}
/**
* @Description: 后置异常通知
*/
@AfterThrowing("pointCut()")
public void afterThrowing(JoinPoint jp){
log.info("【注解:AfterThrowing】方法异常时执行.....");
}
/**
* @Description: 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
*/
@After("pointCut()")
public void after(JoinPoint jp){
log.info("【注解:After】方法最后执行.....");
}
/**
* @Description: 环绕通知,环绕增强
* @return
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
log.info("【注解:Around . 环绕前】方法环绕start.....");
try {
//如果不执行这句,会不执行切面的Before方法及controller的业务方法
Object o = pjp.proceed();
log.info("【注解:Around. 环绕后】方法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
- 自定义注解切入点
public @interface RecordLoggerAnnotation {
String value() default "";
}
- 测试控制器
@RestController
@RequestMapping("/hello")
public class HelloController {
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/testone")
@RecordLoggerAnnotation("testone")
public MyReponse<?> testone(@RequestParam Map<String, Object> params) {
log.info("params:{}", params);
return success();
}
}
- 测试单元
@Test
public void testone() throws Exception {
String requestGET = requestGET("/hello/testone?name=123");
log.info("===================rest:{}", requestGET);
}
正常执行的日志
2019-06-09 01:22:49.924 INFO 14436 --- [ main] bertram.wang.test.ApplicationTest : Started ApplicationTest in 34.477 seconds (JVM running for 35.444)
2019-06-09 01:22:50.108 DEBUG 14436 --- [ main] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@16944b587 pairs: {GET /vueweb/hello/testone?name=123 HTTP/1.1: null}{Accept: text/plain, text/plain, application/json, application/json, application/*+json, application/*+json, */*, */*}{Content-type: application/json;charset=utf-8;Accept:application/json;}{Authorization: dcf08714-7cb6-451a-8dc1-56e2192a6933}{User-Agent: Java/1.8.0_191}{Host: localhost:55305}{Connection: keep-alive}
2019-06-09 01:22:50.804 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Around . 环绕前】方法环绕start.....
2019-06-09 01:22:50.804 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】------------------切面 before
2019-06-09 01:22:50.804 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】浏览器输入的网址=URL : http://localhost:55305/vueweb/hello/testone
2019-06-09 01:22:50.805 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】HTTP_METHOD : GET
2019-06-09 01:22:50.805 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】IP : 127.0.0.1
2019-06-09 01:22:50.806 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】执行的业务方法名=CLASS_METHOD : bertram.wang.vueweb.rest.HelloController.testone
2019-06-09 01:22:50.806 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Before】业务方法获得的参数=ARGS : [{name=123}]
2019-06-09 01:22:50.809 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.rest.HelloController : params:{name=123}
2019-06-09 01:22:50.810 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:Around. 环绕后】方法环绕proceed,结果是 :MyReponse(code=0, message=成功, time=1560014570, data=null)
2019-06-09 01:22:50.810 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:After】方法最后执行.....
2019-06-09 01:22:50.811 INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect : 【注解:AfterReturning】这个会在切面最后的最后打印,方法的返回值 : MyReponse(code=0, message=成功, time=1560014570, data=null)
2019-06-09 01:22:50.846 DEBUG 14436 --- [ main] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader@1dbc607d7 pairs: {null: HTTP/1.1 200}{Access-Control-Allow-Headers: Authorization,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type}{Access-Control-Allow-Methods: OPTIONS,GET,POST,DELETE,PUT}{Access-Control-Allow-Credentials: true}{Content-Type: application/json;charset=UTF-8}{Transfer-Encoding: chunked}{Date: Sat, 08 Jun 2019 17:22:50 GMT}
2019-06-09 01:22:50.851 INFO 14436 --- [ main] bertram.wang.test.ApplicationTest : ===================rest:{"code":0,"message":"成功","time":1560014570}
从日志打印不难看出执行的顺序: 环绕前--前置--控制器(被加强的方法)--环绕后--后置(没有异常所以后置异常通知没有执行)
最后就是把日志记录的业务逻辑修改 就行了。然后控制器的方法上添加注解即可。