讀書筆記-aop基礎

aop是什麼

aop是面向切面編程(aspect oriented programing)的簡稱。aop的出現並非要徹底替代oop,僅是做爲oop的有益補充。
aop的應用場合是有限的,通常只適合於那些具備橫切邏輯的應用場合。java

  • 性能監測正則表達式

  • 訪問控制spring

  • 事務管理數據庫

  • 日誌記錄
    ...編程

aop中的概念

鏈接點(joinpoint)

一個類或一段程序代碼擁有一些具備邊界性質的特定點,這些代碼中的特定點就稱爲鏈接點。好比,app

  • 類開始初始化前,後ide

  • 類中某個方法調用前,後oop

  • 方法拋出異常後
    ...性能

鏈接點由兩個信息肯定:測試

  1. 用方法表示的程序執行點

  2. 用相對點表示的方位

如在Test.foo()方法執行前的鏈接點,執行點爲Test.foo(),方位爲該方法執行前的位置。
spring使用切點對執行點定位,而方位則在加強類型中定義.

切點(pointcut)

每一個程序類均可能有多個鏈接點,aop經過切點定位特定點。類比於數據庫查詢:鏈接點至關於數據庫中的記錄,切點至關於查詢條件。
切點和鏈接點不是一對一關係,一個切點能夠匹配多個鏈接點。
切點只定位到某個方法上,若是但願定位到具體的鏈接點上,還須要提供方位信息。

加強(advice)

加強是織入到目標類鏈接點上的一段代碼.它除用於描述一段代碼外,還擁有另外一個和鏈接點相關的信息,這即是執行點的方位。結合執行點方位信息和切點信息,就能夠找到特定的鏈接點了。
spring提供的加強接口都是帶方位名的:BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。

目標對象(target)

加強邏輯的織入目標類。

引介(introduction)

引介是一種特殊的加強,它爲類添加一些屬性和方法。這樣,即便一個業務類原來沒有實現某個接口,經過引介,也能夠動態的爲業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。

織入(weaving)

織入是將加強添加對目標類具體鏈接點的過程。aop有三種織入方式:

  1. 編譯期織入,這要求使用特殊的java編譯器

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

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

spring使用第3種方式織入,aspectj使用第1,2種方式。

代理(proxy)

一個類被aop織入加強後,就產生一個結果類,它融合了原來類和加強邏輯的代理類。咱們能夠採用調用原來類相同的方式調用代理類。

切面(aspect)

切面由切點和加強(引介)組成,它既包括了橫切邏輯的定義,也包括了鏈接點的定義。spring aop負責實施切面,它將切面所定義的橫切邏輯織入到切面所指定的鏈接點鐘。

建立加強類

前置加強

場景:高級餐廳的服務員在回答顧客以前都會說'你好!...'.

public class Waiter {
    public void check(String name){
        System.out.println("結帳?"+name);
    }
    public void serve(String name){
        System.out.println("要點什麼?"+name);
    }
}

前置加強

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class GreetAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] args, Object obj)throws Throwable {
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
    }
}

測試

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

public class TestBeforeAdvice {
    public static void main(String[] args){
        Waiter target=new Waiter();
        BeforeAdvice advice=new GreetAdvice();
        ProxyFactory pf=new ProxyFactory();//spring提供的代理工廠
        pf.setTarget(target);//設置代理目標
        pf.addAdvice(advice);//添加加強
        Waiter proxy=(Waiter)pf.getProxy();//代理實例
        proxy.serve("TheViper");
        proxy.check("TheViper");
    }
}

結果

你好!TheViper
來點什麼?TheViper
你好!TheViper
結帳?TheViper
  • ProxyFactory內部使用JDK代理或CGLib代理,將加強應用到目標類。

  • 還能夠將接口設置爲代理目標。

...
ProxyFactory pf=new ProxyFactory();
pf.setInterfaces(target,getClass().getInterfaces);
pf.setTarget(target);
...

在spring中配置

application-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd ">
    <bean id='greetAdvice' class='com.GreetAdvice'></bean>
    <bean id='target' class='com.Waiter'></bean>
    <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
    p:target-ref='target' p:interceptorNames='greetAdvice'/>
</beans>

