SpringBoot Aop的小结

SpringBoot Aop概念

下面这段概念摘自武哥的《SpringBoot经典学习笔记》

AOP:Aspect Oriented Programming 的缩写,意为:面向切面编程。面向切面编程的目标就是分离关注点。什么是关注点呢?就是关注点,就是你要做的事情。假如你是一位公子哥,没啥人生目标,每天衣来伸手,饭来张口,整天只知道一件事:玩(这就是你的关注点,你只要做这一件事)!但是有个问题,你在玩之前,你还需要起床、穿衣服、穿鞋子、叠被子、做早饭等等等等,但是这些事情你不想关注,也不用关注,你只想想玩,那么怎么办呢?
对!这些事情通通交给下人去干。你有一个专门的仆人 A 帮你穿衣服,仆人 B 帮你穿鞋子,仆人 C 帮你叠好被子,仆人 D 帮你做饭,然后你就开始吃饭、去玩(这就是你一天的正事),你干完你的正事之后,回来,然后一系列仆人又开始帮你干这个干那个,然后一天就结束了!
这就是 AOP。AOP 的好处就是你只需要干你的正事,其它事情别人帮你干。也许有一天,你想裸奔,不想穿衣服,那么你把仆人 A 解雇就是了!也许有一天,出门之前你还想带点钱,那么你再雇一个仆人 E 专门帮你干取钱的活!这就是AOP。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。

添加Maven依赖

先创建一个SpringBoot工程,添加下面依赖

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

切面有哪些元素

  • @Aspect 表明是一个切面类

  • @Component 将当前类注入到Spring容器内

  • @Pointcut
    切入点,其中execution用于使用切面的连接点。使用方法:execution(方法修饰符(可选) 返回类型 方法名 参数
    异常模式(可选)) ,可以使用通配符匹配字符,*可以匹配任意字符。

  • @Before 在方法前执行

  • @After 在方法后执行

  • @AfterReturning 在方法执行后返回一个结果后执行 -

  • @AfterThrowing
    在方法执行过程中抛出异常的时候执行

  • @Around
    环绕通知,就是可以在执行前后都使用,这个方法参数必须为ProceedingJoinPoint,proceed()方法就是被切面的方法,上面四个方法可以使用JoinPoint,JoinPoint包含了类名,被切面的方法名,参数等信息。

  • JoinPoint
    JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象

普通方法实现切面

package com.example.aop.config;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/8/31 14:58
 */
@Component
@Aspect
public class LogAspectHandler
{
    
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义一个切点,拦截com.itcodai.course09.controller包和子包下的所有方法
     */
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    public void pointCut()
    {
    
    
    }

    /**
     * 在上面定义的切面方法之前执行该方法
     *
     * @param joinPoint jointPoint
     */
    @Before("pointCut()")
    public void doBefore(JoinPoint joinPoint)
    {
    
    
        logger.info("====doBefore方法进入了====");

        // 获取签名
        Signature signature = joinPoint.getSignature();
        // 获取切入的包名
        String declaringTypeName = signature.getDeclaringTypeName();
        // 获取即将执行的方法名
        String funcName = signature.getName();
        logger.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);

