1. 程式人生 > >spring 框架之Aop

spring 框架之Aop

Spring對AOP的支援

  • 基於代理的經典AOP;

  • @AspectJ註解驅動的切面;

  • 純POJO切面;

  • 注入式AspectJ切面(適合Spring個版本);

Spring是在執行期將切面織入到所管理的Bean中的,如圖所示,代理類封裝了目標類,當攔截到方法呼叫時,在呼叫目標Bean的方法之前,代理會執行切面邏輯。真正應用需要被代理的Bean時,Spring才會建立代理物件。Spring的切面由包裹了目標物件的代理類實現,代理類處理方法的呼叫,執行額外的切面邏輯,並呼叫目標方法。

image

Spring的切面由包裹了目標物件的代理類實現,代理類處理方法的呼叫,執行額外的切面邏輯,並呼叫目標方法。

Spring只支援方法連線點,缺少對欄位連線點的支援,例如攔截物件欄位的修改。也不支援構造器連線點,也就無法在Bean建立時應用通知。

使用切點選擇連線點

Spring AOP中,需要使用AspectJ的切點表示式來定義切點。

AspectJ指示器 描述
arg() 限制連線點匹配引數為指定型別的執行方法
@args() 限制連線點匹配引數由指定註解標註的執行方法
execution() 用於匹配是連線點的執行方法
this() 限制連線點匹配AOP代理的Bean引用為指定型別的類
target() 限制連線點匹配目標物件為執行型別的類
@target() 限制連線點匹配特定的執行物件,這些物件對應的類要具備指定型別的註解
within() 限制連線點匹配指定的型別
@within() 限制連線點匹配指定註解所標註的型別
@annotation() 限制匹配帶有指定註解連線點

編寫切點

image

使用AspectJ切點表示式來定位

這裡使用了execution()指示器來選擇Instrument的play()方法。表示式以*開頭表示不關心返回值的型別,然後指定了全限定類名和方法名,使用..作為方法的引數列表,表示可以是任意的入參。

使用&&將execution()和within()進行連線,那麼也就可以使用||(或)和!(非)。

image

使用within()指示器限制切點範圍

使用Spring的bean()指示器

bean()使用Bean id來作為引數,從而限制切點只匹配特定的Bean,如:

execution(* com.springinaction.springidol.Instrument.play()) and bean(eddie)

這裡,表示在執行Instrument的play()方法時應用通知,但限定Bean的id為eddie。

在XML中宣告切面

AOP配置元素 描述
<aop:advisor> 定義AOP通知器
<aop:after> 定義AOP後置通知(不管該方法是否執行成功)
<aop:after-returning> 在方法成功執行後呼叫通知
<aop:after-throwing> 在方法丟擲異常後呼叫通知
<aop:around> 定義AOP環繞通知
<aop:aspect> 定義切面
<aop:aspect-autoproxy> 定義@AspectJ註解驅動的切面
<aop:before> 定義AOP前置通知
<aop:config> 頂層的AOP配置元素,大多數的<aop:*>包含在<aop:config>元素內
<aop:declare-parent> 為被通知的物件引入額外的介面,並透明的實現
<aop:pointcut> 定義切點

所需jar包:

無標題.png

表演者介面:

public interface Performer {

    void perform();

}

歌唱家類:

public class Instrumentalist implements Performer {

    private String song;

    private Instrument instrument;

    public Instrumentalist() {

    }

    public void perform() {

        System.out.print("Playing " + song + " : ");

        instrument.play();

    }

    public void setSong(String song) { // 注入歌曲

        this.song = song;

    }

    public String getSong() {

        return song;

    }

    public String screamSong() {

        return song;

    }

    public void setInstrument(Instrument instrument) { // 注入樂器

        this.instrument = instrument;

    }

}

樂器介面:

public interface Instrument {

    public void play();

}

吉他類:

public class Guitar implements Instrument {

    public void play() {

        System.out.println("Strum strum strum");

    }

}

下面定義一個觀眾類:

public class Audience {    // 表演之前
    public void takeSeats() {
        System.out.println("The audience is taking their seats.");
    }    // 表演之前
    public void turnOffCellPhones() {
        System.out.println("The audience is turning off their cellphones");
    }    // 表演之後
    public void applaud() {
        System.out.println("CLAP CLAP CLAP CLAP CLAP");
    }    // 表演失敗之後
    public void demandRefund() {
        System.out.println("Boo! We want our money back!");
    }
}

宣告前置和後置通知

<?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: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.xsd">

    <bean id="eddie" class="com.springinaction.springidol.Instrumentalist">
        <property name="instrument">
            <bean class="com.springinaction.springidol.Guitar" />
        </property>
        <property name="song" value="my love" />
    </bean>

    <bean id="audience" class="com.springinaction.springidol.Audience" />

    <aop:config>
        <aop:aspect ref="audience"><!-- 引用audience Bean -->
            <!-- 宣告切入點 -->
            <aop:pointcut id="performance"
                expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
            <!-- 表演之前 -->
            <aop:before pointcut-ref="performance" method="takeSeats" />
            <aop:before pointcut-ref="performance" method="turnOffCellPhones" />
            <!-- 表演之後 -->
            <aop:after-returning pointcut-ref="performance"
                method="applaud" />
            <!-- 表演失敗之後 -->
            <aop:after-throwing pointcut-ref="performance"
                method="demandRefund" />
        </aop:aspect>
    </aop:config></beans>

<aop:config>中,可以宣告一個或多個通知器、切面或者切點。pointcut屬性定義了通知所引用的切點。最終的通知邏輯如何織入到業務邏輯中:

image

Audience切面包含4中通知,這些通知把通知=邏輯織入到匹配的切面的切點方法中