測試

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestBeforeAdvice {
    public static void main(String[] args){        
        ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
        Waiter waiter=(Waiter)ctx.getBean("waiter");
        waiter.serve("TheViper");
        waiter.check("TheViper");
    }
}

ProxyFactoryBean經常使用配置:

  • target:代理的目標對象

  • proxyInterfaces:代理所要實現的接口,能夠是多個接口。該屬性還有一個別名屬性interfaces

  • interceptorNames:須要植入目標對象的Bean列表。這些Bean必須是實現了org.aopalliance.intercept.MethodInterceptororg.springframework.aop.Advisor的Bean,配置中的順序對應調用的順序。

  • singleton:返回的代理是否爲單例,默認爲單例

  • optimize:設置爲true時,強制使用CGLib代理。對於singleton代理,推薦使用CGLib,對於其餘做用域類型的代理,最好使用JDK代理。由於CGLib建立代理速度慢,而建立出的代理對象運行效率較高。JDK代理的表現與之相反

  • proxyTargetClass:是否對類進行代理(不是對接口進行代理),設置爲true時,使用CGLib

從上面spring配置能夠看到,ProxyFactoryBean用的是JDK代理,若是將proxyTargetClass設置爲true後,無需再設置proxyInterfaces屬性,即便設置了也會被忽略。

後置加強

場景:服務員和顧客交流後,禮貌的說'please enjoy yourself'.
後置加強

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class GreetAfterAdvice implements AfterReturningAdvice{
    
    @Override
    public void afterReturning(Object returnObj,Method method,Object[] args,Object obj)throws Throwable {
        //returnObj:目標實例方法返回的結果 method:目標類的方法 args:目標實例的方法參數 obj:目標類實例
        System.out.println("please enjoy yourself!");
    }
}

spring配置

...
<bean id='greetBeforeAdvice' class='com.GreetAdvice'></bean>
<bean id='greetAfterAdvice' class='com.GreetAfterAdvice'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:target-ref='target' p:interceptorNames='greetBeforeAdvice'/>
...

結果

你好!TheViper
要點什麼?TheViper
please enjoy yourself!
你好!TheViper
結帳?TheViper
please enjoy yourself!

環繞加強

環繞加強容許在目標類方法調用先後織入橫切邏輯,它綜合實現了前置,後置加強兩種的功能。

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class GreetingInterceptor implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args=invocation.getArguments();
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
        Object obj=invocation.proceed();//經過反射調用目標方法
        System.out.println("please enjoy yourself!");
        return obj;
    }
}
...
<bean id='greetingInterceptor' class='com.GreetingInterceptor'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:target-ref='target' p:interceptorNames='greetingInterceptor,greetAfterAdvice'/>
...

異常拋出加強

異常拋出加強最適合的場景是事務管理。

import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;

public class TransactionManager implements ThrowsAdvice{
    public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{
        System.out.println("method:"+method.getName());
        System.out.println("拋出異常"+ex.getMessage());
        System.out.println("回滾");
    }
}

引介加強

引介加強不是在目標方法周圍織入加強,而是爲目標類建立新的方法和屬性。因此引介加強的鏈接點是類級別的,非方法級別.

建立切面

前面織入加強時,都是織入到目標類的全部方法中。這節將會介紹如何讓加強提供鏈接點方位的信息,如織入到方法前面,後面等,而切點進一步描述具體織入哪些類的哪些方法上。
spring經過org.springframework.aop.Pointcut接口描述切點,PointcutClassFilterMethodMatcher構成。
經過ClassFilter定位到某些特定類上,經過MethodMatcher定位到某些特定方法上。
此外,spring還提供註解切點和表達式切點,二者都使用AspectJ的切點表達式語言。

切點類型

  • 靜態方法切點

  • 動態方法切點

  • 註解切點

  • 表達式切點

  • 流程切點

  • 複合切點

靜態普通方法名匹配切面

StaticMethodMatcherPointcutAdvisor表明一個靜態方法匹配切面,它經過StaticMethodMatcherPointcut定義切點,經過類過濾和方法名匹配定義切點。
例子,Seller類也有serve方法

public class Seller {
    public void serve(String name){
        System.out.println("seller說:要點什麼?"+name);
    }
}

前置加強(advice)

