簡單直白的去理解AOP,瞭解Spring AOP,使用 @AspectJ - 讀書筆記

AOP = Aspect Oriental Programing  面向切面編程java

文章裏不講AOP術語,什麼鏈接點、切點、切面什麼的,這玩意太繞,記不住也罷。旨在以簡單、直白的方式理解AOP,理解Spring AOP, 應用 @AspectJ。正則表達式

  1. 什麼是AOP?
  2. Spring AOP 實現機制
  3. 使用Spring AOP,並經過xml配置(這個稍微看看就好了,你不必定用它)
  4. 使用@AspectJ (未完成)

 

一、什麼是AOP?spring

方法1 方法2 方法3
A A A
代碼x 代碼y 代碼z
B B B

 

 

 

 

從縱向看,方法一、二、3 都執行了相同的A、B代碼,這樣重複代碼是很無聊的。數據庫

一個典型的場景就是:開啓事務,更新表裏數據,提交事務;  開啓事務,刪除表裏數據,提交事務。編程

因此咱們從橫向來,把重複代碼抽取出來,變爲框架

A A A
方法1(代碼x) 方法2(代碼y) 方法3(代碼z)
B B B

 

 

 

 

AOP但願將A、B 這些分散在各個業務邏輯中的相同代碼,經過橫向切割的方式抽取到一個獨立的模塊中,還業務邏輯類一個清新的世界。ide

固然,將這些重複性的橫切邏輯獨立出來很容易,可是如何將獨立的橫切邏輯 融合到 業務邏輯中 來完成和原來同樣的業務操做,這是事情的關鍵,也是AOP要解決的主要問題。測試

 

2.Spring AOP 實現機制優化

Spring AOP使用動態代理技術在運行期織入加強的代碼,使用了兩種代理機制,一種是基於JDK的動態代理,另外一種是基於CGLib的動態代理this

織入、加強 是AOP的兩個術語,織入加強的代碼簡單點就是在你的代碼上插入另外一段代碼。

JDK動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy 和 InvocationHandler(接口)。

直接上代碼

package test;

public interface CalcService {
    public void add(int x, int y);
}
CalcService 接口
package test;

public class CalcServiceImpl implements CalcService{
    public void add(int x, int y) {
        System.out.println("結果爲" + (x + y));
    }
}
CalcServiceImpl 實現類
package test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class CalcHandler implements InvocationHandler {
    
    public Object target;
    
    
    public CalcHandler(Object target){
        this.target = target;
    }
    
    /** 
     * 實現接口的方法
     * @param proxy 最終生成的代理實例 
     * @param method 被代理目標(也就是target)的某個具體方法
     * @param args   某個具體方法的入參參數
     * @return Object 方法返回的值*/
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("*******調用方法前執行代碼******");
        Object obj = method.invoke(this.target, args);
        System.out.println("*******調用方法後執行代碼******");
        return  obj;
    }

}
CalcHandler 實現InvocationHandler
package test;

import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args){
        long start = System.nanoTime();
        
        CalcService target = new CalcServiceImpl();
        CalcHandler handler = new CalcHandler(target);
        CalcService calcProxy = (CalcService)Proxy.newProxyInstance(
                target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), 
                handler);
        System.out.println("建立時間:" + (System.nanoTime()-start));
        
        start = System.nanoTime();
        calcProxy.add(2, 3);
        System.out.println("執行時間:" + (System.nanoTime()-start));

    }
}
Test 測試

執行結果爲

*******調用方法前執行代碼******
結果爲2
*******調用方法後執行代碼******

可是JDK動態代理有一個限制,即它只能爲接口建立代理實例*******************************

看Proxy的方法 newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

interfaces 是須要代理實例實現的接口列表

那麼對於一個沒有經過接口定義業務方法的類,怎麼建立代理實例?

CGLib

CGLib採用很是底層的字節碼技術,能夠在運行時爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用。

package test;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CalcProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    
    public Object getProxy(Class clazz){
        enhancer.setSuperclass(clazz); // 設置須要被代理的類 target
        enhancer.setCallback(this);
        return enhancer.create(); // 經過字節碼技術動態建立子類
    }
    
    // 攔截父類全部方法的調用
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("*******調用方法前執行代碼******");
        Object result = proxy.invokeSuper(obj, args); // 經過代理類調用父類中的方法
        System.out.println("*******調用方法後執行代碼******");
        return result;
    }

}
CalcProxy 實現MethodInterceptor接口
package com.ycp.framework.test.proxyPattern.sample2;

