Spring AOP 编程
# aop的注解
@Before @After @AfterReturning @AfterThrowing @Around @Pointcut
AOP就是一种更高级的动态代理的使用; Aspect Oritention Programming(面向切面编程) 切入点:要加入业务逻辑的点(在哪些类的哪些方法上面) 通知:通知包含两个方面,1,代表方法的执行时间,2,在这个时间上面要做什么事情; 切面:一个切入点+一个通知=一个切面(在什么地方,在什么时候,做什么事情); 织入:把一个切面应用到真实对象上面的过程,就叫做织入;
# SpringAOP的执行流程:
1,解析xml;
2,实例化所有的bean;
3,解析aop:config;
1,解析aop:aspect,得到aspect引用的对象;txManager
2,解析aop:aspect里面的每一个切面;
1,得到该aspect对应的pointcut-ref;
2,得到pointcut-ref对应的pointcut的表达式;
3,使用表达式中用于匹配类型的表达式;
4,使用该表达式去和spring里面配置的所有的bean的类型进行匹配;
1,如果匹配不上,不管;
2,如果匹配上了,该对象作为spring动态代理的目标对象;
1,如果该对象实现了接口,使用JDK的动态代理包装;
2,如果该对象没有实现接口,使用cglib包装;
3,得到配置的拦截时机+逻辑提供类(txManager)的对应方法(从method解析)+pointcut表达式中方法的匹配模式创建一个拦截器
4,在把该拦截器使用对应的动态代理机制代理成代理对象;
5,替换spring容器中的对应bean的实例;
# AOP执行顺序
指定order,order越小越是最先执行
spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfter、doAfterReturn方法。也就是说对多个AOP来说,先before的,一定后after。
如果我们要在同一个方法事务提交后执行自己的AOP,那么把事务的AOP order设置为2,自己的AOP order设置为1,然后在doAfterReturn里边处理自己的业务逻辑。
@Around相当于一个蛋,里面先@Before,再执行切入点代码,@AfterReturning返回结果 @After 相当于try-catch-finally里面的finally,最后执行
参考文章:
Spring中AOP的执行流程_ming13849012515的博客-CSDN博客_aop执行流程 (opens new window)
# 与SpringBoot整合
# 引入starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2
3
4
# starter分析
AopAutoConfiguration: 配置了AspectJAutoProxyingConfiguration,默认使用了ClassProxyingConfiguration(引入starter就用这个做动态代理,不然就是cglib)、CglibAutoProxyConfiguration动态代理,设置spring.aop.proxy-target-class=false,可以使用jdk动态代理(这种方式原类必须要定义接口)
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {}
2
3
# 开启aop注解
@EnableAspectJAutoProxy(exposeProxy = true) // 开始aop注解,暴露动态代理
# 写代码
@Aspect
@Component
@Slf4j
public class CalcAop {
@Pointcut("execution(public * com.zxp.springboot.service..CalServiceImpl.*(..) )")
public void pointcut(){
}
@Before(value = "pointcut()")
public void before(){
log.info(" before ...");
}
@After(value = "pointcut()")
public void after(){
log.info(" After ...");
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint pjp){
log.info(" Around前 ...");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
log.info(" Around后 ...");
return result;
}
@AfterReturning(value = "pointcut()")
public void afterReturning(){
log.info(" AfterReturning ...");
}
@AfterThrowing(value = "pointcut()")
public void afterThrowing(){
log.info(" AfterThrowing ...");
}
}
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
@Override
public int div(int x, int y) {
int result = x / y;
Object o = AopContext.currentProxy(); // 获取动态代理对象
log.info("动态代理对象:{} 执行结果:{}",o.getClass(), result);
return result;
}
2
3
4
5
6
7
# 应用场景
1、日志记录
2、权限控制
3、统一参数校验
4、异常拦截,一般用@ControllerAdvice
/**
* 日志记录
*
* @author fengshuonan
* @date 2016年12月6日 下午8:48:30
*/
@Aspect
@Component
public class LogAop {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut(value = "@annotation(cn.stylefeng.guns.base.log.BussinessLog)")
public void cutService() {
}
@Around("cutService()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
//先执行业务
Object result = point.proceed();
try {
handle(point);
} catch (Exception e) {
log.error("日志记录出错!", e);
}
return result;
}
private void handle(ProceedingJoinPoint point) throws Exception {
//获取拦截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getName();
//如果当前用户未登录,不做日志
LoginUser user = LoginContextHolder.getContext().getUser();
if (null == user) {
return;
}
//获取拦截方法的参数
String className = point.getTarget().getClass().getName();
Object[] params = point.getArgs();
//获取操作名称
BussinessLog annotation = currentMethod.getAnnotation(BussinessLog.class);
String bussinessName = annotation.value();
String key = annotation.key();
Class dictClass = annotation.dict();
StringBuilder sb = new StringBuilder();
for (Object param : params) {
sb.append(param);
sb.append(" & ");
}
//如果涉及到修改,比对变化
String msg;
if (bussinessName.contains("修改") || bussinessName.contains("编辑")) {
Object obj1 = LogObjectHolder.me().get();
Map<String, String> obj2 = HttpContext.getRequestParameters();
msg = Contrast.contrastObj(dictClass, key, obj1, obj2);
} else {
Map<String, String> parameters = HttpContext.getRequestParameters();
AbstractDictMap dictMap = (AbstractDictMap) dictClass.newInstance();
msg = Contrast.parseMutiKey(dictMap, key, parameters);
}
LogManager.me().executeLog(LogTaskFactory.bussinessLog(user.getId(), bussinessName, className, methodName, msg));
}
}
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
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 权限检查的aop
*
* @author fengshuonan
* @date 2017-07-13 21:05
*/
@Aspect
@Order(200)
public class PermissionAop {
@Autowired
private AuthService authService;
@Pointcut(value = "@annotation(cn.stylefeng.guns.base.auth.annotion.Permission)")
private void cutPermission() {
}
@Around("cutPermission()")
public Object doPermission(ProceedingJoinPoint point) throws Throwable {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
Permission permission = method.getAnnotation(Permission.class);
String[] permissions = permission.value();
if (permissions.length == 0) {
//检查全体角色
boolean result = authService.checkAll();
if (result) {
return point.proceed();
} else {
throw new PermissionException();
}
} else {
//检查指定角色
boolean result = authService.check(permissions);
if (result) {
return point.proceed();
} else {
throw new PermissionException();
}
}
}
}
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
@Aspect
@Order(510)
public class ParamValidateAop {
public ParamValidateAop() {
}
@Pointcut("@annotation(cn.stylefeng.roses.kernel.validator.stereotype.ParamValidator)")
private void cutService() {
}
@Around("cutService()")
public Object doInvoke(ProceedingJoinPoint point) throws Throwable {
Object[] methodParams = point.getArgs();
if (methodParams != null && methodParams.length > 0) {
CheckUtil.validateParameters(methodParams);
return point.proceed();
} else {
return point.proceed();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 参数校验器,静态方法调用
* <p>
* 手动验证带有校验注解的参数是否合法
*
* @author fengshuonan
* @date 2020/10/31 14:13
*/
public class ValidatorUtil {
/**
* 验证器实例
*/
private static final Validator VALIDATOR_INSTANCE = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验参数是否合法,返回校验的结果
*
* @param object 被校验的包装类参数
* @param groups 校验组
* @return 参数校验的结果,为ConstraintViolation的集合
* @author fengshuonan
* @date 2020/10/31 14:13
*/
public static Set<ConstraintViolation<Object>> validate(Object object, Class<?>... groups) {
return VALIDATOR_INSTANCE.validate(object, groups);
}
/**
* 校验参数是否合法,直接返回成功和失败
*
* @param object 被校验的包装类参数
* @param groups 校验组
* @return true-参数合法,false-参数不合法
* @author fengshuonan
* @date 2020/10/31 14:13
*/
public static boolean simpleValidate(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = VALIDATOR_INSTANCE.validate(object, groups);
return constraintViolations.isEmpty();
}
/**
* 校验参数是否合法,不返回结果,有问题直接抛出异常
*
* @param object 被校验的包装类参数
* @param groups 校验组
* @author fengshuonan
* @date 2020/10/31 14:13
*/
public static void validateThrowMessage(Object object, Class<?>... groups) {
String errorMessage = validateGetMessage(object, groups);
if (errorMessage != null) {
throw new ParamValidateException(ValidatorExceptionEnum.VALIDATED_RESULT_ERROR, errorMessage);
}
}
/**
* 校验参数是否合法
* <p>
* 不合法会返回不合法的提示,合法的话会返回null
*
* @param object 被校验的包装类参数
* @param groups 校验组
* @author fengshuonan
* @date 2020/10/31 14:13
*/
public static String validateGetMessage(Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = VALIDATOR_INSTANCE.validate(object, groups);
if (!constraintViolations.isEmpty()) {
StringBuilder errorMessage = new StringBuilder();
for (Iterator<ConstraintViolation<Object>> it = constraintViolations.iterator(); it.hasNext(); ) {
ConstraintViolation<Object> violation = it.next();
errorMessage.append(violation.getMessage());
if (it.hasNext()) {
errorMessage.append(", ");
}
}
return StrUtil.format(ValidatorExceptionEnum.VALIDATED_RESULT_ERROR.getUserTip(), errorMessage);
}
return null;
}
}
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 注意事项
切面里面如果报异常,则不会往下执行
aop有顺序之分,可以用
@Order(-100)
定义顺序,越小越靠前执行