圖文結合分析Spring的面向切面編程--AOP

Spring還可以這麼學–AOP

上一篇文章Spring還可以這麼學–IoC(控制反轉) / DI(依賴注入)理解

1. 什麼是AOP?

AOP(Aspect Oriented Programming),即面向切面編程,可以說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。

2. AOP的作用?

利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減少系統的重複代碼,降低模塊之間的耦合度,並有利於未來的可操作性和可維護性。

首先我們來看沒有AOP時,如果我們要做日誌處理,就得在每個方法中添加

這裏寫圖片描述

但大多數日誌處理代碼都是相同的,所以我們將日誌處理抽離成一個新的方法,但是儘管這樣,我們還得手動插入這些方法。

這裏寫圖片描述

但這樣代碼的耦合度很高,當我們要更改這個功能時,就得一個個更改

使用AOP後

這裏寫圖片描述

爲了在指定位置執行這些橫向的功能,需要知道指定的是什麼地方

這裏寫圖片描述

例如上圖,方法級別的aop實現,在一個程序執行鏈條中,把method2稱爲切點,也就是說在method2執行時會執行橫切的功能,那麼是在method2之前還是之後呢,又是執行什麼呢?這些都由advice(通知)來指定。
Spring 方面可以使用下面提到的五種通知工作:

這裏寫圖片描述

3. AOP的核心概念

  • 橫切關注點:對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點
  • 切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象
  • 連接點(joinpoint):被攔截到的點,因爲Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器
  • 切入點(pointcut):對連接點進行攔截的定義
  • 通知(advice):所謂通知指的就是指攔截到連接點之後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類
  • 目標對象:代理的目標對象
  • 織入(weave):將切面應用到目標對象並導致代理對象創建的過程
  • 引入(introduction):在不修改代碼的前提下,引入可以在運行期爲類動態地添加一些方法或字段
    ###4. 實現方式
    ####Spring AOP的XML實現方式
    Employee.java文件
package com.wangc;

public class Employee {
    private String name;
    private int age;
    public String getName() {
        System.out.println("name = "+name);
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        System.out.println("age = "+age);
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void printThrowException() {
        System.out.println("發生異常");
        throw new IllegalArgumentException();
    }
}

Logging.java文件

package com.wangc;

public class Logging {
    //在一個方法執行之前,執行通知。
    public void beforeAdvice(){
        System.out.println("執行employee的方法之前執行.");
    }
    //在一個方法執行之後,不考慮其結果,執行通知。
    public void afterAdvice(){
        System.out.println("執行employee的方法之後執行.");
    }
    //在一個方法執行之後,只有在方法成功完成時,才能執行通知。
    public void afterReturningAdvice(Object retVal){
        System.out.println("返回:" + retVal.toString() );
    }
    //在一個方法執行之後,只有在方法退出拋出異常時,才能執行通知。
    public void AfterThrowingAdvice(IllegalArgumentException ex){
        System.out.println("拋出了一個異常: " + ex.toString());   
    }  
}

Beans.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

   <aop:config>
      <!-- 聲明一個aspect,logging將被依賴注入 -->
      <aop:aspect id="log" ref="logging">
         <!-- 聲明一個切入點 ,該切入點將與 com.wangc 包下的Logging類的方法相匹配,這裏的“*”是通配符-->
         <aop:pointcut id="all" expression="execution(* com.wangc.*.*(..))"/>
         <!-- 定義一個前置通知 -->
         <aop:before pointcut-ref="all" method="beforeAdvice"/>
         <!-- 定義一個後置通知 -->
         <aop:after pointcut-ref="all" method="afterAdvice"/>
         <!-- 定義一個返回後通知 -->
         <aop:after-returning pointcut-ref="all" returning="retVal" method="afterReturningAdvice"/>
         <!-- 定義一個拋出異常後通知 -->
         <aop:after-throwing pointcut-ref="all" throwing="ex" method="AfterThrowingAdvice"/>
      </aop:aspect>
   </aop:config>

   <bean id="employee" class="com.wangc.Employee">
      <property name="name"  value="zhangsan" />
      <property name="age"  value="28" />      
   </bean>

   <bean id="logging" class="com.wangc.Logging"/> 

</beans>

MyApp.java文件

package com.wangc;

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

public class MyApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Employee employee = (Employee) context.getBean("employee");
        employee.getName();
        employee.getAge();
        employee.printThrowException();
    }
}

執行結果:

執行employee的方法之前執行.
name = zhangsan
執行employee的方法之後執行.
返回:zhangsan
執行employee的方法之前執行.
age = 28
執行employee的方法之後執行.
返回:28
執行employee的方法之前執行.
發生異常
執行employee的方法之後執行.
拋出了一個異常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......

注意:上面的例子只有一個橫切關注點,如果有多個橫切關注點,可以使用aspect裏的order屬性來控制橫切關注點的順序。

......
   <aop:config>
      <aop:aspect id="log1" ref="logging1" order="1">
         <aop:pointcut id="addTime" expression="execution(* com.wangc.*.*(..))"/>
         <aop:before pointcut-ref="addTime" method="beforeAdvice"/>
      </aop:aspect>
      <aop:aspect id="log2" ref="logging2" order="2">
         <aop:pointcut id="printLog" expression="execution(* com.wangc.*.*(..))"/>
         <aop:before pointcut-ref="printLog" method="beforeAdvice"/>
      </aop:aspect>
   </aop:config>
   ......

Spring AOP 的 @AspectJ實現方式

這裏只需要在上面的基礎上修改以下兩個文件就可實現,修改後的文件如下:
Beans.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

    <!-- 自動掃描被@Aspect標註的切面 -->
    <aop:aspectj-autoproxy />

    <bean id="employee" class="com.wangc.Employee">
       <property name="name"  value="zhangsan" />
       <property name="age"  value="28" />      
    </bean>

    <bean id="logging" class="com.wangc.Logging"/> 

</beans>

Logging.java文件

package com.wangc;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Logging {
    @Pointcut("execution(* com.wangc.*.*(..))")
    private void all() {
    }
    //在一個方法執行之前,執行通知。
    @Before("all()")
    public void beforeAdvice(){
        System.out.println("執行employee的方法之前執行.");
    }
    //在一個方法執行之後,不考慮其結果,執行通知。
    @After("all()")
    public void afterAdvice(){
        System.out.println("執行employee的方法之後執行.");
    }
    //在一個方法執行之後,只有在方法成功完成時,才能執行通知。
    @AfterReturning(pointcut = "all()", returning="retVal")
    public void afterReturningAdvice(Object retVal){
        System.out.println("返回:" + retVal.toString() );
    }
    //在一個方法執行之後,只有在方法退出拋出異常時,才能執行通知。
    @AfterThrowing(pointcut = "all()", throwing="ex")
    public void AfterThrowingAdvice(IllegalArgumentException ex){
        System.out.println("拋出了一個異常: " + ex.toString());   
    }  
}

這裏做個簡單的說明: 用@Aspect的註解來標識切面,注意不要把它漏了,否則Spring創建代理的時候會找不到它,@Pointcut註解指定了切點,@Before、@After、@AfterReturning和@AfterThrowing指定了運行時的通知。

運行結果:

執行employee的方法之前執行.
name = zhangsan
執行employee的方法之後執行.
返回:zhangsan
執行employee的方法之前執行.
age = 28
執行employee的方法之後執行.
返回:28
執行employee的方法之前執行.
發生異常
執行employee的方法之後執行.
拋出了一個異常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......

上一篇文章Spring還可以這麼學–IoC(控制反轉) / DI(依賴注入)理解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章