SpringBoot使用AOP



最近在学习使用springboot,我们都知道spring的核心是IOC和AOP,但是一直没有实际使用过AOP去实现某个功能,自己边学习边总结一些经验,有哪个地方写的不对的望大家一块指正和讨论。


  1. 首先引入aop的依赖,其他基础包不再贴出
<!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  1. 我们使用注解的方式实现aop
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;


@Aspect
@Component
@Slf4j//如果你的log报错记得下载lombok插件,这里不细讲自行baidu
public class LogAspect {

    //线程副本类去记录各个线程的开始时间
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**1、execution 表达式主体
	  2、第1个* 表示返回值类型  *表示所有类型
	  3、包名  com.*.*.controller下
	  4、第4个* 类名,com.*.*.controller包下所有类
	  5、第5个* 方法名,com.*.*.controller包下所有类所有方法
	  6、(..) 表示方法参数,..表示任何参数
	  */
    @Pointcut("execution(public * com.wf.controller..*.*(..))")
    public void LogAspect() {
    }

    @Before("LogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        startTime.set(System.currentTimeMillis());
        //获取servlet请求对象---因为这不是控制器,这里不能注入HttpServletRequest,但springMVC本身提供ServletRequestAttributes可以拿到
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 想那个url发的请求
        log.info("URL:" + request.getRequestURL().toString());
        log.info("METHOD:" + request.getMethod());
        // 请求的是哪个类,哪种方法
        log.info("请求方法为:" + joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName());
        // 方法本传了哪些参数
        log.info("传递参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    @After("LogAspect()")
    public void doAfter(JoinPoint joinPoint) {
        log.info(joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName()+"方法执行时间:" + (System.currentTimeMillis() - startTime.get())+"ms");
    }

    @AfterReturning(returning = "ret", pointcut = "LogAspect()")
    public void doAfterReturning(JoinPoint joinPoint,Object ret) {
        log.info("返回值 : " + ret);
    }

    @AfterThrowing("LogAspect()")
    public void deAfterThrowing(JoinPoint joinPoint) {
    }

    @Around("LogAspect()")
    public Object deAround(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }

}

直接使用上面的代码就可以用aop实现controller层每个接口日志信息打印和接口请求时间的计算。

  1. 大概讲解一下每个注解的作用
@Aspect -- 作用是把当前类标识为一个切面供容器读取
@Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
@Before -- 标识一个前置增强方法,相当于BeforeAdvice的功能
@AfterReturning -- 后置增强,相当于AfterReturningAdvice,方法退出时执行
@AfterThrowing -- 异常抛出增强,相当于ThrowsAdvice
@After -- final增强,不管是抛出异常或者正常退出都会执行
@Around -- 环绕增强,相当于MethodInterceptor

其中@Pointcut是比较关键的一个注解,下面是用法

//表示匹配所有方法  
1)execution(* *(..))  
//表示匹配com.wf.server.UserService中所有的公有方法  
2)execution(public * com. wf.service.UserService.*(..))  
//表示匹配com.savage.server包及其子包下的所有方法 
3)execution(* com.savage.server..*.*(..))

我贴出的代码中是这样写的
@Pointcut(“execution(public * com.wf.controller….(…))”)
因为我的controller下还有子包所以后面是controller…后面是连着的两个点,如果你的controller包下直接就是类,写一个点即可@Pointcut(“execution(public * com.wf.controller..(…))”) 。

4.如果你使用上面代码没有打印日志,可能问题是你定义的类没有被主程序扫描到,要检查一下主程序上面的扫描包。
我的LogAspect这个类写在com.wf.aop ,所以在主程序上要加上这个包,这样就会生效,代码如下,这样你就可以看到日志的输出打印。

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@ComponentScan(basePackages = { "com.wf.controller","com.wf.service",
       "com.wf.configuration","com.wf.util","com.wf.aop"})
@MapperScan({"com.wf.mapper"})
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 看到以下结果恭喜你操作成功
2019-11-12 11:05:42.143  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : URL:http://*/*/*/*
2019-11-12 11:05:42.143  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : METHOD:POST
2019-11-12 11:05:42.143  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : 请求方法为:com.wf.controller.*.*.*
2019-11-12 11:05:42.143  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : 传递参数:
2019-11-12 11:05:42.144  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : com.wf.controller.*.*方法执行时间:1ms
2019-11-12 11:05:42.144  INFO 8545 --- [o-8111-exec-100] com.wf.aop.LogAspect                     : 返回值 : Result{code=10000, msg='操作成功', data=[]}
  1. 以上为本人自己学习结果以及碰到的问题,有什么不对的,请大家指正,有什么问题我们也可以一块讨论!