SpringBoot 之AOP

aop作用

在开发中我们经常使用oop这种纵向结构来开发,但是却会出现一些横切的功能。譬如,日志记录的功能,我们需要在每个方法执行的详细信息通过日志记录,但是我们为每个方法去写日志,明显不合理。再如异常处理功能,我们需要在每个方法执行抛出的异常都专门处理都不合理。这样就需要AOP面向切面开发来处理横切问题。

aop术语

  • 通知(advice):
    通知主要是定义切面是什么以及何时使用。
    Before:在接合点之前执行通知。
    AfterReturning:在接合点执行完成之后执行通知。
    AfterThrowing:如果从接合点抛出了任何异常,都执行通知。
    After:接合点执行完成之后,无论是否抛出了异常,都执行通知。
    Around:在接合点周围执行通知,意思就是可能在接合点之前执行,也可能在接合点之后执行。
  • 连接点(join point):
    意思就是代码中的点,在这个点上开始玩切面。效果肯定是向应用程序中插入额外的逻辑。
  • 切点(point cut):
    用来选择需要执行一个或者多个连接点的表达式。
  • 切面(aspect):
    切面就是切点和通知的结合。

通知注解

注解 用途
@Pointcut 定义切入点
@Before 目标方法执行之前执行
@After 目标方法执行之后必定执行,无论是否报错
@AfterReturning 目标方法有返回值且正常返回后执行
@AfterThrowing 目标方法抛出异常后执行
@Around 可以获取到目标方法的入参和返回值

maven导包

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义切入点

利用execution表达式来给包、类、方法定义切入点。

// 解释:匹配cn.spy.service.impl.MyServiceImpl类下的所有方法
execution(* cn.spy.service.impl.MyServiceImpl.(..))
// 解释:匹配cn.spy.service.impl.MyServiceImpl类下的public void xx();方法。
execution(public void cn.spy.service.impl.MyServiceImpl.(..))
// 解释:匹配cn.spy.service.impl.MyServiceImpl类下第一个参数为String类型,无返回值的所有公共方法。
execution(public void cn.spy.service.impl.MyServiceImpl.*(String,..))

用法:

@Pointcut("execution(public String com.sunpeiyu.demoitem.controller.RedisController.testRequest())")

切入点对象JoinPoint

//获取目标方法的参数信息
Object[] obj = joinPoint.getArgs();
//AOP代理类的信息
joinPoint.getThis();
//代理的目标对象
joinPoint.getTarget();
//用的最多 通知的签名
Signature signature = joinPoint.getSignature();
//代理的是哪一个方法
System.out.println("代理的是哪一个方法"+signature.getName());
//AOP代理类的名字
System.out.println("AOP代理类的名字"+signature.getDeclaringTypeName());
//AOP代理类的类(class)信息
signature.getDeclaringType();
//获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//请求
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
//会话
HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
//响应
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();
//获取请求参数
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 = JSON.toJSONString(parameterMap);
if(obj.length > 0) {
	System.out.println("请求的参数信息为:"+str);
}

例子1,给指定方法返回的字符串编码

@Slf4j
@Aspect
@Component
public class ExcrypResultAspect {

    @Pointcut("execution(public String com.sunpeiyu.demoitem.controller.RedisController.testRequest())")
    public void cutHttpRequest() {

    }

    @Around(value = "cutHttpRequest()")
    public Object encrypt(ProceedingJoinPoint joinPoint) throws Throwable {
        String targetResult = (String) joinPoint.proceed();
        log.info("=========================== targetResult = " + targetResult);
        byte[] encodeArr = Base64.getEncoder().encode(targetResult.getBytes(StandardCharsets.UTF_8));
        String base64Str = new String(encodeArr, StandardCharsets.UTF_8);
        log.info("=========================== base64Str = " + base64Str);
        return base64Str;
    }
}

结果:
image

例子2,将自定义注释的方法,对其返回的字符串编码

// 自定义注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptResult {

    String rule() default "MD5";
}

// 切面实现
@Slf4j
@Aspect
@Component
public class ExcrypResultAspect {

    @Pointcut("@annotation(com.sunpeiyu.demoitem.aspect.EncryptResult)")
    public void cutHttpRequest() {

    }

    @Around(value = "cutHttpRequest()")
    public Object encrypt(ProceedingJoinPoint joinPoint) throws Throwable {
        String targetResult = (String) joinPoint.proceed();
        log.info("=========================== targetResult = " + targetResult);
        EncryptResult encryptResult = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(EncryptResult.class);
        String rule = encryptResult.rule();

        if (rule.equals("base64")) {
            byte[] encodeArr = Base64.getEncoder().encode(targetResult.getBytes(StandardCharsets.UTF_8));
            String base64Str = new String(encodeArr, StandardCharsets.UTF_8);
            log.info("=========================== base64Str = " + base64Str);
            return base64Str;
        }

        String md5Digest = MD5Utils.md5Digest(targetResult);
        log.info("=========================== md5Digest = " + md5Digest);
        return md5Digest;
    }
}

// 测试
@EncryptResult
@GetMapping("/testRequest")
public String testRequest() {
	return "success";
}

结果:

image

例子3,HttpServletRequest解密

// 请求体解码注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptResult {

}

// 切面方法
@Around(value = "@annotation(com.sunpeiyu.demoitem.aspect.DecryptResult)")
public Object decrypt() {
	HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	byte[] httpBody = HttpUtils.getHttpBody(request);
	byte[] decodeArr = Base64.getDecoder().decode(httpBody);
	return new String(decodeArr, StandardCharsets.UTF_8);
}

参考

https://blog.csdn.net/weixin_45203607/article/details/120248631

posted @ 2023-08-21 22:47  sunpeiyu  阅读(26)  评论(0编辑  收藏  举报