Spring-1-AOP概念簡述-程式碼演示

風暴計劃發表於2021-01-03

目錄

0、動態代理簡述

1、JDK動態代理

1.1 JDK動態代理的程式碼演示

1.1.1 建立一個被代理的物件:介面SomeService

1.1.2 建立介面SomeService的實現類SomeServiceImpl

1.1.3 建立介面SomeService的增強類ServiceUtils

1.1.4 建立一個MyInvocationHandler類, 且實現InvocationHandler介面, 來增強被代理類

1.1.5 測試動態代理的效果-測試程式碼的基類BaseTest

1.1.6 測試動態代理的效果(通過建立動態代理來對被代理類實現方法增強)

1.1.7 測試結果與專案結構

2、AOP:面向切面程式設計:簡述

2.1 AOP簡述(掌握)

2.2 AOP面向切面程式設計對有什麼好處?

2.3 AOP程式設計術語(掌握)

2.3.1 切面(Aspect)

2.3.2 連線點(JoinPoint)

2.3.3 切入點(Pointcut)

2.3.4 目標物件(Target)

2.3.5 通知(Advice)

3、AOP:面向切面程式設計:程式碼演示

3.1 AspectJ 對 AOP 的實現(掌握)

3.2 AspectJ 簡介

3.3 AspectJ 的通知型別(理解)

3.4 AspectJ 的切入點表示式(掌握)

3.4.1 切入點表示式的原型解釋

3.4.2 切入點表示式的原型舉例:

3.5 AspectJ 實現AOP的程式碼演示

3.5.0 使用AspectJ實現AOP的具體步驟如下

3.5.0 建立一個Maven專案, 專案結構圖

3.5.1 加入jar包依賴(Spring依賴+AspectJ依賴):pom.xml

3.5.2 建立目標類(是一個介面):SomeService.java

3.5.3 建立目標類的實現類:SomeServiceImpl.java

3.5.4 建立切面類和編寫切面方法:MyAspect.java

3.5.5 建立spring.xml配置檔案聲名物件, 也即把物件交給Spring容器統一管理

3.5.6 建立springmvc.xml(AOP暫時用不到, 只是web專案必要的配置檔案)

3.5.7 AOP的測試類基類:BaseTest.java

3.5.8 AOP的測試類和測試結果:MyTest.java

4、AOP-面向切面變成-回顧

5、AOP中的其他型別的通知-程式碼演示

5.1 前置通知 @Before:方法有JoinPoint引數

5.1.1 測試結果

5.2 後置通知 @AfterReturning:註解有returning屬性

5.2.1 測試結果

5.3 環繞通知 @Around:增強方法有 ProceedingJoinPoint 引數

5.3.1 測試結果

5.4 異常通知 @AfterThrowing:註解中有throwing屬性

5.5 最終通知 @After:總是會被執行

5.6 定義切入點 @Pointcut

5.7 目前為止的專案結構

【參考B站視訊】https://www.bilibili.com/video/BV1nz4y1d7uy?p=1

0、動態代理簡述

1、JDK動態代理

1.1 JDK動態代理的程式碼演示

1.1.1 建立一個被代理的物件:介面SomeService

package com.wind.service;


import org.springframework.stereotype.Service;

@Service
public interface SomeService {

    void doSome();

    void doOther();
}

1.1.2 建立介面SomeService的實現類SomeServiceImpl

package com.wind.serviceImpl;

import com.wind.service.SomeService;
import org.springframework.stereotype.Service;

@Service
public class SomeServiceImpl implements SomeService {

    @Override
    public void doSome() {
        System.out.println("業務方法===SomeServiceImpl.doSome done...");
    }

    @Override
    public void doOther() {
        System.out.println("業務方法===SomeServiceImpl.doOther done...");
    }
}

1.1.3 建立介面SomeService的增強類ServiceUtils

package com.wind.utils;

import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class ServiceUtils {

    public static void doLog() {
        System.out.println("非業務方法===方法開始執行的時間=" + new Date());
    }

    public static void doTx() {
        System.out.println("非業務方法===方法執行完畢,提交事務的時間=" + new Date());
    }
}

