Springboot中aop的具体实现与自定义注解的使用

本文不介绍aop的相关理论和概念,仅记录spring aop的具体实现,顺便使用一下自定义注解。

1.使用aop完成日志功能示例

1-1.创建一个springboot空项目,添加依赖:
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1-2.创建测试方法
1
2
3
4
5
6
7
8
@RestController
public class AopTestController {
@RequestMapping("test")
public String test(){
System.out.println("处理业务");
return "完成";
}
}
1-3.创建切面类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Aspect
@Component
public class LogAspect {

@Pointcut("execution(public * com.lizxing.aop.demo.controller.*.*(..))")
public void writingLog(){}

/**
* 前置通知
* @param joinPoint
* @throws Throwable
*/
@Before("writingLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable{
System.out.println("被代理方法:" + joinPoint.getSignature().getName());
System.out.println("前置通知,连接点执行前");
}

/**
* 后置通知
* @param joinPoint
*/
@After("writingLog()")
public void doAfter(JoinPoint joinPoint){
System.out.println("后置通知,连接点执行后");
}

/**
* 返回通知
* @param joinPoint
*/
@AfterReturning("writingLog()")
public void doAfterReturning(JoinPoint joinPoint){
System.out.println("返回通知,方法退出后执行");
}

/**
* 异常通知
* @param joinPoint
*/
@AfterThrowing("writingLog()")
public void doAfterThrowing(JoinPoint joinPoint){
System.out.println("异常通知,方法异常后执行");
}

// /**
// * 环绕方法,相当于把整个被代理的目标方法包装起来
// * @param proceedingJoinPoint
// * @throws Throwable
// */
// @Around("writingLog()")
// public void doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
// try{
// try{
// System.out.println("连接点执行前(@Before)");
// proceedingJoinPoint.proceed();
// }finally {
// System.out.println("连接点执行后(@After)");
// }
// System.out.println("方法返回后(@AfterReturning)");
// }catch (Throwable e){
// System.out.println("方法执行异常(@AfterThrowing)");
// }
// }
}

要注意各种通知的执行时机,先把环绕通知注释掉,访问测试方法 http://localhost:8080/test 查看执行效果:

环绕通知的话,比较特殊,这个通知能把整个目标方法包装,集中处理目标方法执行前后等各种时机,注释掉上面的通知,单独开启环绕通知效果:

2.自定义注解

2-1.使用自定义注解

自定义注解在一定的场景有重要的作用,比如登录拦截。这里演示一下自定义注解的基本使用方法。

新建注解:

1
2
3
4
5
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {

}

新建拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class SourceAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器");

// 反射方式获取注解
HandlerMethod handlerMethod = (HandlerMethod)handler;
LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);

if (loginRequired == null){
return true;
} else {
response.setContentType("application/json;charset=utf-8");
response.getWriter().println("访问此资源需要登录");
return false;
}
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
}

拦截器注册:

1
2
3
4
5
6
7
8
@Configuration
public class InterceptorConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SourceAccessInterceptor())
.addPathPatterns("/**");
}
}

测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class AnnotationTestController {
@RequestMapping("sourceA")
public String sourceA(){
return "访问资源A";
}

@LoginRequired
@RequestMapping("sourceB")
public String sourceB(){
return "访问资源B";
}
}

运行效果:

2-2.在aop例子中使用自定义注解

对于有些切面,并不需要在整个controller里面的所有方法里使用,可以在Pointcut里的execution函数里指定,也可以用自定义注解,更加方便。

新建一个自定义注解类:

1
2
3
4
5
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WritingLog {
String desc() default "无";
}

修改切面类的Pointcut:

1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class LogAspect {

// @Pointcut("execution(public * com.lizxing.aop.demo.controller.*.*(..))")
@Pointcut(value = "@annotation(com.lizxing.aop.demo.annotation.WritingLog)")
public void writingLog(){}

......

测试方法加上注解:

1
2
3
4
5
6
7
8
9
@RestController
public class AopTestController {
@WritingLog
@RequestMapping("test")
public String test(){
System.out.println("处理业务");
return "完成";
}
}

运行后测试结果与1例中一致。

本次示例demo源码地址 https://github.com/lizxing/aop-demo