1. 程式人生 > >spring之旅第五篇-AOP詳解

spring之旅第五篇-AOP詳解

string類型 ets 的區別 aspectj version lap 賦值 :after 代理

一、什麽是AOP?

Aspect oritention programming(面向切面編程),AOP是一種思想,高度概括的話是“橫向重復,縱向抽取”,如何理解呢?舉個例子:訪問頁面時需要權限認證,如果每個頁面都去實現方法顯然是不合適的,這個時候我們就可以利用切面編程。

每個頁面都去實現這個方法就是橫向的重復,我們直接從中切入,封裝一個與主業務無關的權限驗證的公共方法,這樣可以減少系統的重復代碼,降低模塊之間的耦合度,簡單的示意圖如下:

技術分享圖片

二、應用場景

AOP用來封裝橫切關註點,具體可以在下面的場景中使用:

Authentication 權限

Caching 緩存

Context passing 內容傳遞

Error handling 錯誤處理

Lazy loading 懶加載

Debugging  調試 l

ogging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 性能優化

Persistence  持久化

Resource pooling 資源池

Synchronization 同步 T

ransactions 事務

三、相關概念

1.連接點(Joinpoint) 所謂連接點是指那些可能被攔截到的方法。例如:所有可以增加的方法

2.切點(Pointcut) 已經被增強的連接點

3.增強(Advice) 增強的代碼

4.目標對象(Target) 目標類,需要被代理的類

5.織入(Weaving) 是指把增強advice應用到目標對象target來創建新的代理對象proxy的過程

6.代理(Proxy) 一個類被AOP織入增強後,就產生出了一個結果類,它是融合了原類和增強邏輯的代理類。

7.切面(Aspect)切入點+通知

通知類型:Spring按照通知Advice在目標類方法的連接點位置,可以分為5類

  • 前置通知 (在目標方法執行前實施增強)

  • 後置通知(在目標方法執行後實施增強)

  • 環繞通知(在目標方法執行前後實施增加)

  • 異常拋出通知(在方法跑出異常時通知)

  • 引介通知(在目標類中添加一些新的方法和屬性)

四、實現原理

AOP的實現關鍵在於AOP框架自動創建的AOP代理。AOP代理主要分為兩大類:

靜態代理:使用AOP框架提供的命令進行編譯,從而在編譯階段就可以生成AOP代理類,因此也稱為編譯時增強;靜態代理一Aspectj為代表。

動態代理:在運行時借助於JDK動態代理,CGLIB等在內存中臨時生成AOP動態代理類,因此也被稱為運行時增強,Spring AOP用的就是動態代理。

4.1 靜態代理

例子:在增加員工和刪除員工時增加事務處理

//員工類
public class Employee {
    private Integer uid;

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public Integer getUid() {

        return uid;
    }

    private Integer age;

    private String name;

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}

員工接口:

//員工接口
public interface EmployeeService {
    //新增方法
    void addEmployee(Employee employee);
    //刪除方法
    void deleteEmployee(Integer uid);
}

員工實現:

//員工方法實現
public class EmployeeServiceImpl implements EmployeeService {
    @Override
    public void addEmployee(Employee employee) {
        System.out.println("新增員工");
    }

    @Override
    public void deleteEmployee(Integer uid) {
        System.out.println("刪除員工");

    }
}

事務類:

//事務類
public class MyTransaction {
    //開啟事務
    public void before(){
        System.out.println("開啟事務");
    }
    //提交事務
    public void after(){
        System.out.println("提交事務");
    }
}

代理類:

//代理類
public class ProxyEmployee implements EmployeeService {
    //
    private EmployeeService employeeService;

    private MyTransaction myTransaction;

    public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction)
    {
       this.employeeService=employeeService;
       this.myTransaction=myTransaction;
    }
    @Override
    public void addEmployee(Employee employee) {
         myTransaction.before();
         employeeService.addEmployee(employee);
         myTransaction.after();
    }

    @Override
    public void deleteEmployee(Integer uid) {
         myTransaction.before();
         employeeService.deleteEmployee(uid);
         myTransaction.after();
    }
}

測試:

@Test
    public void fun1(){
        MyTransaction transaction = new MyTransaction();
        EmployeeService EmployeeService = new EmployeeServiceImpl();
        //產生靜態代理對象
        ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction);
        proxy.addEmployee(null);
        proxy.deleteEmployee(0);
    }

結果:

技術分享圖片

這是靜態代理的實現方式,靜態代理有明顯的缺點:

1、代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要為每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。

2、如果接口增加一個方法,比如 EmployeeService增加修改 updateEmployee()方法,則除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的復雜度。

4.2 動態代理

動態代理就不要自己手動生成代理類了,我們去掉 ProxyEmployee.java 類,增加一個 ObjectInterceptor.java 類

public class ObjectInterceptor implements InvocationHandler {

    //目標類
    private Object target;
    //切面類(這裏指事務類)
    private MyTransaction transaction;

    //通過構造器賦值
    public ObjectInterceptor(Object target,MyTransaction transaction){
        this.target = target;
        this.transaction = transaction;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        this.transaction.before();
        method.invoke(target,args);
        this.transaction.after();
        return  null;
    }
}

測試:

@Test
public void fun2(){
    //目標類
    Object target = new EmployeeServiceImpl ();
    //事務類
    MyTransaction transaction = new MyTransaction();
    ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
    /**
     * 三個參數的含義:
     * 1、目標類的類加載器
     * 2、目標類所有實現的接口
     * 3、攔截器
     */
    EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(), proxyObject);
    employeeService.addEmployee(null);
    employeeService.deleteEmployee(0);
}

