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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 切面注解 Aspect 使用入门
* 1、@Aspect:声明本类为切面类
* 2、@Component:将本类交由 Spring 容器管理
* 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE
*
* @author wangMaoXiong
* @version 1.0
* @date 2020/8/20 19:22
*/
@Aspect
@Order(value = 999)
@Component
public class AspectHelloWorld {
private static final Logger LOG = LoggerFactory.getLogger(AspectHelloWorld.class);
/**
* @Pointcut :切入点声明,即切入到哪些目标方法。value 属性指定切入点表达式,默认为 ""。
* 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式
* <p>
* 切入点表达式常用格式举例如下:
* - * com.wmx.aspect.EmpService.*(..)):表示 com.wmx.aspect.EmpService 类中的任意方法
* - * com.wmx.aspect.*.*(..)):表示 com.wmx.aspect 包(不含子包)下任意类中的任意方法
* - * com.wmx.aspect..*.*(..)):表示 com.wmx.aspect 包及其子包下任意类中的任意方法
* </p>
* value 的 execution 可以有多个,使用 || 隔开.
*/
@Pointcut(value =
"execution(* com.wmx.hb.controller.DeptController.*(..)) " +
"|| execution(* com.wmx.hb.controller.EmpController.*(..))")
private void aspectPointcut() {
}
/**
* 前置通知:目标方法执行之前执行以下方法体的内容。
* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* <br/>
* * @param joinPoint:提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p>
* * * Object[] getArgs():返回此连接点处(目标方法)的参数,目标方法无参数时,返回空数组
* * * Signature getSignature():返回连接点处的签名。
* * * Object getTarget():返回目标对象
* * * Object getThis():返回当前正在执行的对象
* * * StaticPart getStaticPart():返回一个封装此连接点的静态部分的对象。
* * * SourceLocation getSourceLocation():返回与连接点对应的源位置
* * * String toLongString():返回连接点的扩展字符串表示形式。
* * * String toShortString():返回连接点的缩写字符串表示形式。
* * * String getKind():返回表示连接点类型的字符串
* * * </p>
*/
@Before(value = "aspectPointcut()")
public void aspectBefore(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
Object target = joinPoint.getTarget();
Object aThis = joinPoint.getThis();
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
SourceLocation sourceLocation = joinPoint.getSourceLocation();
String longString = joinPoint.toLongString();
String shortString = joinPoint.toShortString();
LOG.debug("【前置通知】" +
"args={},signature={},target={},aThis={},staticPart={}," +
"sourceLocation={},longString={},shortString={}"
, Arrays.asList(args), signature, target, aThis, staticPart, sourceLocation, longString, shortString);
}
/**
* 后置通知:目标方法执行之后执行以下方法体的内容,不管目标方法是否发生异常。
* value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
*/
@After(value = "aspectPointcut()")
public void aspectAfter(JoinPoint joinPoint) {
LOG.debug("【后置通知】kind={}", joinPoint.getKind());
}
/**
* 返回通知:目标方法返回后执行以下代码
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* returning 属性:通知签名中要将返回值绑定到的参数的名称,默认为 ""
*
* @param joinPoint :提供对连接点处可用状态和有关它的静态信息的反射访问
* @param result :目标方法返回的值,参数名称与 returning 属性值一致。无返回值时,这里 result 会为 null.
*/
@AfterReturning(pointcut = "aspectPointcut()", returning = "result")
public void aspectAfterReturning(JoinPoint joinPoint, Object result) {
LOG.debug("【返回通知】,shortString={},result=", joinPoint.toShortString(), result);
}
/**
* 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知不会再触发
* value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 ""
* throwing 属性:与方法中的异常参数名称一致,
*
* @param ex:捕获的异常对象,名称与 throwing 属性值一致
*/
@AfterThrowing(pointcut = "aspectPointcut()", throwing = "ex")
public void aspectAfterThrowing(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
if (ex instanceof ArithmeticException) {
LOG.error("【异常通知】" + methodName + "方法算术异常(ArithmeticException):" + ex.getMessage());
} else {
LOG.error("【异常通知】" + methodName + "方法异常:" + ex.getMessage());
}
}
/**
* 环绕通知
* 1、@Around 的 value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式
* 2、Object ProceedingJoinPoint.proceed(Object[] args) 方法:继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
* 3、假如目标方法是控制层接口,则本方法的异常捕获与否都不会影响目标方法的事务回滚
* 4、假如目标方法是控制层接口,本方法 try-catch 了异常后没有继续往外抛,则全局异常处理 @RestControllerAdvice 中不会再触发
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around(value = "aspectPointcut()")
public Object handleControllerMethod(ProceedingJoinPoint joinPoint) throws Throwable {
this.checkRequestParam(joinPoint);
StopWatch stopWatch = StopWatch.createStarted();
LOG.debug("【环绕通知】执行接口开始,方法={},参数={} ", joinPoint.getSignature(), Arrays.asList(joinPoint.getArgs()).toString());
//继续下一个通知或目标方法调用,返回处理结果,如果目标方法发生异常,则 proceed 会抛异常.
//如果在调用目标方法或者下一个切面通知前抛出异常,则不会再继续往后走.
Object proceed = joinPoint.proceed(joinPoint.getArgs());
stopWatch.stop();
long watchTime = stopWatch.getTime();
LOG.debug("【环绕通知】执行接口结束,方法={}, 返回值={},耗时={} (毫秒)", joinPoint.getSignature(), proceed, watchTime);
return proceed;
}
/**
* 参数校验,防止 SQL 注入
*
* @param joinPoint
*/
private void checkRequestParam(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args == null || args.length <= 0) {
return;
}
String params = Arrays.toString(joinPoint.getArgs()).toUpperCase();
String[] keywords = {"DELETE ", "UPDATE ", "SELECT ", "INSERT ", "SET ", "SUBSTR(", "COUNT(", "DROP ",
"TRUNCATE ", "INTO ", "DECLARE ", "EXEC ", "EXECUTE ", " AND ", " OR ", "--"};
for (String keyword : keywords) {
if (params.contains(keyword)) {
LOG.warn("参数存在SQL注入风险,其中包含非法字符 {}.", keyword);
throw new RuntimeException("参数存在SQL注入风险:params=" + params);
}
}
}
}
|