1.1.4 建立一個MyInvocationHandler類, 且實現InvocationHandler介面, 來增強被代理類

package com.wind.handler;


import com.wind.utils.ServiceUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    //定義一個目標物件:即將來的someServiceImpl物件
    private Object targetObject;

    public MyInvocationHandler(Object targetObject) {
        this.targetObject = targetObject;
    }

    /**
     * 通過代理物件執行目標方法時,會執行invoke方法。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("動態代理===MyInvocationHandler.invoke() done...+準備執行的方法=" + method.getName());

        //定義一個方法返回值
        Object result = null;

        //動態代理-前置的方法增強
        ServiceUtils.doLog();

        //執行目標類的目標方法,底層通過Method類中的invoke方法完成
        result = method.invoke(targetObject, args);

        //動態代理-後置的方法增強
        ServiceUtils.doTx();

        //返回目標方法的返回值
        return result;
    }
}

1.1.5 測試動態代理的效果-測試程式碼的基類BaseTest

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/spring.xml"})
public abstract class BaseTest {
}

1.1.6 測試動態代理的效果(通過建立動態代理來對被代理類實現方法增強)

import com.wind.handler.MyInvocationHandler;
import com.wind.service.SomeService;
import com.wind.serviceImpl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

@Component
public class MyApp extends BaseTest {

    @Autowired
    private SomeServiceImpl someServiceImpl;

    @Test
    public void someServiceTest() {
        someServiceImpl.doSome();
        someServiceImpl.doOther();
    }

    /***
     * 使用JDK的Proxy建立動態代理
     */
    @Test
    public void someServiceProxyTest() {

        //1.建立目標類物件
        SomeServiceImpl targetService = new SomeServiceImpl();

        //2.建立InvocationHandler物件
        InvocationHandler invocationHandler = new MyInvocationHandler(targetService);

        //3.建立Proxy動態代理物件
        SomeService targetServiceProxy = (SomeService) Proxy.newProxyInstance(
                targetService.getClass().getClassLoader(),
                targetService.getClass().getInterfaces(),
                invocationHandler
        );

        //4.通過動態代理物件執行invocationHandler中的invoke方法
        targetServiceProxy.doSome();
    }
}

1.1.7 測試結果與專案結構

2、AOP:面向切面程式設計:簡述

2.1 AOP簡述(掌握)

AOP(Aspect Orient Programming),面向切面程式設計,面向切面程式設計是從動態角度考慮程式執行過程。AOP,面向切面程式設計,可通過執行期動態代理實現程式功能的統一維護的一種技術。AOP 是 Spring 框架中的一個重要內容。利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性, 同時提高了開發的效率。

面向切面程式設計,就是將交叉業務邏輯封裝成切面,利用 AOP 容器的功能將切面織入到主業務邏輯中。所謂交叉業務邏輯是指,通用的、與主業務邏輯無 關的程式碼,如安全檢查、事務、日誌、快取等。 若不使用 AOP,則會出現程式碼糾纏,即交叉業務邏輯與主業務邏輯混合在 一起。這樣,會使主業務邏輯變的混雜不清。

例如,轉賬,在真正轉賬業務邏輯前後,需要許可權控制、日誌記錄、載入事務、結束事務等交叉業務邏輯,而這些業務邏輯與主業務邏輯間並無直接關係。但是,它們的程式碼量所佔比重能達到總程式碼量的一半甚至還多。它們的存在,不僅產生了大量的“冗餘”程式碼,還大大干擾了主業務邏輯---轉賬。

2.2 AOP面向切面程式設計對有什麼好處?

(1)減少重複。(2)專注業務。(3)注意:面向切面程式設計只是物件導向程式設計的一種補充。使用 AOP 減少重複程式碼,專注業務實現:

2.3 AOP程式設計術語(掌握)

2.3.1 切面(Aspect)

切面,泛指交叉業務邏輯。上例中的事務處理、日誌處理就可以理解為切面。常用的切面是通知(Advice),實際就是對主業務邏輯的一種增強。

2.3.2 連線點(JoinPoint)

連線點,是指可以被切面織入的具體方法,通常業務介面中的方法均為連線點。

2.3.3 切入點(Pointcut)

