面向切面的Spring

前言

在軟件開發中,散佈於應用中多處的功能被稱爲橫切關注點。這些橫切關注點從概念上是與應用的業務邏輯相分離的。把這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP)所要解決的問題。AspectJ---另外一種流行的AOP實現。 若是是要重用通用功能的話,最多見的面向對象技術是繼承或委託。可是,若是在整個應用中都使用相同的基類,繼承每每會致使一個脆弱的對象體系;而使用委託可能須要對委託對象進行復雜的調用。正則表達式

AOP

通知(Advice):定義了切面是什麼以及什麼時候使用。除了描述切面要完成的工做,通知還解決了什麼時候執行這個工做的問題。spring

Spring切面能夠應用五種類型的通知:express

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

鏈接點(Join Point):在用於執行過程當中可以插入切面的一個點。切面代碼能夠利用這些點插入到應用的正常流程中,並添加新的行爲。編程

切點(PointCut):切點的定義會匹配通知全部織入的一個或多個鏈接點。一般使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。maven

切面(Aspect):切面是通知和切點的結合,通知和切點共同定義了切面的所有內容--它是什麼,在什麼時候和何處完成其功能。ide

引入(Introduction):引入容許咱們向現有的類添加新方法或屬性。post

織入(Weaving):織入是把切面應用到目標對象並建立新的代理對象的過程。切面在指定的鏈接點被織入到目標對象中。在目標對象的生命週期裏有多個點能夠進行織入:ui

  1. 編譯期:切面在目標類編譯時被織入,這種方式須要特殊的編譯器。AspectJ的織入編譯器就是以這種方式織入切面的。

2.類加載期:切面在目標類加載到JVM時被織入。這種方式須要特殊的類加載器(ClssLoader),他能夠在目標類被引入應用以前加強該目標類的字節碼。this

3.運行期:切面在應用運行的某個時刻被織入。通常狀況下,在織入切面時,AOP容器會爲目標對象動態地建立一個代理對象。spa

Spring對AOP的支持

Spring提供了4中類型的AOP支持。 1.基於代理的經典SpringAOP。

2.純POJO切面。

3.@AspectJ註解驅動的切面。

4.注入式AspectJ切面

經過切點來選擇鏈接點

1.編寫切點

首先:定義一個HelloWorld接口:

public interface HelloWorld
{
    void printHelloWorld();
    void doPrint();
}

切點的配置

execution(* com.xrq.aop.HelloWorld.(..)) 切點表達式。execution 在方法執行時觸發, com.xrq.aop.HelloWorld.(..) 篩選制定的方法。 com.xrq.aop.HelloWorld.(..)) 是鏈接點。??

<aop:config>
            <aop:aspect id="time" ref="timeHandler">
                <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" />
                <aop:before method="printTime" pointcut-ref="addAllMethod" />
                <aop:after method="printTime" pointcut-ref="addAllMethod" />
            </aop:aspect>
        </aop:config>

2.使用註解建立切面

使用@AspectJ註解進行了標註,該註解代表 Advices 不只是一個POJO類,也是一個切面。 使用@PoinCut定義命名的切點,能夠簡化重複的代碼。

package com.zhangguo.Spring052.aop02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

/**
 * 通知類,橫切邏輯
 *
 */
@Component
@Aspect
public class Advices {
    @Before("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void before(JoinPoint jp){
        System.out.println("----------前置通知----------");
        System.out.println(jp.getSignature().getName());
    }
    
    @After("execution(* com.zhangguo.Spring052.aop02.Math.*(..))")
    public void after(JoinPoint jp){
        System.out.println("----------最終通知----------");
    }
}

2.JavaConfig啓用切面的代理 在配置類的級別上經過使用@EnableAspectJAutoProxy 註解啓動代理功能。

package com.zhangguo.Spring052.aop05;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration  //用於表示當前類爲容器的配置類,相似<beans/>
@ComponentScan(basePackages="com.zhangguo.Spring052.aop05")  //掃描的範圍,至關於xml配置的結點<context:component-scan/>
@EnableAspectJAutoProxy(proxyTargetClass=true)  //自動代理,至關於<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
public class ApplicationCfg {
    //在配置中聲明一個bean,至關於<bean id=getUser class="com.zhangguo.Spring052.aop05.User"/>
    @Bean
    public User getUser(){
        return new User();
    }
}

3.xml裝配Bean,啓動註解: aspectj-autoproxy 元素聲明aop的啓動。

<?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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
        <context:component-scan base-package="com.zhangguo.Spring052.aop02">
        </context:component-scan>
        <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>

環繞通知 Around