public class Test2 {
    public static void main(String[] args) {
        CalcProxy proxy = new CalcProxy();
        CalcServiceImpl calcImpl = (CalcServiceImpl)proxy.getProxy(CalcServiceImpl.class);
        calcImpl.add(2, 3);
    }
}
Test2 測試類

 

以後對二者作了一個效率對比

我在本身本機上經過System.nanoTime()對二者作了記錄,結果以下

                        JDK動態代理                  CGLiib
建立代理對象時間         720 1394                    1 3473 7007       (時間單位爲納秒)

代理對象執行方法時間      97 7322                     15 2080

一個建立花費時間長,一個執行時間長。

 

3.使用 Spring AOP,並經過XML配置

在Spring中,定義了 AopProxy接口,並提供了兩個final類型的實現類

Cglib2AopProxy          JdkDynamicAopProxy

以一個前置加強爲例,也就是說在目標方法執行前執行的代碼

package test;

import java.lang.reflect.Method;


import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

public class CalcBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object obj) throws Throwable {
        System.out.println("*******調用目標方法前執行代碼******");

    }
    
    public static void main (String [] args){
        CalcService target = new CalcServiceImpl();
        
        CalcBeforeAdvice advice = new CalcBeforeAdvice();
        
        // 1 spring 提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        // 2 設置代理目標
        pf.setInterfaces(target.getClass().getInterfaces());// 指定對接口進行代理,將使用JdkDynamicAopProxy
        // 下面兩行操做,有任意一行,都將使用Cglib2AopProxy
        pf.setOptimize(true);// 啓用代理優化,將使用Cglib2AopProxy
        pf.setProxyTargetClass(true); // true表示對類進行代理,將使用Cglib2AopProxy
        
        pf.setTarget(target);
        // 3 爲代理目標添加加強
        pf.addAdvice(advice);
        // 4 生成代理實例
        
        CalcService proxy  = (CalcService) pf.getProxy();
        System.out.println(proxy.getClass().getName());
        proxy.add(2, 3);
    }
}
經過Spring實現前置加強

以上經過ProxyFactory建立代理,下面咱們經過Spring配置來聲明代理

<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="calcProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
  p:proxyInterfaces="test.CalcService" //指定代理接口,若是有多個接口,可用,隔開
  p:interceptorNames="calcBeforAdvice"//指定使用的加強,引用第一行,若是有多個可用,隔開
  p:target-ref="calcTarget"//指定對那個Bean進行代理,引用第二行
  p:proxyTargetClass="true" //指定是否對類進行代理
  p:singleton="true"//指定返回的代理是否單實例,默認爲true
/>

 除了前置加強BeforeAdvice,還有後置加強AfterReturningAdvice、環繞加強MethodInterceptor、異常拋出加強

ThrowsAdvice、及引介加強IntroductionInterceptor,均爲接口。

其中引介加強稍微強調一下,它會在目標類中增長一些新的方法和屬性。

 

到了這裏,可能對AOP稍有些瞭解了,那咱們簡單說一下AOP的幾個名詞

鏈接點Joinpoint:類初始化前、初始化後, 方法調用前、調用後,方法拋出異常後,這些特定的點,叫鏈接點。

切點Pointcut:想一想數據庫查詢,切點就是經過其所設定的條件找到對應的鏈接點。

加強Advice:就是把代碼加到某個鏈接點上。

引介Introduction:一種特殊的加強,它爲類增長一些屬性和方法,假設某個業務類沒有實現A接口,咱們給它添加方法,讓其成爲A的實現類。

織入Weaving:就是怎麼將加強添加到鏈接點上。

         三種織入方式:一、編譯期織入,要求使用特殊的JAVA編譯器

        2.類裝載期織入,要求使用特殊的類裝載器

        3.動態代理織入,在運行期爲目標類添加加強

        Spring採用動態代理,而AspectJ採用編譯期織入和類裝載期織入。

