淺談Spring AOP

1. 什麼是AOP

AOP(Aspect Oriented Programming) 面向切面編程,是目前軟件開發中的一個熱點,是Spring框架內容,利用AOP能夠對業務邏輯的各個部分隔離,從而使的業務邏輯各部分的耦合性下降,提升程序的可重用性,提高開發效率。java

AOP實現原理是java動態代理,可是jdk的動態代理必須實現接口,因此spring的aop是用cglib這個庫實現的,cglib使用裏asm這個直接操縱字節碼的框架,因此能夠作到不使用接口的狀況下實現動態代理。正則表達式

2. 應該場景

AOP是處理一些橫切行問題。這些橫切性問題不會影響到主邏輯的實現,可是會散落到代碼的各個部分,難以維護。AOP就是把這些問題和主業務邏輯分開,達到與主業務邏輯解耦的目的。spring

  • Authentication 權限
  • Caching 緩存
  • Context passing 內容傳遞
  • Error handling 錯誤處理
  • Lazy loading 懶加載
  • Debugging  調試
  • logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準
  • Performance optimization 性能優化
  • Persistence  持久化
  • Resource pooling 資源池
  • Synchronization 同步
  • Transactions 事務

3. AOP與OOP的區別:

OOP面向對象編程,針對業務處理過程的實體及其屬性和行爲進行抽象封裝,以得到更加清晰高效的邏輯單元劃分。而AOP則是針對業務處理過程當中的切面進行提取,它所面對的是處理過程的某個步驟或階段,以得到邏輯過程的中各部分之間低耦合的隔離效果。這兩種設計思想在目標上有着本質的差別。編程

經過下面的圖能夠清晰的理解AOP與OOP的區別:緩存

4. AOP中的概念

  1. AOP代理(AOP Proxy):AOP框架建立的對象,代理就是目標對象的增強。Spring中的AOP代理可使JDK動態代理,也能夠是CGLIB代理,前者基於接口,後者基於子類。
  2. Join point(鏈接點):鏈接點就是Advice在應用程序上執行的點或時機,表示在程序中明肯定義的點,通常是方法的調用。被攔截到的點,由於Spring只支持方法類型的鏈接點,因此在Spring中鏈接點指的就是被攔截到的方法,實際上鍊接點還能夠是字段或者構造器。
  3. Advice(通知):Advice 定義了在 Pointcut裏面定義的程序點具體要作的操做,AOP在特定的切入點上執行的加強處理,有before(前置),after(後置),afterReturning(最終),afterThrowing(異常),around(環繞)。
    • Before:在目標方法被調用以前作加強處理,@Before只須要指定切入點表達式即
    • AfterReturning:在目標方法正常完成後作加強,@AfterReturning除了指定切入點表達式後,還能夠指定一個返回值形參名returning,表明目標方法的返回值
    • AfterThrowing:主要用來處理程序中未處理的異常,@AfterThrowing除了指定切入點表達式後,還能夠指定一個throwing的返回值形參名,能夠經過該形參名來訪問目標方法中所拋出的異常對象
    • After:在目標方法完成以後作加強,不管目標方法是否成功完成。@After能夠指定一個切入點表達式
    • Around:環繞通知,在目標方法完成先後作加強處理,環繞通知是最重要的通知類型,像事務,日誌等都是環繞通知,注意編程中核心是一個ProceedingJoinPoint
  4. Aspect(切面): Aspect 聲明相似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的 Advice。
  5. Pointcut(切點):表示一組 joint point,這些 joint point 或是經過邏輯關係組合起來,或是經過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
  6. Target(目標對象):織入 Advice 的目標對象。
  7. Weave(織入):將 Aspect 和其餘對象鏈接起來, 並建立 Adviced object 的過程。
  8. Target Object(目標對象): 包含鏈接點的對象。也被稱做被通知或被代理對象。POJO(Plain Ordinary Java Object)簡單的Java對象,實際就是普通JavaBeans。
  9. introduction(引入):在不修改代碼的前提下,引入能夠在運行期爲類動態地添加一些方法或字段