測試程式碼:

@Test
    public void testBeforeAndAfter() throws PerformanceException{
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-idol.xml");
        Performer performer = (Performer) context.getBean("eddie");
        performer.perform();
    }

測試結果:

The audience is taking their seats.
The audience is turning off their cellphones
Playing my love : Guitar Guitar Guitar
CLAP CLAP CLAP CLAP CLAP

宣告環繞通知

前置通知和後置通知之間共享訊息需要使用成員變數,而Audience是單例,使用成員變數有可能存線上程安全問題。使用環繞通知可以完成之前前置和後置所實現的相同功能,而且只需一個方法。

package com.springinaction.springidol;
import org.aspectj.lang.ProceedingJoinPoint;
public class AroundAudience {    
public void watchPerformance(ProceedingJoinPoint joinpoint) {        
try {            // 表演之前
            System.out.println("The audience is taking their seats.");
            System.out.println("The audience is turning off their cellphones");            
            long start = System.currentTimeMillis();            // 執行被通知的方法
            joinpoint.proceed();            // 表演之後
            long end = System.currentTimeMillis();
            System.out.println("CLAP CLAP CLAP CLAP CLAP");
            System.out.println("The performance took " + (end - start) + " milliseconds.");
        } catch (Throwable t) {            // 表演失敗之後
            System.out.println("Boo! We want our money back!");
        }
    }
}

ProceedingJoinPoint作為入參,從而可以在通知裡呼叫被通知的方法。

XML配置:

<bean id="audience" class="com.springinaction.springidol.AroundAudience" />
<aop:config>
    <aop:aspect ref="audience"><!-- 引用audience Bean -->
        <!-- 宣告切入點 -->
        <aop:pointcut id="performance"
            expression="execution(* com.springinaction.springidol.Performer.perform(..))" />
        <aop:around method="watchPerformance" pointcut-ref="performance" />
    </aop:aspect>
    </aop:config></pre>

一個實際例子---日誌記錄

AspectJ使用org.aspectj.lang.JoinPoint介面表示目標類連線點物件,如果是環繞增強時,使用org.aspectj.lang.ProceedingJoinPoint表示連線點物件,該類是JoinPoint的子介面。任何一個增強方法都可以通過將第一個入參宣告為JoinPoint訪問到連線點上下文的資訊。我們先來了解一下這兩個介面的主要方法:
1)JoinPoint
 java.lang.Object[] getArgs():獲取連線點方法執行時的入參列表;
 Signature getSignature() :獲取連線點的方法簽名物件;
 java.lang.Object getTarget() :獲取連線點所在的目標物件;
 java.lang.Object getThis() :獲取代理物件本身;
2)ProceedingJoinPoint
ProceedingJoinPoint繼承JoinPoint子介面,它新增了個用於執行連線點方法的方法:
 java.lang.Object proceed() throws java.lang.Throwable:通過反射執行目標物件的連線點處的方法;

二、程式碼演示。

SecurityHandler.java

package com.tgb.spring;  

import org.aspectj.lang.JoinPoint;  

public class SecurityHandler{  

    private void checkSecurity(JoinPoint joinPoint){  

        for (int i = 0; i < joinPoint.getArgs().length; i++) {  

            System.out.println(joinPoint.getArgs()[i]);  

        }  

        System.out.println(joinPoint.getSignature().getName());  
        System.out.println("=====checkSecurity====");  
    }  
}  

Client.java

package com.tgb.spring;  

  

import org.springframework.beans.factory.BeanFactory;  

import org.springframework.context.support.ClassPathXmlApplicationContext;  

  

import com.tgb.spring.UserManager;  

  

  

  

public class Client {  

  

    public static void main(String[] args) {  

    BeanFactory factory=new ClassPathXmlApplicationContext("applicationContext.xml");  

        UserManager userManager=(UserManager) factory.getBean("userManager");  

        userManager.addUser("張三", "123");  

        //userManager.delUser(1);  

          

    }  

}  

UserManager.java

package com.tgb.spring;  

  

public interface UserManager {  

      

    public void addUser(String username,String password);  

      

    public void delUser(int userId);  

      

    public String findUserById(int userId);  

      

    public void modifyUser(int userId,String username,String password);  

      

}  

UserManagerImpl.java

package com.tgb.spring;  

  

public class UserManagerImpl implements UserManager {  

  

    public void addUser(String username, String password) {  

        //checkSecurity();  

            System.out.println("===UserManager.addUser===");  

  

    }  

  

    public void delUser(int userId) {  

        //checkSecurity();  

        System.out.println("===UserManager.delUser===");  

  

    }  

  

    public String findUserById(int userId) {  

        //checkSecurity();  

        System.out.println("===UserManager.findUserById===");  

    return  "張三";  

    }  

  

    public void modifyUser(int userId, String username, String password) {  

        //checkSecurity();  

        System.out.println("===UserManager.modifyUser===");  

  

    }  

      

//  private void checkSecurity(){  

//      System.out.println("checkSecurity");  

//  

//  }  

  

}  


applicationContext.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:aop="http://www.springframework.org/schema/aop"  

         xmlns:tx="http://www.springframework.org/schema/tx"  

         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  

           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd  

           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  

  

  

<bean id="userManager" class="com.tgb.spring.UserManagerImpl" />  

<bean id="securityHandler" class="com.tgb.spring.SecurityHandler"/>  

<aop:config>  

    <aop:aspect id="securityAspect" ref="securityHandler">  

  

         <aop:pointcut id="addAddMethod" expression="execution(* com.tgb.spring.*.*(..))" />  

        <aop:before method="checkSecurity" pointcut-ref="addAddMethod" />  

    </aop:aspect>  

</aop:config>  

</beans>