学习

https://www.yiibai.com/spring_aop/springaop_around_aspect.html

https://www.cnblogs.com/leeSmall/p/7667040.html

https://mrbird.cc/深入理解Spring-AOP原理.html

说明

实现 AOP 关键特点是定义好两个角色 切点 和 切面

execution切点函数

语法:execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选))

参数部分允许使用通配符:

1
2
3
4
5
*  匹配任意字符,但只能匹配一个元素

.. 匹配任意字符,可以匹配任意多个元素,表示类时,必须和*联合使用

+ 必须跟在类名后面,如Horseman+,表示类本身和继承或扩展指定类的所有类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//表示匹配所有方法  
1)execution(* *(..))
//表示匹配com.fsx.run.UserService中所有的公有方法
2)execution(public * com.fsx.run.UserService.*(..))
//表示匹配com.fsx.run包及其子包下的所有方法
3)execution(* com.fsx.run..*.*(..))

// Pointcut定义时,还可以使用&&、||、! 这三个运算。进行逻辑运算
// 签名:消息发送切面
@Pointcut("execution(* com.fsx.run.MessageSender.*(..))")
private void logSender(){}
// 签名:消息接收切面
@Pointcut("execution(* com.fsx.run.MessageReceiver.*(..))")
private void logReceiver(){}
// 只有满足发送 或者 接收 这个切面都会切进去
@Pointcut("logSender() || logReceiver()")
private void logMessage(){}

通知

@Before:前置增强方法

@After:final 增强,不管是抛出异常或者正常退出都会执行

@Around:环绕增强,相当于MethodInterceptor。可以实现@Before和@After的功能

@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行

@AfterThrowing:异常抛出增强,相当于ThrowsAdvice

Java 注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @desc 定义一个不重复提交的注解
* @author x了个w
* @create 2020年07月02日15:55:07
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {

/**
* 防止重复参数默认值
* @return
*/
String value() default "";

/**
* 过期时间
* @return
*/
int milliseconds() default 1;
}

方式一:XML 配置方式

注意:aop:config 一定要放到定义各个 bean 对象之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 其它 bean 创建...

// 切面 bean 创建
<bean id="logHandler" class="com.xrq.aop.LogHandler" />
<bean id="repeatSubmitAspect" class="com.bjtcrj.scm.common.utils.RepeatSubmitAspect" />

// aop 配置:切面、切入点、通知
<aop:config>
<aop:aspect id="log" ref="logHandler" order="1">
<aop:pointcut id="printLog" expression="execution(* com.xrq.aop.HelloWorld.do*(..))" />
<aop:before method="LogBefore" pointcut-ref="printLog" />
<aop:after method="LogAfter" pointcut-ref="printLog" />
</aop:aspect>

<aop:aspect id="repeatSubmitAspect" ref="repeatSubmitAspect" order="2">
<aop:pointcut id="noRepeatSubmit" expression="@annotation(com.bjtcrj.scm.common.utils.NoRepeatSubmit)" />
<aop:around method="noRepeatCheck" pointcut-ref="noRepeatSubmit" />
</aop:aspect>
</aop:config>

方式二:注解方式

参考

https://blog.csdn.net/autfish/article/details/51184405

切面及切点配置

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@Aspect
@Component
@Log4j2
public class RepeatSubmitAspect {

@Autowired
private RedisUtil redisUtil;

@Pointcut("@annotation(com.bjtcrj.scm.common.utils.NoRepeatSubmit)")
// @Pointcut("execution(* com.bjtcrj.scm.event.controller.EventAddController.*_App(..))")
public void pointCut() {
}

@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
Object result = null;
StringBuilder sb = new StringBuilder();


// 拦截的实体类
Object target = pjp.getTarget();
//获取类名
String className = target.getClass().getName();

sb.append(className);


MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();

//获得注解
NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);


// 拦截的方法名称。当前正在执行的方法
String methodName = method.getName();

sb.append(methodName);

// 拦截的方法参数
Object[] args = pjp.getArgs();


for (Object arg : args) {
if (arg == null) {
continue;
}
int code = arg.hashCode();
sb.append("#");
sb.append(code);
}


String value = noRepeatSubmit.value();
if (StringUtils.isEmpty(value)) {
value = sb.toString();
}

//获取过期时间
int milliseconds = noRepeatSubmit.milliseconds();
try {

long count = redisUtil.incr(value, 1);

redisUtil.expire(value, milliseconds);

if (count > 1) {
System.out.println(count);
//重复提交
CommonDto dto = new CommonDto();
dto.setCode(500);
dto.setMsg("您的操作过快,请刷新重试");
return dto;
}

} catch (Exception e) {
redisUtil.del(value);
log.error("redis加锁异常", e);
throw new BusinessException(e.getMessage());
}

try {
//执行原来方法
result = pjp.proceed();
} catch (Throwable e) {
log.error("分布式防重复操作异常Throwable::" + e.getMessage());
e.printStackTrace();
throw new BusinessException(e.getMessage());
}
return result;
}

/**
* 获取入参数据
* @param joinPoint
* @return
*/
private String preHandle(ProceedingJoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String reqParam = "";
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
Annotation[] annotations = targetMethod.getAnnotations();
for (Annotation annotation : annotations) {
//此处可以改成自定义的注解
if (annotation.annotationType().equals(RequestMapping.class)) {
reqParam = JSON.toJSONString(request.getParameterMap());
break;
}
}
return reqParam;
}
}

自动扫描和 aop:aspectj 自动代理配置

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

<!--开启Spring注解扫描器 -->
<context:component-scan base-package="com.study.spring.springtest" />
<!-- 在配置IOC的基础上增加了aop:aspectj-autoproxy节点,Spring框架会自动为与AspectJ切面配置的Bean创建代理,
proxy-target-class="true"属性表示被代理的目标对象是一个类,
而非实现了接口的类,反之proxy-target-class="false" 主要是为了选择不同的代理方式。-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>

使用

1
2
3
4
@NoRepeatSubmit
public void XXX(HttpServletRequest request, HttpServletResponse response, EventDto eventDto) {
//业务代码
}