AOP中的Joinpoint能夠有多種類型:構造方法調用,字段的設置和獲取,方法的調用,方法的執行,異常的處理執行,類的初始化。也就是說在AOP的概念中咱們能夠在上面的這些Joinpoint上織入咱們自定義的Advice,可是在Spring中卻沒有實現上面全部的joinpoint,確切的說,Spring只支持方法執行類型的Joinpoint性能優化

5. 實戰

pom.xmlbash

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>
複製代碼

四種實現方式

  1. 經典的基於代理的AOP
  2. @AspectJ註解驅動的切面
  3. 純POJO切面,經過aop:config標籤配置
  4. 注入式AspectJ切面

1. 原生spring實現

定義一個通用接口,全部實現此接口的類都有一個鹹魚方法和一個測試aop的方法app

public interface HelloWorld {
    void saltedFish();

    void testPrintTime();
}
複製代碼

實現1框架

public class HelloWorldImpl1 implements HelloWorld{
    @Override
    public void saltedFish() {
        System.out.println("this is a salted fish =========== 1");
    }

    @Override
    public void testPrintTime() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testPrintTime 1=============111111111");
    }
}
複製代碼

實現2ide

public class HelloWorldImpl2 implements HelloWorld{
    @Override
    public void saltedFish() {
        System.out.println("this is a salted fish =========== 2");
    }

    @Override
    public void testPrintTime() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testPrintTime 2=============22222222");
    }
}
複製代碼

定義一個Advice,實如今鏈接點以前以後該乾的事

public class TimeHandler implements MethodBeforeAdvice, AfterReturningAdvice {
    Long before = 0L;

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        Long after = System.currentTimeMillis();
        System.out.println("==========代理後time, " + after + " ======= 間隔: " + (after - before) + "==========");
    }

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        before = System.currentTimeMillis();
        System.out.println("==========代理前time:" + before + "===========");
    }
}
複製代碼

經過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="h1" class="com.example.demo.aop.HelloWorldImpl1"></bean>
    <bean id="h2" class="com.example.demo.aop.HelloWorldImpl2"></bean>

    <!--  定義advice -->
    <bean id="timeHandler" class="com.example.demo.aop.TimeHandler"></bean>

    <!--  定義point cut  -->
    <bean id="timePointCut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*testPrintTime"></property>
    </bean>

    <!-- 切面 關聯切入點與通知 -->
    <bean id="timeHandlerAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="timeHandler"></property>
        <property name="pointcut" ref="timePointCut"></property>
    </bean>

    <!-- 設置代理-->
    <bean id="proxy1" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--  代理的對象 -->
        <property name="target" ref="h1"></property>
        <!-- 使用的切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口 -->
        <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property>
    </bean>

    <!-- 設置代理-->
    <bean id="proxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--  代理的對象 -->
        <property name="target" ref="h2"></property>
        <!-- 使用的切面 -->
        <property name="interceptorNames" value="timeHandlerAdvisor"></property>
        <!-- 代理接口 -->
        <property name="interfaces" value="com.example.demo.aop.HelloWorld"></property>
    </bean>
</beans>
複製代碼

測試類

public class AOPTest {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("./application.xml");
        HelloWorld helloWorld1 = (HelloWorld) applicationContext.getBean("proxy1");
        HelloWorld helloWorld2 = (HelloWorld) applicationContext.getBean("proxy2");

        helloWorld1.saltedFish();
        System.out.println("---------------------");
        helloWorld1.testPrintTime();

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

        helloWorld2.saltedFish();
        System.out.println("---------------------");
        helloWorld2.testPrintTime();
    }
}
複製代碼

打印

this is a salted fish =========== 1
---------------------
==========代理前time:1582477901748===========
testPrintTime 1=============111111111
==========代理後time, 1582477902750 ======= 間隔: 1002==========
=======================
this is a salted fish =========== 2
---------------------
==========代理前time:1582477902750===========
testPrintTime 2=============22222222
==========代理後time, 1582477903250 ======= 間隔: 500==========
複製代碼
複製代碼
相關文章
相關標籤/搜索