切入點,是指宣告的一個或多個連線點的集合。通過切入點指定一組方法。 被標記為 final 的方法是不能作為連線點與切入點的。因為最終的是不能被修改的,不能被增強的。

2.3.4 目標物件(Target)

目標物件,是指將要被增強的物件。即包含主業務邏輯的類的物件。上例中的 StudentServiceImpl 的物件若被增強,則該類稱為目標類,該類物件稱為目標 物件。當然,不被增強,也就無所謂目標不目標了。

2.3.5 通知(Advice)

通知,表示切面的執行時間,Advice 也叫增強。上例中的 MyInvocationHandler 就可以理解為是一種通知。換個角度來說,通知定義了增強程式碼切入到目的碼的時間點,是目標方法執行之前執行,還是之後執行等。通知型別不同,切入時間不同。切入點定義切入的位置,通知定義切入的時間。

3、AOP:面向切面程式設計:程式碼演示

3.1 AspectJ 對 AOP 的實現(掌握)

對於 AOP 這種程式設計思想,很多框架都進行了實現。Spring 就是其中之 一,可以完成面向切面程式設計。然而,AspectJ 也實現了 AOP 的功能,且其實現方式更為簡捷,使用更為方便,而且還支援註解式開發。所以,Spring 又將 AspectJ 的對於 AOP 的實現也引入到了自己的框架中。在 Spring 中使用 AOP 開發時,一般使用 AspectJ 的實現方式。

3.2 AspectJ 簡介

AspectJ 是一個優秀面向切面的框架,它擴充套件了 Java 語言,提供了強大的切 面實現。AspetJ官網地址:https://www.eclipse.org/aspectj/

AspetJ是 Eclipse 的開源專案,官網介紹如下:

(1)a seamless aspect-oriented extension to the Javatm programming language:一種基於 Java 平臺的面向切面程式設計的語言。
(2)Java platform compatible:相容 Java 平臺,可以無縫擴充套件。
(3)easy to learn and use:易學易用。

3.3 AspectJ 的通知型別(理解)

AspectJ 中常用的通知有五種型別:(1)前置通知。(2)後置通知。(3)環繞通知。(4)異常通知。(5)最終通知。

3.4 AspectJ 的切入點表示式(掌握)

AspectJ 定義了專門的表示式用於指定切入點。表示式的原型是:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

3.4.1 切入點表示式的原型解釋

(1)modifiers-pattern:訪問許可權型別
(2)ret-type-pattern:返回值型別
(3)declaring-type-pattern:包名類名

(4)name-pattern(param-pattern):方法名(引數型別和引數個數)

(5)throws-pattern:丟擲異常型別

(6)?:表示可選的部分以上表示式共 4 個部分。

【總結】execution(訪問許可權 方法返回值 方法宣告(引數) 異常型別)

切入點表示式要匹配的物件就是目標方法的方法名。所以,execution 表示式中明顯就是方法的簽名。注意,表示式中黑色文字表示可省略部分,各部分間用空格分開。在其中可以使用以下符號:

3.4.2 切入點表示式的原型舉例:

【總結】execution(訪問許可權 方法返回值 方法宣告(引數) 異常型別)

(1)execution(public * *(..)) :指定切入點為:任意公共方法。

(2)execution(* set*(..)) :指定切入點為:任何一個以“set”開始的方法。

(3)execution(* com.xyz.service.*.*(..)) :指定切入點為:定義在 service 包裡的任意類的任意方法。

(4)execution(* com.xyz.service..*.*(..)) :指定切入點為:定義在 service 包或者子包裡的任意類的任意方法。“..”出現 在類名中時,後面必須跟“*”,表示包、子包下的所有類。

(5)execution(* *..service.*.*(..)) :指定所有包下的 serivce 子包下所有類(介面)中所有方法為切入點。

(6)execution(* *.service.*.*(..)) :指定只有一級包下的 serivce 子包下所有類(介面)中所有方法為切入點。

(7)execution(* *.ISomeService.*(..)) :指定只有一級包下的 ISomeSerivce 介面中所有方法為切入點 。

(8)execution(* *..ISomeService.*(..)) :指定所有包下的 ISomeSerivce 介面中所有方法為切入點。