目標對象Target:也就是你本身的業務類,AOP就是對這個類作加強、引介。

代理Proxy: 目標對象被織入加強後產生的結果類。

切面:由切點和加強(引介)組成,Spring AOP就是負責實施切面的框架,它將切面所定義的橫切邏輯(也就是代碼)織入到切面所指定的鏈接點(也就是代碼往哪加)中。

看完了名詞,再看完以前的代碼,咱們發現加強被織入到了目標類的全部方法中(XX的,都木有選擇的餘地....)

如今咱們要對某些類的某些方法織入加強,那這時候就涉及到切點概念了

以以下爲例:我只想針對全部的以add開頭的方法作處理

靜態普通方法名匹配

package com.ycp.framework.test.proxyPattern.sample2;

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.IntroductionInterceptor;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class AddAdvisor extends StaticMethodMatcherPointcutAdvisor {
    
    //StaticMethodMatcherPointcutAdvisor 抽象類
    
    // 實現父類 matches方法
    public boolean matches(Method method, Class clazz) {
        //只匹配add方法
        return  0 == "add".indexOf(method.getName());
    }
    
//    //切點類匹配規則爲 CalcServiceImpl的類或子類,
//    @Override
//    public ClassFilter getClassFilter(){
//        return new ClassFilter(){
//            public boolean matches(Class clazz){
//                return CalcServiceImpl.class.isAssignableFrom(clazz);
//            }
//        };
//    }
    
    public static void main (String [] args){
        CalcService target = new CalcServiceImpl();
        CalcBeforeAdvice advice = new CalcBeforeAdvice();
        
        // 1 spring 提供的代理工廠
        ProxyFactory pf = new ProxyFactory();
        
        // 2 設置代理目標
        pf.setInterfaces(target.getClass().getInterfaces());// 指定對接口進行代理,將使用JdkDynamicAopProxy
        // 下面兩行操做,有任意一行,都將使用Cglib2AopProxy
        pf.setOptimize(true);// 啓用代理優化,將使用Cglib2AopProxy
        pf.setProxyTargetClass(true); // true表示對類進行代理,將使用Cglib2AopProxy
        
        pf.setTarget(target);
        
        // 3 爲代理目標添加加強
        AddAdvisor advisor = new AddAdvisor();
        advisor.setAdvice(advice);
        
        pf.addAdvisor(advisor);
        
        // 4 生成代理實例
        CalcService proxy  = (CalcService) pf.getProxy();
        
        System.out.println(proxy.getClass().getName());
        proxy.add(2, 3);
    }

}
AddAdvisor 繼承StaticMethodMatcherPointcutAdvisor

經過Spring配置來定義切面

<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="addAdvisor" class="test.AddAdvisor" 
   p:advice-ref="calcBeforAdvice"//向切面注入一個前置加強
 />

<bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean"
  p:interceptorNames="addAdvisor"
  p:proxyTargetClass="true" 
/>

<bean id="calc" parent="parent" p:target-ref="calcTarget"/> //CalcServiceImpl的代理

 

上面的忒麻煩,咱們經過靜態正則表達式來匹配

<bean id="calcBeforAdvice" class="test.CalcBeforeAdvice"/>
<bean id="calcTarget" class="test.CalcServiceImpl"/>
<bean id="addRegexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" 
   p:advice-ref="calcBeforAdvice"//向切面注入一個前置加強 >
    <property name="patterns">
     <list>
      <value> .add*</value>//匹配模式串
    </list>
  </property> 
</bean>
<bean id="calc" class="org.springframework.aop.framework.ProxyFactoryBean"
   p:interceptorNames="addRegexpAdvisor"
    p:target-ref="calcTarget"
  p:proxyTargetClass="true" 
/>

Spring提供了6種類型的切點,靜態方法切點、動態方法切點、註解切點、表達式切點、流程切點,我能力有限,沒有研究下去,僅以靜態切點 StaticMethodMatcherPointcut 作個例子就算完事,啥時項目用到了啥時再研究吧。

 

4.使用AspectJ

Spring AOP應用是比較麻煩的,要實現這個那個接口,寫這個那個XML描述,你頭疼不?

使用@AspectJ的註解能夠很是容易的定義一個切面,不須要實現任何的接口

相關文章
相關標籤/搜索