環繞通知接受 ProceedingJoinPoint做爲參數。這個對象是必需要有的,由於你要在通知中經過它來調用被通知的方法。通知方法中能夠作任何的事情,當要將控制權交給被通知的方法時,它須要調用ProceedingJoinPoint的Proceed()方法。

若是忘記調用Proceed()方法,通知實際上會阻塞對被通知方法的調用。也能夠在通知中對它進行多長調用,實現充實邏輯。

package com.zhangguo.Spring052.aop04;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 通知類,橫切邏輯
 */
@Component
@Aspect
public class Advices {
    //切點
    @Pointcut("execution(* com.zhangguo.Spring052.aop04.Math.a*(..))")
    public void pointcut(){
    }
    
    //前置通知
    @Before("pointcut()")
    public void before(JoinPoint jp){
        System.out.println(jp.getSignature().getName());
        System.out.println("----------前置通知----------");
    }
    
    //最終通知
    @After("pointcut()")
    public void after(JoinPoint jp){
        System.out.println("----------最終通知----------");
    }
    
    //環繞通知
    @Around("execution(* com.zhangguo.Spring052.aop04.Math.s*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println(pjp.getSignature().getName());
        System.out.println("----------環繞前置----------");
        Object result=pjp.proceed();
        System.out.println("----------環繞後置----------");
        return result;
    }
    
    //返回結果通知
    @AfterReturning(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.m*(..))",returning="result")
    public void afterReturning(JoinPoint jp,Object result){
        System.out.println(jp.getSignature().getName());
        System.out.println("結果是:"+result);
        System.out.println("----------返回結果----------");
    }
    
    //異常後通知
    @AfterThrowing(pointcut="execution(* com.zhangguo.Spring052.aop04.Math.d*(..))",throwing="exp")
    public void afterThrowing(JoinPoint jp,Exception exp){
        System.out.println(jp.getSignature().getName());
        System.out.println("異常消息:"+exp.getMessage());
        System.out.println("----------異常通知----------");
    }
}

4 經過註解引入新功能

@DeclareParents註解:

  • value 屬性指定了那種類型的bean要引入該接口。
  • DefaultImpl 屬性指定了爲引入功能提供實現的類。
  • @DeclareParents註解所標註的靜態屬性指明瞭要引入的接口。

面向註解的切面聲明有一個明顯的劣勢:你必須可以爲通知類添加註解。若是你沒有源碼的話,或者不想將AspectJ註解放入到代碼中,SpringXml能夠實現。

4.4 在xml中聲明切面

優先的原則:基於註解的配置要優於基於Java的配置,基於Java的配置要優於基於XMl的配置。 基於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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
       
    <!-- 被代理對象 -->
    <bean id="math" class="com.zhangguo.Spring052.aop01.Math"></bean>
    
    <!-- 通知 -->
    <bean id="advices" class="com.zhangguo.Spring052.aop01.Advices"></bean>
    
    <!-- aop配置 -->
    <aop:config proxy-target-class="true">
        <!--切面 -->
        <aop:aspect ref="advices">
            <!-- 切點 -->
            <aop:pointcut expression="execution(* com.zhangguo.Spring052.aop01.Math.*(..))" id="pointcut1"/>
            <!--鏈接通知方法與切點 -->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <aop:after method="after" pointcut-ref="pointcut1"/>
        </aop:aspect>
    </aop:config>

</beans>

注入AspectJ切面

當咱們須要建立更細粒度的通知或想監測bean的建立時,Spring所支持的AOP就比較弱了,這時,能夠選擇使用AspectJ提供的構造器切點,而且能夠藉助Spring的依賴注入把bean裝配進AspectJ切面中。下面就來舉個栗子:

//定義表演接口
package concert;
public interface Performance {
    void perform(); 

    void finishPerform(String performer, String title); 
}
//定義鋼琴表演
package concert;
public class PianoPerform implements Performance {
    public PianoPerform(){
        System.out.println("有請鋼琴表演");
    }

    @Override
    public void perform() {
        System.out.println("鋼琴表演開始");
    }

    @Override
    public void finishPerform(String performer, String title) {
        System.out.println(performer + "演奏鋼琴曲:" + title);
    }
}
//定義小提琴表演
package concert;
public class ViolinPerform implements Performance {
    public ViolinPerform(){
        System.out.println("有請小提琴表演");
    }

    @Override
    public void perform() {
        System.out.println("小提琴表演開始");
    }

    @Override
    public void finishPerform(String performer, String title){
        System.out.println(performer + "演奏了小提琴曲:" + title);
    }   
}
//定義工做人員,將做爲切面的協做bean
package concert;
public class Worker {
    public void take(){
        System.out.println("觀衆已所有交出手機");
    }

    public void sendMsg(String name){
        System.out.println(name + "表演即將開始,請各位觀衆交出手機");
    }