(9)execution(* com.xyz.service.IAccountService.*(..)):指定切入點為:IAccountService 介面中的任意方法。
(10)execution(* com.xyz.service.IAccountService+.*(..)) :指定切入點為:IAccountService 若為介面,則為介面中的任意方法及其所有 實現類中的任意方法;若為類,則為該類及其子類中的任意方法。

(11)execution(* joke(String,int))) :指定切入點為:所有的 joke(String,int)方法,且 joke()方法的第一個引數是 String,第二個引數是 int。如果方法中的引數型別是 java.lang 包下的類,可 以直接使用類名,否則必須使用全限定類名,如 joke( java.util.List, int)。

(12)execution(* joke(String,*))) :指定切入點為:所有的 joke()方法,該方法第一個引數為 String,第二個引數 可以是任意型別,如 joke(String s1,String s2)和 joke(String s1,double d2) 都是,但 joke(String s1,double d2,String s3)不是。

(13)execution(* joke(String,..))) :指定切入點為:所有的 joke()方法,該方法第一個引數為 String,後面可以有 任意個引數且引數型別不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。

(14)execution(* joke(Object)) :指定切入點為:所有的 joke()方法,方法擁有一個引數,且引數是 Object 類 型。joke(Object ob)是,但,joke(String s)與 joke(User u)均不是。

(15)execution(* joke(Object+))) :指定切入點為:所有的 joke()方法,方法擁有一個引數,且引數是 Object 型別或該類的子類。不僅 joke(Object ob)是,joke(String s)和 joke(User u)也 是。

3.5 AspectJ 實現AOP的程式碼演示

3.5.0 使用AspectJ實現AOP的具體步驟如下

3.5.0 建立一個Maven專案, 專案結構圖

參考https://blog.csdn.net/cmm0401/article/details/111773134

3.5.1 加入jar包依賴(Spring依賴+AspectJ依賴):pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wind</groupId>
    <artifactId>ssm-web8-aop</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.2.5.RELEASE</spring.version>
        <mybatis.version>3.4.6</mybatis.version>
        <mybatis.spring.version>2.0.3</mybatis.spring.version>
        <mysql.version>8.0.22</mysql.version>
        <druid.version>1.2.4</druid.version>
    </properties>
    
    <dependencies>

        <!--測試AOP功能依賴的包-開始-->
        <!--Spring依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--AspectJ的依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>

        <!--測試AOP功能依賴的包-結束-->

        <!--Spring事務依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--Spring原生JDBC依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--SpringMVC依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!--SpringMVC使用的Servlet依賴-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

        <!--SpringMVC使用的jsp依賴-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2.1-b03</version>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!--MyBatis依賴-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!--MyBatis與SpringMVC整合時需要的依賴-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>

        <!--mysql驅動依賴-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!--mysql資料庫連線池依賴:使用的是德魯伊資料庫連線池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--springMVC序列化用的jackson依賴-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.10.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.2</version>
        </dependency>

        <!--單元測試依賴-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.5.RELEASE</version>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <finalName>ssm-web</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>

        <!--
        maven預設掃描src/main/java中的檔案而不理會src/main/resources中的xml檔案,
        所以後來新增了resource節點,這樣就將src/main/resources中的xml檔案改變maven預設的掃描策略,
        防止造成src/main/resources下的配置檔案打包丟失。
        編譯之後的檔案中少了mapper.xml,這個和maven有關,maven編譯src/java程式碼的時候,
        預設只會對java檔案進行編譯然後放在target/classes目錄,需要在pom.xml中加入下面配置-->
        <!--如果不新增此節點,mapper.xml檔案、config.properties檔案、config.spring檔案等
        都不會被載入到target的classes中去,也就不能被使用,也就會報錯。-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

3.5.2 建立目標類(是一個介面):SomeService.java

package com.wind.bao01;


/**
 * 這個目標介面
 */
public interface SomeService {

    void doSome(String name, Integer age);

    String doOther(String name, Integer age);

    String doAround(String name, Integer age);
}

3.5.3 建立目標類的實現類:SomeServiceImpl.java

package com.wind.bao01;


