1. 程式人生 > >Spring in Anction:Spring AOP 小記

Spring in Anction:Spring AOP 小記

1.什麼是AOP(面向切面程式設計)

AOP

不扯那些概念的東西,簡單說來AOP是OOP的一個補充,AOP可以在程式執行期追加一些公用的功能,比如許可權判斷,日誌記錄,這些功能都是專案需要的,但是又不能每個地方都呼叫,這樣無疑增加了程式碼的複雜度和工作量,我們可以將這些分散在系統中的公用程式碼集中於一個地方並通過aop技術應用於系統各個地方。

2. AOP術語

  • 切點:我們需要插入這些公用功能的點?比如哪些類?哪些方法?等等(何處呼叫)
  • 切面:這些公用功能程式碼(呼叫什麼)
  • 通知:在什麼時候呼叫?

3.通知分類

  • 前置通知(Before):在目標方法呼叫前呼叫
  • 後置通知(After):在目標方法呼叫後呼叫
  • 返回通知(AfterReturning):在目標方法成功執行後呼叫
  • 異常通知(AfterThrowing):在目標方法執行失敗拋異常後呼叫
  • 環繞通知(Around):將目標方法包裹,由你決定什麼時候呼叫(最強大,最常用)

3.Spring AOP

Spring 通過生成代理類將目標物件包裹從而實現AOP,呼叫者呼叫目標方法時,其實是在呼叫代理類,再由代理類
呼叫目標方法,從而控制對目標方法的呼叫,Spring只支援最細方法級別的連線點,如果需要實現更細粒度的控制
則需要藉助專業的AOP框架AspectJ,可以控制欄位,構造方法等更細的東西,不過需要學習新的語法。

AOP

4.Spring 切點表示式

可以通過這些表示式配置切點

AOP

上面是Spring支援的AspectJ 切點表示式。除execution是增加切點範圍的外,其他都是用於縮小範圍

5.編寫切點

AOP

6.編寫切面

用註解@Aspect表示一個切面,註解用於類上

@Aspect //表示是切面
@Component //Bean注入Spring
@Order(1)//優先執行該切面
public class TicketLoginAspect {
    @Autowired
    private HttpServletRequest request;

    /**
     * 切點
     */
       //表示作用於TicketLogin註解
@Pointcut("@annotation(com.cnct.webchat.common.annotation.TicketLogin)") public void loginPointCut() { //擴充套件作用範圍,不用在每個@Around後寫很長的表示式了,只需寫方法名即可,像下面 } @Around("loginPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { } }

寫完切面後要開啟Spring aop自動代理才能生效,如果使用JavaConfig的話,在配置類上使用
@EnableAspectJ-AutoProxy即可,如果用XML配置則加入

7.通過註解為目標類增加新功能(方法)

既然AOP通過代理實現,那麼就可以為目標物件增加新的代理方法。

@Aspect //定義切面
@Component
public class TestAop{
  @DeclareParents(value = "com.lyq3.NewFunctionInterface+",
                  defaultImpl = NewFunctionImpl.class
  )
  public static Abc abc;
}
  • value 指定那種型別的Bean要引入,(後面的“+”號表示所有的子型別而不是本身)
  • defaultImpl 指定引入功能的實現類
  • @DeclareParents 所標註的靜態屬性指明要引入的介面,也就是要為Abc介面增加新功能

8.注意:踩坑記

@Aspect
@Component
@Order(2)//登入註解執行後執行該註解
public class HandleRecordAspect {
    @Autowired
    private HandleRecordMapper handleRecordMapper;
    @Autowired
    private HttpSession session;
    /**
     * 切點
     */
    @Pointcut("@annotation(com.cnct.webchat.common.annotation.HandleRecord)")
    public void handlePointCut() {

    }

    @Around("handlePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        //先執行方法
        Object result = point.proceed();
        //訪問目標方法的引數:
        Object[] args = point.getArgs();

        Signature sig = point.getSignature();
        MethodSignature msig = null;
        msig = (MethodSignature) sig;
        Method method = msig.getMethod();
//        String methodName = msig.getName();//方法名
        HandleRecord handleRecord = method.getAnnotation(HandleRecord.class);//獲取註解
        //操作標識(增?刪?改?)
        int handle_status = handleRecord.handle_status();
        String remark = handleRecord.value();
      }
    }

上面的程式碼是我在專案中實現一個存操作紀錄的部分程式碼,咋一看沒毛病。@HandleRecord這個自定義註解在Controller層能用,我放到Service層的時候(service有介面和介面實現,註解放實現類方法上)

  HandleRecord handleRecord = method.getAnnotation(HandleRecord.class);

這句程式碼是獲取不到註解的,也就是說 handleRecord = null;

原因是AOP採用的jdk代理,MethodSignature 轉型之後,會丟失子類的方法註解,所以得先獲取實現類的方法

修改下程式碼,就能用了:

//先執行方法
        Object result = point.proceed();
        //訪問目標方法的引數:
        Object[] args = point.getArgs();

        Signature sig = point.getSignature();
        MethodSignature msig = null;
        msig = (MethodSignature) sig;
        Method method = msig.getMethod();
        //MethodSignature 轉型之後,會丟失子類的方法註解,所以得先獲取實現類的方法
        //============================================
        Method soruceMethod = point.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
        //=============================================
        HandleRecord handleRecord = soruceMethod.getAnnotation(HandleRecord.class);//獲取註解
        //操作標識(增?刪?改?)
        int handle_status = handleRecord.handle_status();
        String remark = handleRecord.value();