利用SpringBoot中的自定义注解加上AOP就可以轻松的帮助我们实现一些特定的功能
1、创建一个自己定义的注解
1、首先需要定义一个注解的interface,也就是我们自定义注解的注解名,同时也要定义里面的内部参数。
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnePrint {
}
在SpringBoot中,有四个元注解,它们被称为注解的注解,我们在自定义注解的时候,就会用到它们,下面对他们分别做个简单的介绍:
1、@Target:这个注解就是规定了我们自定义的注解所使用的的范围,里面有个属性叫ElementType,关于这个属性有着很多值:
2、@Retention:这个注解规定了我们自定义注解的生命周期,里面有个属性叫RetentionPolicy,关于这个属性有三个值:
3、@Inherited:这个注解是一个标记注解,表明被标注的类型是可以被继承的。
4、@Documented:该注解表明被注解信息是否被添加在Javadoc中。
2、使用Spring的AOP先加入Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3、AOP配置
package cn.itedus.lottery.test.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author cl
*/
@Slf4j
@Aspect
@Component
public class OnePrintJoinPoint {
/**
* 定义切点
*/
@Pointcut("@annotation(cn.itedus.lottery.test.annotation.OnePrint)")
public void annotationPointCut() {
}
/**
* 前置通知,方法调用前被调用
*
* @param joinPoint/null
*/
@Before(value = "annotationPointCut()")
public void before(JoinPoint joinPoint) {
//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
log.info("前置通知获取方法名称为:" + joinPoint.getSignature().getName());
log.info("前置通知获取的请求参数为:" + obj);
//AOP代理类的信息
joinPoint.getThis();
//代理的目标对象
joinPoint.getTarget();
//用的最多 通知的签名
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
log.info("前置通知代理的是哪一个方法" + signature.getName());
//AOP代理类的名字
log.info("前置通知AOP代理类的名字" + signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
//获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//如果要获取Session信息的话,可以这样写:
//HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//获取请求参数
Enumeration<String> enumeration = request.getParameterNames();
Map<String, String> parameterMap = new HashMap<>();
while (enumeration.hasMoreElements()) {
String parameter = enumeration.nextElement();
parameterMap.put(parameter, request.getParameter(parameter));
}
String str = String.valueOf(parameterMap);
if (obj.length > 0) {
log.info("前置通知请求从request获取的参数信息为:" + str);
}
}
/**
* 后置返回通知
* 这里需要注意的是:
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,
* 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
*
* @param joinPoint
* @param keys
*/
@AfterReturning(value = "annotationPointCut()", returning = "keys")
public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys) {
log.info("第一个后置返回获取方法名称为:" + joinPoint.getSignature().getName());
log.info("第一个后置返回获取的请求参数为:" + Arrays.toString(joinPoint.getArgs()));
log.info("第一个后置返回通知的返回值:" + keys);
}
@AfterReturning(value = "annotationPointCut()", returning = "keys", argNames = "keys")
public void doAfterReturningAdvice2(String keys) {
log.info("第二个后置返回通知的返回值:" + keys);
}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
*
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = "annotationPointCut()", throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
//目标方法名:
log.info("后置异常通知获取方法名称为:" + joinPoint.getSignature().getName());
log.info("后置异常通知获取的请求参数为:" + Arrays.toString(joinPoint.getArgs()));
if (exception instanceof NullPointerException) {
log.info("发生了空指针异常!!!!!");
}
}
/**
* 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
*
* @param joinPoint
*/
@After(value = "annotationPointCut()")
public void doAfterAdvice(JoinPoint joinPoint) {
//目标方法名:
log.info("后置最终通知获取方法名称为:" + joinPoint.getSignature().getName());
log.info("后置最终通知获取方法参数为:" + Arrays.toString(joinPoint.getArgs()));
log.info("后置最终通知执行了!!!!");
}
/**
* 环绕通知:
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = "annotationPointCut()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
log.info("环绕通知开始了!!!!");
try {
// Object target = point.getTarget(); 获取的实体类
// String methodName = point.getSignature().getName(); 获取的方法名称
// Object[] args = point.getArgs(); 获取的方法参数
log.info("环绕通知的目标方法名:" + proceedingJoinPoint.getSignature().getName());
log.info("环绕通知获取参数为:" + Arrays.toString(proceedingJoinPoint.getArgs()));
Object[] args = processInputArg(proceedingJoinPoint);
// 执行方法,这里可以操作进行限流等其他操作
Object obj = proceedingJoinPoint.proceed(args);
processOutPutObj(obj);
return obj;
} catch (Throwable throwable) {
log.info("环绕通知出现异常了!!!!");
throwable.printStackTrace();
}
log.info("环绕通知结束了!!!!");
return null;
}
/**
* 处理输入参数
*
* @param pjp 切面
*/
private Object[] processInputArg(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
for (Object arg : args) {
System.out.println("ARG原来为:" + arg);
}
args[0] = "改一下进参";
return args;
}
/**
* 处理返回对象
*
* @param obj 返回值
*/
private void processOutPutObj(Object obj) {
System.out.println("OBJ 原本为:" + obj);
Map<String, Object> map = (Map<String, Object>) obj;
map.put("newMessage", "改一下出参" + map.get("newMessage"));
}
}