/**
 * 這個是目標介面的實現類,也就是目標類:被代理的物件
 */
public class SomeServiceImpl implements SomeService {

    /**
     * 目標方法:準備給目標方法做增強,在目標方法執行之前列印目標方法執行的開始時間
     */
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("====目標業務方法執行了。SomeServiceImpl.doSome()====");
    }

    @Override
    public String doOther(String name, Integer age) {
        System.out.println("====目標業務方法執行了。SomeServiceImpl.doOther()====");
        return "doOther.result=abcdefg";
    }

    @Override
    public String doAround(String name, Integer age) {
        System.out.println("====目標業務方法執行了。SomeServiceImpl.doAround()====");
        return "doAround.result=abcdefg";
    }
}

3.5.4 建立切面類和編寫切面方法:MyAspect.java

package com.wind.bao01;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 * @Aspect :這是Aspectj框架中的註解,用來聲名一個類是作為切面類來使用。
 * 作用:標識當前類是一個切面類。
 * 切面類:用來給業務方法增強功能的類,在這個類中寫業務增強功能。
 * 使用位置:該註解定義在一個類的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面類中定義各種各樣的切面方法:切面方法是實現業務功能增強的。
     * 切面方法定義的要求:
     * (1)公共的方法,public
     * (2)方法沒有返回值
     * (3)方法名稱自定義
     * (4)方法可以有引數,也可以沒有引數。如果有引數,則引數不是自定義的,有幾個引數型別可以使用。
     */

    /**
     * @Before :前置通知的註解。
     * 屬性value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之前先執行。
     * (2)不會改變目標方法的執行結果。
     * (3)不會影響目標方法的執行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }

    @After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myAfter() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("後置通知,切面功能myAfter()=在目標方法執行之後輸出時間=" + new Date());
    }

}

3.5.5 建立spring.xml配置檔案聲名物件, 也即把物件交給Spring容器統一管理

<?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
       https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--把物件交給Spring容器來管理,由Spring容器來建立物件-->

    <!--聲名目標物件-->
    <bean id="someServiceImpl" class="com.wind.bao01.SomeServiceImpl"/>

    <bean id="oneClassCglib" class="com.wind.bao01.OneClassCglib"/>

    <!--聲名切面列物件-->
    <bean id="myAspect" class="com.wind.bao01.MyAspect"/>

    <!--AspectJ的配置方式:實現AOP功能-->
    <!-- aspectj-autoproxy:
    (1)自動代理生成器:使用AspectJ框架內部的功能,建立目標物件的動態代理物件。
    所建立的動態代理物件是在記憶體中完成的,通過修改目標物件的記憶體中的結構,為其建立代理物件,
    所以,目標物件就變成了被修改後的代理物件。
    (2)aspectj-autoproxy它會把Spring容器中所有的目標物件,一次性都生成代理物件。
    (3)如果有介面和該介面的實現類,此時預設使用JDK動態代理方式。-->
    <aop:aspectj-autoproxy/>
    
    <!--
    (1)如果有介面和該介面的實現類,此時預設使用JDK動態代理方式。
    (2)如果沒有介面,只有類,則此時預設使用CGLIB動態代理方式。
    (3)另外:下面這句話的含義是:告訴AspectJ框架,即使是介面,只要該介面可以被繼承,就使用CGLIB動態代理。-->
    <!--<aop:aspectj-autoproxy proxy-target-class="true"/>-->

</beans>

3.5.6 建立springmvc.xml(AOP暫時用不到, 只是web專案必要的配置檔案)

<?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:mvc="http://www.springframework.org/schema/mvc"
       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/mvc
       https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--SpringMVC的配置檔案,用來聲名Controller和其他web相關的物件-->

    <!--配置元件掃描器-->
    <context:component-scan base-package="com.wind"/>

    <!--檢視解析器:新增字首和字尾。
    SpringMVC框架為了避免對於請求資源路徑與副檔名上的冗餘,在檢視解析器 InternalResouceViewResolver
    中引入了請求的前輟與後輟。而 ModelAndView 中只需給出要跳轉頁面的檔名即可,對於具體的檔案路徑與副檔名,
    檢視解析器會自動完成拼接。-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--檢視檔案的路徑-->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!--檢視檔案的副檔名-->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--註冊註解驅動。
    (1)響應ajax請求,返回json字串。
    (2)解決靜態資源訪問問題。-->
    <mvc:annotation-driven/>

    <!--載入靜態資源圖片啊,jQuery檔案啊等等-->
    <mvc:resources location="js/" mapping="/js/**"/>

</beans>

3.5.7 AOP的測試類基類:BaseTest.java

import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:config/*.xml"})
public abstract class BaseTest {
}

3.5.8 AOP的測試類和測試結果:MyTest.java

import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest extends BaseTest {

    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        proxy.doSome("yangguo", 20);
    }
}

4、AOP-面向切面變成-回顧

5、AOP中的其他型別的通知-程式碼演示

【不光前置通知的方法可以包含一個 JoinPoint 型別引數,所有的通知方法均可包含該引數,而且,該引數只能放在通知方法中的第一個引數位置上。】

5.1 前置通知 @Before:方法有JoinPoint引數

在目標方法執行之前執行。被註解為前置通知的方法,可以包含一個 JoinPoint 型別引數,該型別的物件本身就是切入點表示式。通過該引數,可獲取切入點表示式、方法簽名、目標物件等。

下面程式碼的功能:在通知方法中獲取業務方法資訊, 如方法簽名/引數等-見方法myBefore2()。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * @Aspect :這是Aspectj框架中的註解,用來聲名一個類是作為切面類來使用。
 * 作用:標識當前類是一個切面類。
 * 切面類:用來給業務方法增強功能的類,在這個類中寫業務增強功能。
 * 使用位置:該註解定義在一個類的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面類中定義各種各樣的切面方法:切面方法是實現業務功能增強的。
     * 切面方法定義的要求:
     * (1)公共的方法,public
     * (2)方法沒有返回值
     * (3)方法名稱自定義
     * (4)方法可以有引數,也可以沒有引數。如果有引數,則引數不是自定義的,有幾個引數型別可以使用。
     */

    /**
     * @Before :前置通知的註解。
     * 屬性value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之前先執行。
     * (2)不會改變目標方法的執行結果。
     * (3)不會影響目標方法的執行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }

    /**
     * 指定通知方法中的引數:JoinPoint
     * JoinPoint:業務方法,要加入到切面功能的業務方法。
     * 作用是:可以在通知方法中獲取業務方法執行時的資訊,例如方法名稱、方法引數等。
     * 如果你的通知方法中需要用到業務方法的資訊,就可以加入JoinPoint來獲取。
     * 這個JoinPoint引數是由框架自動賦值的,必須是在第一個引數位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //獲取方法的完整資訊
        System.out.println("方法的簽名(定義)=" + joinPoint.getSignature());
        System.out.println("方法的名稱=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名稱=" + arg);
            }
        }

        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }

    @After(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myAfter() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("後置通知,切面功能myAfter()=在目標方法執行之後輸出時間=" + new Date());
    }

}

5.1.1 測試結果

5.2 後置通知 @AfterReturning:註解有returning屬性

在目標方法執行之後執行。由於是目標方法之後執行,所以可以獲取到目標方法的返回值。該註解的 returning 屬性就是用於指定接收方法返回值的變數名的。

所以,被註解為後置通知的方法,除了可以包含 JoinPoint 引數外, 還可以包含用於接收返回值的變數。該變數最好為 Object 型別,因為目標方法的返回值可能是任何型別。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

/**
 * @Aspect :這是Aspectj框架中的註解,用來聲名一個類是作為切面類來使用。
 * 作用:標識當前類是一個切面類。
 * 切面類:用來給業務方法增強功能的類,在這個類中寫業務增強功能。
 * 使用位置:該註解定義在一個類的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面類中定義各種各樣的切面方法:切面方法是實現業務功能增強的。
     * 切面方法定義的要求:
     * (1)公共的方法,public
     * (2)方法沒有返回值
     * (3)方法名稱自定義
     * (4)方法可以有引數,也可以沒有引數。如果有引數,則引數不是自定義的,有幾個引數型別可以使用。
     */


    /**
     * @Before :前置通知的註解。
     * 屬性value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之前先執行。
     * (2)不會改變目標方法的執行結果。
     * (3)不會影響目標方法的執行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }

    /**
     * 注意:@Before直接是可以指定通知方法中的引數的:使用關鍵字 JoinPoint 來指定。
     * JoinPoint:業務方法,要加入到切面功能的業務方法。
     * 作用是:可以在通知方法中獲取業務方法執行時的資訊,例如方法名稱、方法引數等。
     * 如果你的通知方法中需要用到業務方法的資訊,就可以加入JoinPoint來獲取。
     * 這個JoinPoint引數是由框架自動賦值的,必須是在第一個引數位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //獲取方法的完整資訊
        System.out.println("方法的簽名(定義)=" + joinPoint.getSignature());
        System.out.println("方法的名稱=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名稱=" + arg);
            }
        }

        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }


    /**
     * @AfterReturning :後置通知的註解。
     * 屬性:
     * (1)value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * (2)returning 自定義的變數,表示目標方法的返回值的。注:自定義的變數名必須和通知方法餓形參名保持一致。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之後執行。
     * (2)可以有引數。
     * (3)可以獲取到目標方法的返回值,可以根據這個返回值做不同的處理功能。相當於:Object myRes = doOther();
     * (4)可以修改這個返回值。
     */
    @AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
    public void myAfter(Object myRes) {
        //myRes:是目標方法執行後返回的值,可以根據這個值來做你的增強功能
        System.out.println("後置通知,切面功能myAfter()=在目標方法執行之後得到的結果=" + myRes);
    }

}
    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        proxy.doOther("yangguo", 20);
    }