結果:

技術分享圖片

五、spring的處理AOP的方式

spring 有兩種方式實現AOP的:一種是采用聲明的方式來實現(基於XML),一種是采用註解的方式來實現(基於AspectJ)

5.1 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:context="http://www.springframework.org/schema/context"
       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/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

     <!--目標類-->
     <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
     <bean name="transaction" class="com.yuanqinnan.aop.MyTransaction"></bean>
     <aop:config>
          <aop:aspect ref="transaction">
               <aop:pointcut id="pointcut" expression="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))"/>
               <!-- 配置前置通知,註意 method 的值要和 對應切面的類方法名稱相同 -->
               <aop:before method="before" pointcut-ref="pointcut"></aop:before>
               <!--配置後置通知,註意 method 的值要和 對應切面的類方法名稱相同-->
               <aop:after-returning method="after" pointcut-ref="pointcut"/>
          </aop:aspect>
     </aop:config>

</beans>

測試:

@Test
public void fun3(){
    ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    EmployeeService employeeService = (EmployeeService) context.getBean("employeeService");
    employeeService.addEmployee(null);
}

結果:

技術分享圖片

補充:

1.aop:pointcut如果位於aop:aspect元素中,則命名切點只能被當前aop:aspect內定義的元素訪問到,為了能被整個aop:config元素中定義的所有增強訪問,則必須在aop:config下定義切點。

2.如果在aop:config元素下直接定義aop:pointcut,必須保證aop:pointcutaop:aspect之前定義。aop:config下還可以定義aop:advisor,三者在aop:config中的配置有先後順序的要求:首先必須是aop:pointcut,然後是aop:advisor,最後是aop:aspect。而在aop:aspect中定義的aop:pointcut則沒有先後順序的要求,可以在任何位置定義。

aop:pointcut:用來定義切入點,該切入點可以重用;

aop:advisor:用來定義只有一個通知和一個切入點的切面;

aop:aspect:用來定義切面,該切面可以包含多個切入點和通知,而且標簽內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。

3.在使用spring框架配置AOP的時候,不管是通過XML配置文件還是註解的方式都需要定義pointcut"切入點" 例如定義切入點表達式 execution(* com.sample.service.impl...(..)) execution()是最常用的切點函數,其語法如下所示:

整個表達式可以分為五個部分: (1)、execution(): 表達式主體。

(2)、第一個號:表示返回類型,號表示所有的類型。

(3)、包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。

(4)、第二個號:表示類名,號表示所有的類。

(5)、(..):最後這個星號表示方法名,號表示所有的方法,後面括弧裏面表示方法的參數,兩個句點表示任何參數。

5.2 註解配置

新建註解類:

@Component
@Aspect
public class AopAspectJ {
    /**
     * 必須為final String類型的,註解裏要使用的變量只能是靜態常量類型的
     */
    public static final String EDP="execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))";

    /**
     * 切面的前置方法 即方法執行前攔截到的方法
     * 在目標方法執行之前的通知
     * @param jp
     */
    @Before(EDP)
    public void doBefore(JoinPoint jp){

        System.out.println("=========執行前置通知==========");
    }


    /**
     * 在方法正常執行通過之後執行的通知叫做返回通知
     * 可以返回到方法的返回值 在註解後加入returning
     * @param jp
     * @param result
     */
    @AfterReturning(value=EDP,returning="result")
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========執行後置通知============");
    }

    /**
     * 最終通知:目標方法調用之後執行的通知(無論目標方法是否出現異常均執行)
     * @param jp
     */
    @After(value=EDP)
    public void doAfter(JoinPoint jp){
        System.out.println("===========執行最終通知============");
    }

    /**
     * 環繞通知:目標方法調用前後執行的通知,可以在方法調用前後完成自定義的行為。
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(EDP)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("======執行環繞通知開始=========");
        // 調用方法的參數
        Object[] args = pjp.getArgs();
        // 調用的方法名
        String method = pjp.getSignature().getName();
        // 獲取目標對象
        Object target = pjp.getTarget();
        // 執行完方法的返回值
        // 調用proceed()方法,就會觸發切入點方法執行
        Object result=pjp.proceed();
        System.out.println("輸出,方法名:" + method + ";目標對象:" + target + ";返回值:" + result);
        System.out.println("======執行環繞通知結束=========");
        return result;
    }

    /**
     * 在目標方法非正常執行完成, 拋出異常的時候會走此方法
     * @param jp
     * @param ex
     */
    @AfterThrowing(value=EDP,throwing="ex")
    public void doAfterThrowing(JoinPoint jp,Exception ex) {
        System.out.println("===========執行異常通知============");
    }
}

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:context="http://www.springframework.org/schema/context"
       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/context
         http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.yuanqinnan.aop" ></context:component-scan>
    <!-- 聲明spring對@AspectJ的支持 -->
    <aop:aspectj-autoproxy/>
    <!--目標類-->
    <bean name="employeeService" class="com.yuanqinnan.aop.EmployeeServiceImpl"></bean>
</beans>

測試:

@Test
public void fun4(){
    ApplicationContext act =  new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    EmployeeService employeeService = (EmployeeService) act.getBean("employeeService");
    employeeService.addEmployee(null);
}

結果:

技術分享圖片

pringAOP的知識就總結到這裏

spring之旅第五篇-AOP詳解