SpringBoot中AspectJ的使用


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM