再學習之Spring(面向切面編程).

1、概念

一、理論

    把橫切關注點和業務邏輯相分離是面向切面編程所要解決的問題。若是要重用通用功能的話,最多見的面向對象技術是繼承(inheritance)或 組成(delegation)。可是,若是在整個應用中都使用相同的基類,繼承每每會致使一個脆弱的對象體系;而使用組成可能須要對委託對象進行復雜的調用。切面提供了取代繼承和委託的另外一種可選方案,並且在不少場景下更清晰簡潔。Spring AOP 基於動態代理,因此Spring只支持方法鏈接點,這與一些其餘的AOP框架是不一樣的,例如AspectJJBoss,除了方法切點,它們還提供了字段和構造器接入點。 
java

二、AOP術語

橫切關注點(cross-cutuing concern):散佈在應用中多處的功能。web

切面(aspect) : 橫切關注點模塊化爲特殊的類。切面是通知和切點的結合。spring

通知(advice):定義了切面是什麼以及什麼時候使用。express

Spring切面能夠應用5種類型的通知:
編程

前置通知(Before):在目標方法被調用以前調用通知功能;
後置通知(After):在目標方法完成以後調用通知,此時不會關心方法的輸出是什麼;
返回通知(After-returning):在目標方法成功執行以後調用通知;
異常通知(After-throwing):在目標方法拋出異常後調用通知;
環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用以前和調用以後執行自定義的行爲。app

切點(pointcut):定義了切面在何處調用,會匹配通知所要織入的一個或多個鏈接點。
鏈接點(join point):在應用執行過程當中可以插入切面的一個點。這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。框架

織入(Weaving):織入是把切面應用到目標對象並建立新的代理對象的過程。ide

織入有三種方式能夠實現,Spring採用的是第三種,在運行期織入的:模塊化

編譯期:切面在目標類編譯時被織入。這種方式須要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。
類加載期:切面在目標類加載到JVM時被織入。這種方式須要特殊的類加載器(ClassLoader),它能夠在目標類被引入應用以前加強該目標類的字節碼。AspectJ 5的加載時織入(load-time weaving,LTW)就支持以這種方式織入切面。
運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象。代理類封裝了目標類,並攔截被通知方法的調用,再把調用轉發給真正的目標bean。SpringAOP就是以這種方式織入切面的。測試

三、AspectJ的切點表達式語言

 

 

注意:只有execution指示器是實際執行匹配的,而其餘的指示器都是用來限制匹配的。這說execution指示器是咱們在編寫切點定義時最主要使用的指示器 。同時須要注意的是, 表達式之間容許用 &&(and)、||(or)、!(not) 來匹配複雜的被通知類。除了上面羅列的表達式外,Spring 還提供了一個Bean 表達式來匹配 Bean 的id,例如  execution(* com.service.Performance.perform(..)) && bean(performance)

@args的正確用法:自定義一個ElementType.TYPE的註解,這個註解用來修飾自定義類型(好比本身寫的一個類),一個方法以這個自定義的類的實例爲參數且只能有這惟一一參數,那這個方法在調用時會被匹配@args(自定義註解)的切面攔截。
@annotation的正確用法:在切面類上用@annotation加自定義註解就能夠攔截使用這個註解的方法。好比匹配 @RequestMapping  註解的類  @annotation(org.springframework.web.bind.annotation.RequestMapping)

@target (cn.javass.spring.chapter6.Secure)  任何目標對象持有Secure註解的類方法;必須是在目標對象上聲明這個註解,在接口上聲明的對它不起做用。

2、使用註解建立切面

一、添加pom.xml依賴

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.11</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.11</version>
        </dependency>
View Code

二、定義切面

@Aspect //表示這是一個切面類
public class Audience {

    //使用簡明的PointCut
    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}
    
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    //前置通知 即 @Before("execution(* com.service.Performance.perform(..))")
    @Before("performance()")
    public void takeSeats(){
        System.out.println("Taking seats");
    }

    //方法調用結束通知(並非指返回值通知,即便是void的返回值,仍然會觸發通知) 即 @AfterReturning("execution(* com.service.Performance.perform(..))")
    @AfterReturning("performance()")
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }

    //有異常拋出的時候通知,即 @AfterThrowing("execution(* com.service.Performance.perform(..))")
    @AfterThrowing("performance()")
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}
View Code

三、啓用AspectJ註解的自動代理 

有兩種方式能夠啓用AspectJ 註解的自動代理:

(1)在 Java 配置文件中顯示配置

@Configuration
@EnableAspectJAutoProxy //啓用Aop自動代理
public class JavaConfig {
    @Bean
    public Audience getAudience(){
        return new Audience();
    }
}
View Code

(2)在XML文件中配置

    <!--啓用AspectJ自動代理-->
    <aop:aspectj-autoproxy/>
    <bean id="audience" class="com.aspect.Audience"/>
View Code

無論你是使用JavaConfig仍是XMLAspectJ自動代理都會爲使用@Aspect註解的bean建立一個代理,這個代理會圍繞着全部該切面的切點所匹配的bean。當程序執行到鏈接點的時候,就會由代理轉到切面觸發相應的通知。

四、建立環繞通知

@Aspect
public class Audience3 {

    @Pointcut("execution(* com.service.Performance.perform(..))")
    public void performance(){}

    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}
View Code

 注意 ProceedingJoinPoint 做爲參數。這個對象是必需要有的,由於你要在通知中經過它來調用被通知的方法。當要將控制權交給被通知的方法時,它須要調用ProceedingJoinPointproceed()方法。 