public class GreetingBeforeAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] args, Object obj)throws Throwable {
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
    }
}

切面(advisor)

import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{
    
    @Override
    public boolean matches(Method method, Class<?> cls) {//切點方法匹配
        return "serve".equals(method.getName());
    }
    //切點類匹配規則:Waiter的類或子類
    public ClassFilter getClassFilter(){
        return new ClassFilter(){
            public boolean matches(Class cls){
                return Waiter.class.isAssignableFrom(cls);
            }
        };
    }
}

spring配置

...
<bean id='greetBeforeAdvice' class='com.GreetingBeforeAdvice'></bean>
<bean id='waiterTarget' class='com.Waiter'></bean>
<bean id='sellerTarget' class='com.Seller'></bean>
<!-- 向切面注入前置加強 -->
<bean id='greetingAdvisor' class='com.GreetAdvisor' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='greetingAdvisor'/>
<bean id='waiter' parent='parent' p:target-ref='waiterTarget'></bean><!-- waiter代理 -->
<bean id='seller' parent='parent' p:target-ref='sellerTarget'></bean><!-- seller代理 -->
...

<bean id='parent' abstract='true' ...>表示經過一個父<bean>定義公共的配置信息。
測試

ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
Waiter waiter=(Waiter)ctx.getBean("waiter");
Seller seller=(Seller)ctx.getBean("seller");
waiter.serve("TheViper");
waiter.check("TheViper");
seller.serve("TheViper");

結果

你好!TheViper
waiter說:要點什麼?TheViper
waiter說:結帳?TheViper
seller說:要點什麼?TheViper

能夠看到切面只是織入到Waiter.serve()方法調用前的鏈接點上,而Waiter.check()和Seller.serve()都沒有織入切面。

靜態正則表達式匹配切面

RegexpMethodPointcutAdvisor是正則表達式方法匹配的切面實現類。該類已是功能齊備的的實現類了,通常狀況下,無需擴展該類。

...
<bean id='regexpAdvisor' class='org.springframework.aop.support.RegexpMethodPointcutAdvisor' 
p:advice-ref='greetBeforeAdvice'>
    <property name="patterns">
        <list>
            <value>.*che.*</value>
        </list>
    </property>
</bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='regexpAdvisor'/>
...

這裏定義了一個匹配模式.*che.*,它會匹配check()方法。

...
waiter.serve("TheViper");
waiter.check("TheViper");
...
waiter說:要點什麼?TheViper
你好!TheViper
waiter說:結帳?TheViper

動態切面

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;

public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{
    private static List<String> specialCients=new ArrayList<String>();
    static{
        specialCients.add("Tom");//添加白名單
        specialCients.add("TheViper");
    }
    //切點類匹配規則:Waiter的類或子類
    public ClassFilter getClassFilter(){//靜態匹配
        return new ClassFilter(){
            public boolean matches(Class cls){
                System.out.println("對"+cls.getName()+"類作靜態檢查");
                return Waiter.class.isAssignableFrom(cls);
            }
        };
    }
    public boolean matches(Method method, Class<?> cls) {//切點方法靜態匹配
        System.out.println("對"+cls.getName()+"類的"+method.getName()+"方法作靜態檢查");
        return "serve".equals(method.getName());
    }
    @Override
    public boolean matches(Method method, Class<?> cls, Object[] args) {//動態匹配
        System.out.println("對"+cls.getName()+"類的"+method.getName()+"方法作動態檢查");
        String clientName=args[0].toString();
        return specialCients.contains(clientName);
    }
}

匹配規則:目標類爲Waiter或其子類,方法名爲serve,動態傳入的參數name必須在白名單中存在。

<bean id='dynamicAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'>
        <property name="pointcut">
            <bean class='com.GreetingDynamicPointcut'/>
        </property>
        <property name="advice">
            <bean class='com.GreetingBeforeAdvice'/>
        </property>
    </bean>
    <bean id='waiter1' class='org.springframework.aop.framework.ProxyFactoryBean' 
    p:interceptorNames='dynamicAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter1");
        waiter.serve("Peter");
        waiter.check("Peter");
        waiter.serve("TheViper");
        waiter.check("TheViper");
