Aop的基本介紹

基本概念java

  • 通知

   就是你想要的功能,也就是咱們常說的安全、事物、日誌等。先定義好這些,而後再想用的地方用一下。包含Aspect的一段處理代碼web

    注意:其實這些功能(通知)並非咱們業務邏輯所必須的,只是爲了安全,輸出信息,或者其餘的緣由,總之是爲了方便咱們對項目維護而增長的操做,通常咱們會把這些功能封裝成相關的方法,可是咱們又不想這些功能直接入侵咱們的正常業務代碼,由於這樣會增長關注度而且污染咱們的業務邏輯,因此咱們就用切面的思想來很好解決這個問題spring

  • 鏈接點

    就是spring容許你加 通知(Advice)的地方,那可就真多了,基本每一個方法的前、後(二者都有也行),或拋出異常時均可以是鏈接點,spring通常只支持方法鏈接點,除非引入其餘的aop框架才能夠實現更細粒度的鏈接點。其餘如AspectJ還可讓你在構造器或屬性注入時都行,不過那不是我們關注的,只要記住,和方法有關的前先後後都是鏈接點express

  • 切入點

    在上面說的鏈接點的基礎上,來定義切入點。例如:你的一個類裏,有15個方法,那就有至少十幾個鏈接點了對吧,可是你並不想在全部方法附近都使用通知(使用叫織入,下面再說),你只是想讓其中幾個,在調用這幾個方法以前、以後或者拋出異常時乾點什麼,那麼就用切入點來定義這幾個方法,讓切點來篩選鏈接點,選中那幾個你想要的方法安全

    注意:切入點定義的通常是你的業務中的某些方法(也有多是某些特殊地方,在「特例」下有個例子切點切的就是方法,而是自定義的註解),就是供切面實際切入的地方,也就是須要執行通知的地方框架

  • 切面

     用來切插業務方法的類。fetch

    切面是通知和切入點的結合。如今發現了吧,沒鏈接點什麼事,連接點就是爲了讓你好理解切點搞出來的,明白這個概念就好了。通知說明了幹什麼和何時幹(何時經過方法名中的befor,after,around等就能知道),二切入點說明了在哪幹(指定究竟是哪一個方法),這就是一個完整的切面定義spa

  • 目標

    引入中所提到的目標類,也就是要被通知的對象,也就是真正的業務邏輯,他能夠在絕不知情的狀況下,被我們織入切面,而本身專一於業務自己的邏輯。代理

  • 代理

    怎麼實現整套AOP機制的,都是經過代理,這個一下子給細說日誌

  • 織入(weaving) 

    把切面應用到目標對象來建立新的代理對象的過程。有三種方式,spring採用的是運行時,爲何是運行時,在上一文《Spring AOP開發漫談之初探AOP及AspectJ的用法》中第二個標提到

  • 目標對象 

    項目原始的Java組件。

  • AOP代理  

    由AOP框架生成java對象。

  • AOP代理方法 

    代理方法= advice + 目標對象的方法。

 

