Spring-1-AOP概念簡述-程式碼演示
目錄
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 測試動態代理的效果(通過建立動態代理來對被代理類實現方法增強)
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.8 AOP的測試類和測試結果:MyTest.java
5.1 前置通知 @Before:方法有JoinPoint引數
5.2 後置通知 @AfterReturning:註解有returning屬性
5.3 環繞通知 @Around:增強方法有 ProceedingJoinPoint 引數
5.4 異常通知 @AfterThrowing:註解中有throwing屬性
【參考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 目前為止的專案結構
相關文章
- Dart 非同步程式設計相關概念簡述Dart非同步程式設計
- SAP HANA Delivery Unit概念簡述
- 簡單演示Excel中VBA程式碼的使用Excel
- SAP Commerce Cloud 裡的 Media 概念簡述Cloud
- WebSocket 程式碼演示Web
- 密碼學簡述密碼學
- 【程式碼簡述設計模式】----- 觀察者模式設計模式
- Python Selenium的簡單演示程式Python
- Shell指令碼知識簡述指令碼
- 好程式設計師Java培訓簡述Java新手如何學程式碼程式設計師Java
- MODBUS協議整理——功能碼簡述協議
- 簡述小資料池,編碼和解碼
- nms 演算法演示(附程式碼)演算法
- ECMASCRIPT 2021新功能程式碼演示案例
- Spark RPC框架原始碼分析(一)簡述SparkRPC框架原始碼
- 簡述Linux 中程式與執行緒Linux執行緒
- kafka消費者提交方式(程式碼演示)Kafka
- CSS設定div邊框演示程式碼CSS
- Dojo簡述
- CNN 簡述CNN
- Java JMS 極簡演示Java
- 簡述JavaScript模組化程式設計(二)JavaScript程式設計
- 簡述top命令與結束程式kill命令
- 遺留程式碼處理技巧與案例演示
- 【.NET基礎】Linq常用語法程式碼演示
- CSS滑鼠懸浮出現遮罩層程式碼演示CSS遮罩
- CMN簡述 --20240305
- ViT簡述【Transformer】ORM
- 轉移簡述
- Java代理簡述Java
- 文字摘要簡述
- Angular框架簡述Angular框架
- DES加密簡述加密
- Spring MVC 簡述SpringMVC
- Casbin中的概念簡明及使用經驗(大白話無程式碼版)
- 超詳細 | 使用Nexus搭建私服 (帶程式碼演示)
- 高手 Linux 程式碼炫酷秀(含演示視訊)Linux
- DevOps全面綜述:從概念到實踐dev