5.2.1 測試結果

5.3 環繞通知 @Around:增強方法有 ProceedingJoinPoint 引數

在目標方法執行之前與之後執行。被註解為環繞增強的方法要有返回值,Object 型別,並且方法可以包含一個 ProceedingJoinPoint 型別的引數。介面 ProceedingJoinPoint 其有一個 proceed()方法,用於執行目標方法。若目標方法有返回值,則該方法的返回值就是目標方法的返回值。最後,環繞增強方法將其返回值返回,該增強方法實際是攔截了目標方法的執行。

package com.wind.bao01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Date;

/**
 * @Aspect :這是Aspectj框架中的註解,用來聲名一個類是作為切面類來使用。
 * 作用:標識當前類是一個切面類。
 * 切面類:用來給業務方法增強功能的類,在這個類中寫業務增強功能。
 * 使用位置:該註解定義在一個類的上面。
 */
@Aspect
public class MyAspect {

    /**
     * 在切面類中定義各種各樣的切面方法:切面方法是實現業務功能增強的。
     * 切面方法定義的要求:
     * (1)公共的方法,public
     * (2)方法沒有返回值
     * (3)方法名稱自定義
     * (4)方法可以有引數,也可以沒有引數。如果有引數,則引數不是自定義的,有幾個引數型別可以使用。
     */


