首页 > 技术文章 > SpringBoot中AspectJ的使用

iven98 2021-10-15 13:22 原文

SpringBoot中AspectJ的使用

AspectJ作为语言级别的AOP框架,功能相比于SpringAOP更加强大。SpringAOP旨在提供给用户一个轻量级的AOP实现方案,它只能应用在SpringIOC容器中管理的bean。而AspectJ旨在提供给用户一个完整的AOP解决方案,它可以应用在所有的域对象中。

- AspectJ织入代码方式

AspectJ在织入代码时,有三种不同类型的编织:
官网对于织入时机的解释

  1. 编译时织入(CTW)
  2. 编译后织入 (BTW)
  3. 类加载时织入 (LTW)

- SpringAOP织入代码方式

SpringAOP是基于动态代理来实现的,在运行期通过接口或者子类的方式来实现AOP。在SpringAOP中主要有两种:

  1. JDK动态代理(基于接口实现)
  2. CGLib动态代理(基于类实现)

本文主要介绍AspectJ在SpringBoot中的两种实现(CTW、LTW)

1. CTW的实现

使用CTW的方式织入时,需要采用特殊的编译器(ajc)来进行编译

  1. IDEA的相关配置
    在Setting将Java Complier替换为Ajc编译器
    在这里插入图片描述
    注意:必须现在项目中导入aspectjtools.jar
    在Project Structure中导入依赖包aspectjrt.jar
    在这里插入图片描述
    在facets中添加AspectJ
    在这里插入图片描述
  2. 相关设置
    aop.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
    <aspectj>
        <weaver>
            <include within="com.zakary.qingblog..*" />
        </weaver>
        <aspects>
            <aspect name="com.zakary.qingblog.aop.LoginAspect"/>
            <aspect name="com.zakary.qingblog.aop.InterceptorAspect"/>
            <aspect name="com.zakary.qingblog.aop.ParamsCheckAspect"/>
        </aspects>
    </aspectj>
  1. 切面实现
    切面
@Aspect
@DeclarePrecedence("InterceptorAspect,LoginAspect,ParamsCheckAspect")
public class LoginAspect {
    private Logger logger= LoggerFactory.getLogger(QingblogApplication.class);
    /**
     * 匹配规则
     * execution: 用于匹配方法执行的连接点;
     * execution(public * *(..)) ==> 匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法。
     * execution(* com.oysept.springboot.controller..*.*(..))                ==> 该包及所有子包下任何类的任何方法。
     * execution(* com.oysept.springboot.controller.*(..))                   ==> 该包下任何类的任何方法。
     * execution(* com.oysept.springboot.controller.AspectJController.*(..)) ==> 该包下AspectJController类的任何方法。
     * execution(* com..*.*Controller.method*(..)) ==> 匹配包名前缀为com的任何包下类名后缀为Controller的方法,方法名必须以method为前缀。
     * execution(* *To(..)) ==> 匹配目标类所有以To为后缀的方法。
     * 注: 该方法只是为了声明一个公共的环绕通知,也可以直接在具体方法配置,如: @Around("execution(* com.oysept.springboot.controller..*.*(..))")
     */
    @Pointcut("execution(* com.zakary.qingblog.controller.LoginController.userLogin(..))")
    public void loginAop() {}

    @Before("loginAop()")
    public void before(JoinPoint point) throws Throwable {
        Object[] objArgs = point.getArgs();
        String mail= AnalysisUtils.getObjectToMap(objArgs[0]).get("userMail").toString();
        String password=AnalysisUtils.getObjectToMap(objArgs[0]).get("userPassword").toString();
        logger.info("User Login : [ userMail : "+mail+"\t , password : "+password+"\t ]");
    }
}

切入点

@Controller
public class LoginController {
    @Autowired
    private LoginService loginService;
    @RequestMapping("/userLogin")
    @ResponseBody
    public JSONResult userLogin(@RequestBody @Validated({ValidationGroups.LoginGroup.class}) User user, HttpServletRequest request){
        User user1=loginService.login(user);
        HttpSession session=request.getSession();
        session.setAttribute("userId",user1.getUserId());
        return JSONResult.ok("success");
    }
}

编译后的class文件

@Controller
public class LoginController {
    @Autowired
    private LoginService loginService;

    public LoginController() {
    }

    @RequestMapping({"/userLogin"})
    @ResponseBody
    public JSONResult userLogin(@RequestBody @Validated({LoginGroup.class}) User user, HttpServletRequest request) {
        JoinPoint var5 = Factory.makeJP(ajc$tjp_0, this, this, user, request);

        JSONResult var9;
        try {
            LoggerAspect.aspectOf().before(var5);
            LoginAspect.aspectOf().before(var5);
            ParamsCheckAspect.aspectOf().beforeLogin(var5);
            User user1 = this.loginService.login(user);
            HttpSession session = request.getSession();
            session.setAttribute("userId", user1.getUserId());
            var9 = JSONResult.ok("success");
        } catch (Throwable var10) {
            LoggerAspect.aspectOf().releaseResource(var5);
            throw var10;
        }

        LoggerAspect.aspectOf().releaseResource(var5);
        return var9;
    }

    static {
        ajc$preClinit();
    }
}

图中执行了多个AspectOf方法,因为我在此处实现了多个切面

2. LTW的实现

  1. IDEA的相关配置
    RUN->Edit Configurations->Configuration->Main Class->Environment->VM options填入
    -javaagent:"C:\Users\Zakary.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar"
    -javaagent:"C:\Users\Zakary.m2\repository\org\springframework\spring-instrument\5.1.9.RELEASE\spring-instrument-5.1.9.RELEASE.jar"
    此包在maven仓库中可以下载
  2. application.yml
  spring:
    aop:
        auto: false

关闭Springaop
设置ltw CustomLtwConfig.java

@Configuration
@ComponentScan("com.zakary.qingblog.controller")
@EnableLoadTimeWeaving(aspectjWeaving=ENABLED)
public class CustomLtwConfig{

}

注:aspectjWeaving有三个值,ENABLED为强制使用LTW的方式,DISENABLED不使用,AUTODETECT为查找META-INF下有无aop.xml文件,如果没有,不使用LTW,有的话使用LTW

  1. 切面实现同上

3. 多切面执行顺序的解决方案

  1. 如果采用SpringAop时,同一个切点含有多个advice时,使用@order注解来决定每个advice的执行顺序,其中value值越小越先执行。但@order不能使用在aspectj的切面中,@order只能决定SpringIOC容器中的bean。
  2. 采用AspectJ时,使用注解@DeclarePrecedence,参数为切面类,
    例如
@DeclarePrecedence("InterceptorAspect,LoginAspect,ParamsCheckAspect")

参考文章
CTW
LTW

推荐阅读