對com.Waiter類作靜態檢查
對com.Waiter類的serve方法作靜態檢查
對com.Waiter類作靜態檢查
對com.Waiter類的check方法作靜態檢查
對com.Waiter類作靜態檢查
對com.Waiter類的clone方法作靜態檢查
對com.Waiter類作靜態檢查
對com.Waiter類的toString方法作靜態檢查
//上面是織入前spring對目標類中的全部方法進行的靜態切點檢查
對com.Waiter類作靜態檢查
對com.Waiter類的serve方法作靜態檢查
對com.Waiter類的serve方法作動態檢查
waiter說:要點什麼?Peter
對com.Waiter類作靜態檢查
對com.Waiter類的check方法作靜態檢查
//靜態方法檢查沒經過,不用動態檢查了
waiter說:結帳?Peter
對com.Waiter類的serve方法作動態檢查
//第二次調用不用執行靜態檢查
你好!TheViper
//動態檢查,知足白名單,執行前置加強
waiter說:要點什麼?TheViper
waiter說:結帳?TheViper

定義動態切點時,切勿忘記同時覆蓋getClassFilter()和matches(Method method,Class cls)方法,經過靜態切點檢查能夠排除掉大部分不符合匹配規則的方法。

流程切面

spring的流程切面由DefaultPointcutAdvisorControlFlowPointcut實現。流程切點表明由某個方法直接或間接發起調用的其餘方法。
定義Waiter的代理

public class WaiterDelegate {
    private Waiter waiter;
    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }
    public void service(String name){
        waiter.serve(name);
        waiter.check(name);
    }
}
<bean id='controlFlowPointcut' class='org.springframework.aop.support.ControlFlowPointcut'>
    <constructor-arg type='java.lang.Class' value='com.WaiterDelegate'/>
    <constructor-arg type='java.lang.String' value='service'/><!-- 指定流程切點的方法 -->
</bean>
<bean id='controlFlowAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' 
p:pointcut-ref='controlFlowPointcut' p:advice-ref='greetBeforeAdvice'/>
<bean id='waiter2' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='controlFlowAdvisor' p:target-ref='waiterTarget'/>
Waiter waiter=(Waiter)ctx.getBean("waiter2");
        WaiterDelegate wd=new WaiterDelegate();
        wd.setWaiter(waiter);
        waiter.serve("TheViper");
        waiter.check("TheViper");
        wd.service("TheViper");
waiter說:要點什麼?TheViper
waiter說:結帳?TheViper
//直接調用,加強不起做用
你好!TheViper
waiter說:要點什麼?TheViper
你好!TheViper
waiter說:結帳?TheViper

複合切面

spring提供ComposablePointcut把兩個切點組合起來,經過切點的複合運算表示。
ComposablePointcut自己也是一個切點,它實現了Pointcut接口。
圖片描述
交集運算的方法
圖片描述
並集運算
圖片描述

import java.lang.reflect.Method;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class GreetingComposablePointcut {
    public Pointcut getIntersectionPointcut(){
        ComposablePointcut cp=new ComposablePointcut();
        Pointcut pt1=new ControlFlowPointcut(WaiterDelegate.class,"service");
        MethodMatcher pt2=new NameMatchMethodPointcut(){
            public boolean matches(Method method, Class<?> cls) {// 切點方法靜態匹配
                return "check".equals(method.getName());
            }
        };
        return cp.intersection(pt1).intersection(pt2);
    }
}
<bean id='gcp' class='com.GreetingComposablePointcut'></bean>
<bean id='composableAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' 
p:pointcut='#{gcp.intersectionPointcut}' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='waiter3' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='composableAdvisor' p:target-ref='waiterTarget'/>

\ #{gcp.intersectionPointcut}表示引用gcp.getIntersectionPointcut()方法返回的複合切點

Waiter waiter=(Waiter)ctx.getBean("waiter3");
        WaiterDelegate wd=new WaiterDelegate();
        wd.setWaiter(waiter);
        waiter.serve("TheViper");
        waiter.check("TheViper");
        wd.service("TheViper");
waiter說:要點什麼?TheViper
waiter說:結帳?TheViper
//直接調用,加強不起做用
waiter說:要點什麼?TheViper
你好!TheViper//匹配check方法
waiter說:結帳?TheViper

引介切面

相關文章
相關標籤/搜索