xml方式配置詳解

  • aop所需jar包

       <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>

  • 在使用xml方式的Aop時,首先要保證xml頭部引入了aop包和spring的基礎包,以下

      <beans xmlns="http://www.springframework.org/schema/beans" 

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

 

  • 在spring-context.xml(也就是spring的主配置文件)中加入<aop:aspectj-autoproxy/>
  • aop具體代碼

    <!-- 定義普通的Bean實例 -->

    <bean id="adviceTest" class="com.abc.advice.AdviceTest" />
    <!-Aop配置->
    <aop:config>
        <!-- 將容器中的adviceTest轉換成切面Bean -->  
          <!-- 注意這裏可使用order屬性爲Aspect指定優先級 -->
        <aop:aspect id="firstAspect" ref="adviceTest" order="2">
          <!-切點第一種寫法開始->
              <!-- @Before切點 -->
          <!-method屬性對應的方法必定是切面中的方法->
              <aop:before pointcut="execution(* com.abc.service.*.*(..))" 
                      method="permissionCheck"  arg-names="name,age"/>
              <!-- @After切點 -->
              <aop:after pointcut="execution(* com.abc.service.*.*(..))" 
                      method="releaseResource"/>
              <!-- @AfterReturning切點 -->
              <aop:after-returning pointcut="execution(* com.abc.service.*.*(..))" 
                      method="log" returning="discussion" arg-names="discussion"/>
          注意:若是有returning屬性,那麼arg-names屬性必須有,二者的屬性值能夠任意取名,可是必須一致,否者就會報錯
              <!-- @AfterThrowing切點 -->
              <aop:after-throwing pointcut="execution(* com.abc.service.*.*(..))" 
                      method="handleException"/>
              <!-- @Around切點(多個切點提示符使用and、or或者not鏈接) -->
              <aop:around pointcut="execution(* com.abc.service.*.*(..)) and args(name,time,..)" 
                      method="process"/>
          <!-切點第一種寫法結束->
 
             

          <!-切入點第二種寫法開始->

          <!-單參數傳參->

           <aop:pointcut      id="thinking"   expression="execution(* com.bird.springidol.Thinker.thinkOfSomething(String)) and args(thoughts)"/>             
          <aop:before method="interceptThoughts" pointcut-ref="thinking" arg-names="thoughts"/>
          注意:這個配置的意思就是,攔截這個Thinker接口的實現方法,其中的參數命名爲thoughts,
          下面的before方法裏面的 interceptThoughts方法接受一個參數,這個參數映射到thoughts這個地方,而後暗中的攔截方法就能接受到參數了
 
          <!-對象傳參->

          <aop:pointcut id="log4add" expression="execution(* com.tfedu.discuss.web.TeacherDiscussionController.delete(com.tfedu.discuss.abutment.TestAddLog)) and            args(testAddLog)" />
          <aop:after method="addLog" pointcut-ref="log4add" arg-names="testAddLog"/>

          <!-切入點第二種寫法結束->

          <!-切點爲註解時,也就是將切點切到註解上 開始->

              <!-切點爲註解時,也就是將切點切到註解上 結束-> 
          </aop:aspect>
    </aop:config>
    • pointcut-ref:當pointcut單獨寫時,在通知標籤裏,要是用pointcut-ref屬性來指向響應的切點Id
    • method:通常對應的是切面中的通知方法
    • arg-names:定義的是通知方法中的參數。通常通知方法中須要使用切點方法的參數時,這個屬性最好加上(固然有時不寫也能夠傳參),而且和expression表達式中的args()的小括號中的名稱保持一直
    • order:

      Spring中的事務是經過aop來實現的,當咱們本身寫aop攔截的時候,會遇到跟spring的事務aop執行的前後順序問題,好比說動態切換數據源的問題,若是事務在前,數據源切換在後,會致使數據源切換失效,因此就用到了Order(排序)這個關鍵字.咱們能夠經過在@AspectJ的方法中實現org.springframework.core.Ordered 這個接口來定義order的順序,order 的值越小,說明越先被執行

特例(某註解爲切點時)

通常咱們認爲切點的位置都是某個業務方法以前,以後或者先後都有,可是有些狀況下咱們也能夠將切點切到某個方法的註解上,在解決某些問題時很方便,下面舉例說明此種用法:

配置文件中內容

 

    <bean id="abutmentAspect" class="com.tfedu.discuss.abutment.aop.LogAspect"></bean>
    <aop:config>
      <aop:aspect id="" ref="abutmentAspect">
        <aop:pointcut id="addLog" expression="@annotation(com.tfedu.discuss.annotation.BehaviorLog)" />
        <aop:after method="behaviorLog4Review" pointcut-ref="addLog"/>
      </aop:aspect>
    </aop:config>

註解類

 

     @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
    @Documented
    public @interface BehaviorLog {

 

      OperTypeEnum value();//這裏面定義的方法要在註解上用到,這裏的方法名就是註解上的等號左邊的值

 

      String fiterName();

 

    }

注意:裏面的方法能夠根據具體須要來定義

註解的使用

 

   在業務方法上加上:@BehaviorLog(value = OperTypeEnum.REVIEW, fiterName = "historyDraftId")

通知方法代碼

 

public void behaviorLog4Review(JoinPoint joinPoint) {
      Signature signature = joinPoint.getSignature();
      MethodSignature methodSignature = (MethodSignature) signature;
      Method method = methodSignature.getMethod();
      String fiterName = method.getAnnotation(BehaviorLog.class).fiterName();//這裏獲取的是註解中定義的各類方法的值,例如這裏獲取的註解上的fiterName等號後面的的值

      Object[] args = joinPoint.getArgs();//這裏獲取的是方法裏的參數不是註解上的值,這個切記
      Long historyDraftId = getParam4Common(args, fiterName);
      HistoryDraftEntity historyDraftEntity = historyDraftBaseService.get(historyDraftId);
      if (Util.isEmpty(historyDraftEntity)) {
        ExceptionUtil.bEx("批閱中添加行爲日誌記錄是,歷史稿對象不能爲空", historyDraftEntity);
      }
      addLog(historyDraftId, LogTypeEnum.WRITING.key(), historyDraftEntity.getTitle(), fetchUser.getCurrentUserId(),
      OperTypeEnum.REVIEW.key());
    }

    

      private Long getParam4Common(Object[] args, String fiterName) {
        Optional<Object> obj = Arrays.stream(args).findFirst();
        if (!obj.isPresent()) {
          return PARAM_ID;
        }
        return ConvertUtil.obj2long(BeanUtil.get((obj.get()), fiterName));
      }
相關文章
相關標籤/搜索