    /**
     * @Before :前置通知的註解。
     * 屬性value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之前先執行。
     * (2)不會改變目標方法的執行結果。
     * (3)不會影響目標方法的執行。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore() {
        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }

    /**
     * 注意:@Before直接是可以指定通知方法中的引數的:使用關鍵字 JoinPoint 來指定。
     * JoinPoint:業務方法,要加入到切面功能的業務方法。
     * 作用是:可以在通知方法中獲取業務方法執行時的資訊,例如方法名稱、方法引數等。
     * 如果你的通知方法中需要用到業務方法的資訊,就可以加入JoinPoint來獲取。
     * 這個JoinPoint引數是由框架自動賦值的,必須是在第一個引數位置上。
     */
    @Before(value = "execution( void com.wind.bao01.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore2(JoinPoint joinPoint) {
        //獲取方法的完整資訊
        System.out.println("方法的簽名(定義)=" + joinPoint.getSignature());
        System.out.println("方法的名稱=" + joinPoint.getSignature().getName());
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg != null) {
                System.out.println("方法的名稱=" + arg);
            }
        }

        //這個方法就是你的切面想要執行的功能
        System.out.println("前置通知,切面功能myBefore()=在目標方法執行之前輸出時間=" + new Date());
    }


    /**
     * @AfterReturning :後置通知的註解。
     * 屬性:
     * (1)value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * (2)returning 自定義的變數,表示目標方法的返回值的。注:自定義的變數名必須和通知方法餓形參名保持一致。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)在目標方法執行之後執行。
     * (2)可以有引數。
     * (3)可以獲取到目標方法的返回值,可以根據這個返回值做不同的處理功能。相當於:Object myRes = doOther();
     * (4)可以修改這個返回值。
     */
    @AfterReturning(value = "execution( String com.wind.bao01.SomeServiceImpl.doOther(String,Integer))", returning = "myRes")
    public void myAfter(Object myRes) {
        //myRes:是目標方法執行後返回的值,可以根據這個值來做你的增強功能
        System.out.println("後置通知,切面功能myAfter()=在目標方法執行之後得到的結果=" + myRes);
    }


    /**
     * @Around :環繞通知的註解。
     * 屬性:
     * (1)value:切入點表示式execution,用來標識該切面方法在哪些業務方法中被切入。
     * 位置:作用在切面方法的上面。
     * 特點:
     * (1)它是功能最強的通知。
     * (2)在目標業務方法的前和後都可以執行增強功能。
     * (3)控制目標方法是否可以被呼叫執行。
     * (4)修改原來的目標方法的執行結果,影響最後的結果。
     * (5)環繞通知,類似於JDK動態代理中的InvocationHandler介面。
     * 引數:
     * (1)ProceedingJoinPoint:就等同於Method,用於執行目標方法。
     * (2)返回值,就是目標方法的執行結果,可以被修改。
     * 用處:環繞通知通常情況下是用來做事務的:在目標方法執行之前開啟事務,然後執行目標方法,最後提交事務。
     */
    @Around(value = "execution( String com.wind.bao01.SomeServiceImpl.doAround(String,Integer))")
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object result = null;

        String name = "";
        Object[] args = proceedingJoinPoint.getArgs();
        if (args != null && args.length >= 1) {
            name = (String) args[0];
        }

        //1.在目標方法之前做增強
        System.out.println("環繞通知===目標方法之前做增強,時間=" + new Date());

        //2.執行目標方法
        if (name.equals("yangguo")) {
            result = proceedingJoinPoint.proceed();
            result = result + ",對目標方法的結果做二次修正";
        } else {
            result = "在AOP.myAround()中,我改變了目標方法執行的結果了";
        }

        //3.在目標方法之後做增強
        System.out.println("環繞通知===目標方法之後做增強,提交事務時間=" + new Date());

        //4.返回最終的結果
        return result;
    }

}
import com.wind.bao01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest extends BaseTest {

    @Test
    public void bao01Test() {
        String config = "config/spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) context.getBean("someServiceImpl");
        System.out.println(proxy.getClass().getName());
        //proxy.doSome("yangguo", 20);
        //proxy.doOther("yangguo", 20);
        String around = proxy.doAround("yangguo1", 20);
        System.out.println(around);
    }
}

5.3.1 測試結果

(1)目標方法執行了:

(2)目標方法沒有執行:

5.4 異常通知 @AfterThrowing:註解中有throwing屬性

在目標方法丟擲異常後執行。該註解的 throwing 屬性用於指定所發生的異常類物件。當然,被註解為異常通知的方法可以包含一個引數 Throwable,引數名稱為 throwing 指定的名稱,表示發生的異常物件。

5.5 最終通知 @After:總是會被執行

無論目標方法是否丟擲異常,該增強均會被執行。

5.6 定義切入點 @Pointcut

當較多的通知增強方法使用相同的 execution 切入點表示式時,編寫、維 護均較為麻煩。AspectJ 提供了@Pointcut 註解,用於定義 execution 切入點表示式。

其用法是,將@Pointcut 註解在一個方法之上,以後所有的 execution 的 value 屬性值均可使用該方法名作為切入點。

代表的就是@Pointcut 定義的切 入點。這個使用@Pointcut 註解的方法一般使用 private 的標識方法,即沒有實際作用的方法。

5.7 目前為止的專案結構

 

 

 

 

 

相關文章