SpringBoot02:Spring Boot中的AOP

本文介绍SpringBoot中AOP如何使用,包括拦截器和自定义注解。

1. 源码

地址:https://github.com/tyrival/SpringBoot-Dubbo-Sample

2. 拦截器Interceptor

拦截器建立在controller模块中,此处以Token拦截器为例,讲解拦截器的配置方式,在调用接口时,拦截器比后面所说的AOP先发生作用。

2.1 依赖包

拦截器依赖于spring-boot-starter-web,由于项目的根pom.xml已经引入,而controller的pom.xml继承自根pom.xml,所以无需重复引入

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- spring boot2.0的log4j依赖不全,排除,否则会报错 -->
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>

2.2 TokenInterceptor

需要注意的是,此处拦截器所在的包为com.tyrival.controller.interceptor,而不是com.tyrival.interceptor,后者在模块启动时会扫描不到。

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
package com.tyrival.controller.interceptor;

import com.tyrival.entity.user.User;
import com.tyrival.enums.base.RequestAttrEnum;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TokenInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

// 接受跨域访问
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, Authorization, Origin, Content-Type, Accept, token, apikey");

String token = httpServletRequest.getParameter("token");
User user = new User();

// TODO 解析TOKEN,验证有效性,将得到的用户信息赋予user,并注入请求

httpServletRequest.setAttribute(RequestAttrEnum.USER.getCode(), user);

String newToken = "";

// TODO 生成新TOKEN

httpServletResponse.setHeader("token", newToken);
return true;
}

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

}

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

}
}

2.3 配置文件InterceptorConfig

拦截器配置文件所处的包与拦截器相似,必须在controller之下建文件夹,否则也会扫描不到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tyrival.controller.config;

import com.tyrival.controller.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将拦截器加入序列,并增加URI匹配
registry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**");
}
}

3. 自定义注解和AOP

此处所有的注解都建立在controller模块中,与拦截器相同的是,此处的包都需要建立在com.tyrival.controller之下,否则SpringBoot也会扫描不到。

3.1 依赖包

aop依赖于spring-boot-starter-aop,由于项目的根pom.xml已经引入,而controller的pom.xml继承自根pom.xml,所以无需重复引入

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.2 日志

3.2.1 Log注解

1
2
3
4
5
6
7
8
9
10
package com.tyrival.controller.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
String value();
}

3.2.2 AOP

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
66
67
68
69
package com.tyrival.controller.aspect;

import com.alibaba.dubbo.common.utils.StringUtils;
import com.tyrival.controller.annotation.Log;
import com.tyrival.entity.user.User;
import com.tyrival.enums.base.RequestAttrEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Aspect
@Component
public class LogAspect {

// 设置切面为user包下所有类的所有方法
@Pointcut("execution(public * com.tyrival.controller.user.*.*(..))")
public void log() {
}

@Around("log()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

// 获取请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

// 获取请求中由Token拦截器注入的用户信息
User user = (User) request.getAttribute(RequestAttrEnum.USER.getCode());
if (user == null || StringUtils.isBlank(user.getId())) {
// TODO 记录为游客
}

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

// 类名
String className = signature.getDeclaringTypeName();

// 方法名
Method method = signature.getMethod();
String methodName = method.getName();

// 参数
Object[] arguments = joinPoint.getArgs();

// 参数名称
String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();

Object result = joinPoint.proceed();

// 如存在Log注解,需要记录日志
if (method.isAnnotationPresent(Log.class)) {
Log annotation = method.getAnnotation(Log.class);
// 获取Log注解内的参数
String type = annotation.value();

// TODO 记录日志
System.out.println("记录日志......");
}
return result;
}
}

3.3 权限

3.3.1 Permission注解

1
2
3
4
5
6
7
8
9
10
package com.tyrival.controller.annotation;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permission {

}

3.3.2 AOP

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
package com.tyrival.controller.aspect;

import com.tyrival.controller.annotation.Permission;
import com.tyrival.entity.user.User;
import com.tyrival.enums.base.RequestAttrEnum;
import com.tyrival.exception.CommonException;
import com.tyrival.exception.ExceptionEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Aspect
@Component
public class PermissionAspect {

@Pointcut("execution(public * com.tyrival.controller.user.*.*(..))")
public void permission(){}

@Around("permission()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

// 获取请求
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

// 获取请求中由Token拦截器注入的用户信息
User user = (User) request.getAttribute(RequestAttrEnum.USER.getCode());

// 类名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String className = signature.getDeclaringTypeName();

// 方法名
Method method = signature.getMethod();
String methodName = method.getName();

// 包含Permission注解时,进行权限验证
if (method.isAnnotationPresent(Permission.class)
|| !hasPermission(user, className, methodName)) {
throw new CommonException(ExceptionEnum.NO_PERMISSION);
}
return joinPoint.proceed();
}

private boolean hasPermission(User user, String className, String methodName) {

// TODO 根据用户信息、类名、方法名,查询用户是否有该权限

return true;
}
}

3.4 示例

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
@RestController
public class UserControllerImpl implements UserController {

// 记录为insert类日志,@Permission表示需要验证权限
@Override
@Log("insert")
@Permission
public Result create(HttpServletRequest request, HttpServletResponse response, User user) {
user = userService.create(user);
return new Result(user);
}

// 记录为query类日志,无@Permission注解表示无需验证权限
@Override
@Log("query")
public Result list(HttpServletRequest request, HttpServletResponse response, QueryParam queryParam) {
List<User> list = userService.list(queryParam);
return new Result(list);
}

// 无需记录日志,无需验证权限
@Override
public Result listByPage(HttpServletRequest httpReq, HttpServletResponse httpRsp, QueryParam queryParam) {
PageResult result = userService.listByPage(queryParam);
return new Result(result);
}
}
分享到