        // 也可以用来记录一些信息,比如获取请求的url和ip
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取请求url
        String url = request.getRequestURL().toString();
        // 获取请求ip
        String ip = request.getRemoteAddr();
        logger.info("用户请求的url为:{},ip地址为:{}", url, ip);
    }

    /**
     * 在上面定义的切面方法之后执行该方法
     *
     * @param joinPoint jointPoint
     */
    @After("pointCut()")
    public void doAfter(JoinPoint joinPoint)
    {
    
    

        logger.info("====doAfter方法进入了====");
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        logger.info("方法{}已经执行完", method);
    }

    /**
     * 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
     *
     * @param joinPoint joinPoint
     * @param result    result
     */
    @AfterReturning(pointcut = "pointCut()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result)
    {
    
    

        Signature signature = joinPoint.getSignature();
        String classMethod = signature.getName();
        logger.info("方法{}执行完毕,返回参数为:{}", classMethod, result);
        // 实际项目中可以根据业务做具体的返回值增强
        logger.info("对返回参数进行业务上的增强:{}", result + "增强版");
    }

    /**
     * 在上面定义的切面方法执行抛异常时,执行该方法
     *
     * @param joinPoint jointPoint
     * @param ex        ex
     */
    @AfterThrowing(pointcut = "pointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex)
    {
    
    
        Signature signature = joinPoint.getSignature();
        String method = signature.getName();
        // 处理异常的逻辑
        logger.info("执行方法{}出错,异常为:{}", method, ex);
    }

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
    {
    
    
        logger.info("=============进入doAround==================");
        return proceedingJoinPoint.proceed();

    }
}

写个测试的控制器

package com.example.aop.controller;

import com.example.aop.annotation.LogASP;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/8/31 14:59
 */
@RestController
public class TestController
{
    
    
    @GetMapping("/{name}")
    public String testAop(@PathVariable String name) {
    
    
        return "Hello " + name;
    }
}

浏览器访问8080,代参,进入控制台看打印

Aop实现自定义注解

自定义注解

package com.example.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogASP
{
    
    
    String param() default "";
}

定义切面

强调一下,自定义的注解要当做参数放在括号里,不然会报错

package com.example.aop.config;

import com.example.aop.annotation.LogASP;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/9/5 19:23
 */
@Aspect
@Component
public class LogAop
{
    
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    @Around("@annotation(logASP)")
    public Object around(ProceedingJoinPoint joinPoint, LogASP logASP) throws Throwable
    {
    
    
        long a = System.currentTimeMillis();
        System.out.println("方法开始时间是:"+a);
        Object o = joinPoint.proceed();
        long b = System.currentTimeMillis();
        System.out.println("方法结束时间是:"+b) ;
        log.info("方法总耗时{}秒",b-a);
        return o;
    }
}

控制器方法上加这个注解

package com.example.aop.controller;

import com.example.aop.annotation.LogASP;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2020/8/31 14:59
 */
@RestController
public class TestController
{
    
    
    @GetMapping("/{name}")
    @LogASP(param = "testAop")
    public String testAop(@PathVariable String name) {
    
    
        return "Hello " + name;
    }
}

访问打印

方法开始时间是:1599307795370
2020-09-05 20:09:55.370  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : =============进入doAround==================
2020-09-05 20:09:55.370  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : ====doBefore方法进入了====
2020-09-05 20:09:55.371  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : 即将执行方法为: testAop,属于com.example.aop.controller.TestController包
2020-09-05 20:09:55.372  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : 用户请求的url为:http://localhost:8080/jcl/,ip地址为:0:0:0:0:0:0:0:1
2020-09-05 20:09:55.413  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : 方法testAop执行完毕,返回参数为:Hello jcl
2020-09-05 20:09:55.414  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : 对返回参数进行业务上的增强:Hello jcl增强版
2020-09-05 20:09:55.414  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : ====doAfter方法进入了====
2020-09-05 20:09:55.414  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAspectHandler  : 方法testAop已经执行完
方法结束时间是:1599307795414
2020-09-05 20:09:55.414  INFO 10624 --- [nio-8080-exec-1] com.example.aop.config.LogAop            : 方法总耗时44秒
方法开始时间是:1599307795483
2020-09-05 20:09:55.483  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : =============进入doAround==================
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : ====doBefore方法进入了====
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : 即将执行方法为: testAop,属于com.example.aop.controller.TestController包
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : 用户请求的url为:http://localhost:8080/favicon.ico,ip地址为:0:0:0:0:0:0:0:1
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : 方法testAop执行完毕,返回参数为:Hello favicon.ico
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : 对返回参数进行业务上的增强:Hello favicon.ico增强版
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : ====doAfter方法进入了====
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAspectHandler  : 方法testAop已经执行完
方法结束时间是:1599307795484
2020-09-05 20:09:55.484  INFO 10624 --- [nio-8080-exec-2] com.example.aop.config.LogAop            : 方法总耗时1秒

关于自定义注解,这里只是简单地使用一下,并没有进行深刻的研究,如果想了解更多的玩法,可以找下其他大佬的blog进行深入研究。

猜你喜欢

转载自blog.csdn.net/Curtisjia/article/details/108423708