    public void broadcast(String performer, String title){
        System.out.println(performer + "演奏完畢,剛纔演奏的曲子叫:" + title);
    }
}
//定義切面
package concert;
public aspect Audience {    
    private Worker worker;

    public Audience(){}

    //經過setter方法注入
    public void setWorker(Worker worker){
        this.worker = worker;
        System.out.println("工做人員已入場");
    }

    //定義piano構造器切點和後置通知
    pointcut piano():execution(concert.PianoPerform.new());
    after():piano(){
        worker.sendMsg("鋼琴");
    }

    //定義violin構造器切點和後置通知
    pointcut violin():execution(concert.ViolinPerform.new());
    after():violin(){
        worker.sendMsg("小提琴");
    }

    //定義不帶參數方法切點和前置通知
    pointcut perform():execution(* concert.Performance.perform());
    before():perform(){
        worker.take();
    }

    //定義帶兩個參數的切點和後置通知
    pointcut finishPerform(String performer, String title):execution(* concert.Performance.finishPerform(String, String)) && args(performer, title);
    after(String performer, String title):finishPerform(performer, title){
        worker.broadcast(performer, title);
    }
}

XML配置文件:spring.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--<bean id="piano" class="concert.PianoPerform" lazy-init="true"/>-->

    <bean id="worker" class="concert.Worker"/>

    <!--Spring須要經過靜態方法aspectOf得到audience實例,Audience切面編譯後的class文件附在文末-->
    <bean class="concert.Audience" factory-method="aspectOf">
        <property name="worker" ref="worker" /><!--經過Spring把協做的bean注入到切面中-->
    </bean>

    <!--這裏注意一下bean的順序,由於在構造器切點後置通知時調用了worker的sendMsg(String)方法,因此避免出現空指針異常,我們先把worker聲明在前-->
    <!--若是要將piano或者violin聲明在前,能夠設置lazy-init="true"-->
    <!--因此spring是從上到下解析並實例化bean?仍是解析完整個文件再實例化呢?歡迎評論區留言交流-->

    <bean id="piano" class="concert.PianoPerform"/>

    <bean id="violin" class="concert.ViolinPerform"/>
</beans>
//主程序
package concert;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainClass {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");

        Performance piano = context.getBean("piano", Performance.class);
        piano.perform();
        piano.finishPerform("亞莎·海菲茲", "致愛麗斯");

        Performance violin = context.getBean("violin", Performance.class);
        violin.perform();
        violin.finishPerform("霍洛維茨", "愛之喜悅");
    }

}
<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>       
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.8.RELEASE</version>
        </dependency>        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
        <!--編譯aspect的插件,必需要將Audience.aj編譯爲class文件,
            否則spring建立audience bean的時候找不到類-->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.8</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <complianceLevel>1.8</complianceLevel>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

Audience.class文件

package concert;

import concert.Worker;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class Audience {
    private Worker worker;

    static {
        try {
            ajc$postClinit();
        } catch (Throwable var1) {
            ajc$initFailureCause = var1;
        }

    }

    public void setWorker(Worker worker) {
        this.worker = worker;
        System.out.println("工做人員已入場");
    }

    public Audience() {
    }

    @After(
        value = "piano()",
        argNames = ""
    )
    public void ajc$after$concert_Audience$1$dd71540a() {
        this.worker.sendMsg("鋼琴");
    }

    @After(
        value = "violin()",
        argNames = ""
    )
    public void ajc$after$concert_Audience$2$57c630b6() {
        this.worker.sendMsg("小提琴");
    }

    @Before(
        value = "perform()",
        argNames = ""
    )
    public void ajc$before$concert_Audience$3$1cad9822() {
        this.worker.take();
    }

    @After(
        value = "finishPerform(performer, title)",
        argNames = "performer,title"
    )
    public void ajc$after$concert_Audience$4$1840cdb9(String performer, String title) {
        this.worker.broadcast(performer, title);
    }

    //Aspect提供的靜態方法,返回Audience切面的一個實例,Spring便可經過factory-method屬性得到該實例
    //<bean class="concert.Audience" factory-method="aspectOf">
    public static Audience aspectOf() {
        if(ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("concert_Audience", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }

    public static boolean hasAspect() {
        return ajc$perSingletonInstance != null;
    }
}

寫在最後: Spring所支持的AOP已經能夠知足不少需求,若是要求更高,可使用AspectJ提供的更豐富的切點類型,固然須要熟悉AspectJ語法。 本文只是簡單的舉出了部分切點類型和通知類型,更多的類型讀者能夠自行嘗試。歡迎留言指正,感謝您的閱讀!

相關文章
相關標籤/搜索