五、切面匹配輸入參數

@Aspect
public class TrackCounter {
    
    private Map<Integer,Integer> trackCounts=new HashMap<Integer, Integer>();

    //@Pointcut("execution(* com.service.CompactDisc.playTrack(int)) && args(trackNumber)") //帶入輸入參數
    //@Pointcut("target(com.service.CompactDisc) && args(trackNumber)") // target 匹配目標對象(非AOP對象)爲指定類型
    //@Pointcut("within(com.service..*) && args(trackNumber)") //com.service 包以及子包下的全部方法都執行
    //@Pointcut("within(com.service..CompactDisc+) && args(trackNumber)") //com.service 包的CompactDisc類型以及子類型
     @Pointcut("this(com.service.CompactDisc) && args(trackNumber)") //匹配當前AOP代理對象類型,必須是類型全稱,不支持通配符
     public void trackPlayed(int trackNumber){}

    @Before("trackPlayed(trackNumber)")
    public void countTrack(int trackNumber){
        int playCount = getPlayCount(trackNumber);
        trackCounts.put(trackNumber,playCount+1);
        System.out.println(trackCounts.toString());
    }

    public int getPlayCount(int trackNumber){
        return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
    }
}
View Code
 參數的配置能夠用佔位符 *.. 
  * 的意思是任意類型任意名稱的一個參數
 .. 的意思是任意類型,任意多個參數,而且只能放到args的後面。 

六、利用切面注入新功能

    Java並非動態語言。一旦類編譯完成了,咱們就很難爲該類添加新的功能了。可是,咱們的切面編程卻能夠作到動態的添加方法...話雖如此,其實也不過是障眼法罷了。實際上,面向切面編程,不過是把方法添加到切面代理中,當要對添加的方法調用的時候,能夠把被通知的 Bean 轉換成相應的接口。也就是代理會把此調用委託給實現了新接口的某個其餘對象。實際上,一個bean的實現被拆分到了多個類中。(說實話,想了半天,實在想不到這個功能有什麼做用......)

(1) 從新定義一個接口和實現類

public interface Encoreable {
    void performEncode();
}
View Code
public class DefaultEncoreable implements Encoreable {
    
    public void performEncode() {
        System.out.println("this is DefaultEncoreable");
    }
}
View Code

(2) 把接口實現類嵌入到目標類代理中

@Aspect
public class EncoreableIntroducer {
    
    @DeclareParents(value = "com.service.CompactDisc+",
              defaultImpl = DefaultEncoreable.class) //value 表示要嵌入哪些目標類的代理 。 defaultImpl:表示要嵌入的接口的默認實現方法
    public static Encoreable encoreable;
}
View Code

(3) JUnit 測試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test02 {

    @Autowired
    private CompactDisc compactDisc;
    
    @Test
    public void test02(){
        compactDisc.playTrack(123);
        Encoreable compactDisc = (Encoreable) this.compactDisc; //當要調用添加的新功能的時候,這個用法至關於由代理轉換到對應類實現,不會報類型轉換錯誤
        compactDisc.performEncode();

    }
}
View Code

 

3、使用XML聲明切面

 

一、定義切面

public class AudienceXML {
    
    public void  silenceCellPhones(){
        System.out.println("Silencing cell phones");
    }
    public void takeSeats(){
        System.out.println("Taking seats");
    }
    public void applause(){
        System.out.println("CLAP CLAP CLAP!!!");
    }
    public void demandRefund(){
        System.out.println("Demanding a refund");
    }
}
View Code

二、XML配置切面

    <aop:config>
        <aop:aspect ref="audienceXML">
            <aop:pointcut id="performance" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:before method="silenceCellPhones" pointcut-ref="performance"/>
            <aop:before method="takeSeats" pointcut-ref="performance"/>
            <aop:after-returning method="applause" pointcut-ref="performance"/>
            <aop:after-throwing method="demandRefund" pointcut-ref="performance"/>
        </aop:aspect>
    </aop:config>
View Code

三、建立環繞通知

public class Audience3XML {
    
    public void watchPerformance(ProceedingJoinPoint joinPoint) {
        System.out.println("Silencing cell phones");
        System.out.println("Taking seats");
        try {
            joinPoint.proceed();
            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
            throwable.printStackTrace();
        }
    }
}
View Code
    <aop:config>
        <aop:aspect ref="audience3XML">
            <aop:pointcut id="performance3" expression="execution(* com.service.Performance.perform(..))"/>
            <aop:around method="watchPerformance" pointcut-ref="performance3"/>
        </aop:aspect>
    </aop:config>
View Code

四、匹配輸入參數

    <aop:config>
        <aop:aspect ref="trackCounter">
            <aop:pointcut id="trackPlayed" expression="execution(* com.service.CompactDisc.playTrack(int)) and args(trackNumber)"/>
            <aop:before method="countTrack" pointcut-ref="trackPlayed"/>
        </aop:aspect>
    </aop:config>
View Code

五、注入新功能

    <aop:config>
        <aop:aspect>
            <aop:declare-parents types-matching="com.service.CompactDisc+"
                                 implement-interface="com.service.Encoreable"
                                 default-impl="com.service.impl.DefaultEncoreable"
                                 delegate-ref="encoreableDelegate"/>

        </aop:aspect>
    </aop:config>
View Code
相關文章
相關標籤/搜索