Spring超詳細(xì)講解AOP面向切面
說(shuō)明:基于atguigu學(xué)習(xí)筆記。
簡(jiǎn)介
AOP(Aspect Oriented Programming)是一種面向切面的編程思想。不同于面向?qū)ο罄锏睦^承思想,當(dāng)需要為多個(gè)不具有繼承關(guān)系的對(duì)象引人同一個(gè)公共行為時(shí),也就是把程序橫向看,尋找切面,插入公共行為。
AOP目的是為了些把影響了多個(gè)類的公共行為抽取到一個(gè)可重用模塊里,不通過(guò)修改源代碼方式,在主干功能里面添加新功能,降低模塊間的耦合度,增強(qiáng)代碼的可操作性和可維護(hù)性。
例如,每次用戶請(qǐng)求我們的服務(wù)接口,都要進(jìn)行權(quán)限認(rèn)證,看看是否登錄,就可以在不改變?cè)瓉?lái)接口代碼的情況下,假如認(rèn)證這個(gè)新功能。
Spring AOP底層使用了代理模式。下面具體了解一下。
AOP底層原理
代理概念
所謂代理,也就是讓我們的代理對(duì)象持有原對(duì)象,在執(zhí)行原對(duì)象目標(biāo)方法的前后可以執(zhí)行額外的增強(qiáng)代碼。
代理對(duì)象需要是原對(duì)象接口的實(shí)現(xiàn)或原對(duì)象的子類,這樣就可以在對(duì)象引用處直接替換原對(duì)象。
代理方式分靜態(tài)代理和動(dòng)態(tài)代理,區(qū)別在于代理對(duì)象生成方式不同
靜態(tài)代理:在編譯期增強(qiáng),生成可見的代理class,使用代理類替換原有類進(jìn)行調(diào)用。
動(dòng)態(tài)代理:在運(yùn)行期增強(qiáng),內(nèi)存中動(dòng)態(tài)生成代理類,使用反射動(dòng)態(tài)調(diào)用原對(duì)象方法。
在spring中使用的是JDK、CGLIB動(dòng)態(tài)代理對(duì)象。
JDK動(dòng)態(tài)代理:必須基于接口,即生成的代理對(duì)象是對(duì)原對(duì)象接口的實(shí)現(xiàn),相當(dāng)于替換了實(shí)現(xiàn)類,面向?qū)ο笾薪涌诳梢蕴鎿Q實(shí)現(xiàn)類。
CGLIB動(dòng)態(tài)代:理基于繼承,即生成的代理對(duì)象是原對(duì)象的子類,面向?qū)ο笾凶宇惪梢蕴鎿Q父類。
JDK動(dòng)態(tài)代理實(shí)現(xiàn)
使用 JDK 動(dòng)態(tài)代理,使用反射包里 java.lang.refelft.Proxy 類的 newProxyInstance 方法創(chuàng)建代理對(duì)象。
源碼如下
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
Objects.requireNonNull(h);
final Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
/*
* Look up or generate the designated proxy class and its constructor.
*/
Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
return newProxyInstance(caller, cons, h);
}方法有三個(gè)參數(shù):
第一參數(shù),類加載器
第二參數(shù),增強(qiáng)方法所在的類,這個(gè)類實(shí)現(xiàn)的接口,支持多個(gè)接口
第三參數(shù),實(shí)現(xiàn)這個(gè)接口 InvocationHandler,創(chuàng)建代理對(duì)象,寫增強(qiáng)的部分
下面以JDK動(dòng)態(tài)代理為例,具體步驟。
1.創(chuàng)建接口,定義方法
public interface UserDao {
public int add(int a,int b);
public String update(String id);
}2.創(chuàng)建接口實(shí)現(xiàn)類,實(shí)現(xiàn)方法
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public String update(String id) {
return id;
}
}3.使用 Proxy 類創(chuàng)建接口代理對(duì)象
public class JDKProxy {
public static void main(String[] args) {
//創(chuàng)建接口實(shí)現(xiàn)類代理對(duì)象
Class[] interfaces = {UserDao.class};
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// });
UserDaoImpl userDao = new UserDaoImpl();
UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1, 2);
System.out.println("result:"+result);
}
}
//創(chuàng)建代理對(duì)象代碼
class UserDaoProxy implements InvocationHandler {
//1 把創(chuàng)建的是誰(shuí)的代理對(duì)象,把誰(shuí)傳遞過(guò)來(lái)
//有參數(shù)構(gòu)造傳遞
private Object obj;
public UserDaoProxy(Object obj) {
this.obj = obj;
}
//增強(qiáng)的邏輯
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法之前
System.out.println("方法之前執(zhí)行...."+method.getName()+" :傳遞的參數(shù)..."+ Arrays.toString(args));
//被增強(qiáng)的方法執(zhí)行
Object res = method.invoke(obj, args);
//方法之后
System.out.println("方法之后執(zhí)行...."+obj);
return res;
}
}Spring中的AOP
相關(guān)術(shù)語(yǔ)
1.連接點(diǎn)(Join point): 類里面可以被增強(qiáng)的方法。
2.切入點(diǎn):真正被增強(qiáng)的方法。
3.通知:實(shí)際增強(qiáng)處理的邏輯。
AOP框架匯總通知分為以下幾種:
- 前置通知@Before
- 后置通知@AfterReturning
- 環(huán)繞通知@Around
- 異常通知@AfterThrowing
- 最終通知@After
4.切面:把通知應(yīng)用到切入點(diǎn)的過(guò)程,是一個(gè)動(dòng)作。
AspectJ
AspectJ 不是 Spring 組成部分,獨(dú)立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,進(jìn)行 AOP 操作。
基于 AspectJ 實(shí)現(xiàn) AOP 操作可以有兩種方式:基于xml配置文件、基于注解。
要使用AspectJ,首先要引入相關(guān)依賴:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>使用AspectJ時(shí),會(huì)尋找切入點(diǎn),這時(shí)候會(huì)用到切入點(diǎn)表示,為了知道對(duì)哪個(gè)類里面的哪個(gè)方法進(jìn)行增強(qiáng)。
語(yǔ)法結(jié)構(gòu): execution([權(quán)限修飾符] [返回類型] [類全路徑] [方法名稱]([參數(shù)列表]) )
舉例 1:對(duì) com.example.dao.BookDao 類里面的 add 進(jìn)行增強(qiáng)
execution(* com.example.dao.BookDao.add(..))舉例 2:對(duì) com.example.dao.BookDao 類里面的所有的方法進(jìn)行增強(qiáng)
execution(* com.example.dao.BookDao.* (..))舉例 3:對(duì) com.example.dao 包里面所有類,類里面所有方法進(jìn)行增強(qiáng)
execution(* com.example.dao.*.* (..))
實(shí)現(xiàn)AOP
1.創(chuàng)建項(xiàng)目,引入依賴
依賴如下:
<?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>org.example</groupId>
<artifactId>spring-demo02</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
</dependencies>
</project>2.創(chuàng)建類
創(chuàng)建一個(gè)自己的類,寫一個(gè)要增強(qiáng)的方法,并使用注解管理bean
package com.example;
import org.springframework.stereotype.Component;
@Component
public class User {
public void add () {
System.out.println("user add method...");
}
}3.創(chuàng)建代理增強(qiáng)類
創(chuàng)建增強(qiáng)類,使用@Aspect注解。
在增強(qiáng)類里面,創(chuàng)建方法,讓不同方法代表不同通知類型,此例創(chuàng)建前置通知使用@Before
package com.example;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserProxy {
@Before(value = "execution(* com.example.User.add())")
public void before () {
System.out.println("proxy before...");
}
}
4.xml配置
開啟注解掃描和Aspect 生成代理對(duì)象
<?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.example"></context:component-scan>
<!-- 開啟 Aspect 生成代理對(duì)象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>5.測(cè)試類
package com.example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ap = new ClassPathXmlApplicationContext("bean1.xml");
User user = ap.getBean("user", User.class);
user.add();
}
}結(jié)果先輸出proxy before…,再輸出user add method…。說(shuō)明我們的前置通知確實(shí)再被增強(qiáng)方法之前執(zhí)行成功。
不同通知類型實(shí)現(xiàn)
下面把五種通知都實(shí)現(xiàn)看一下順序,修改我們的代理類如下:
package com.example;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserProxy {
/**
* 前置通知
*/
@Before(value = "execution(* com.example.User.add())")
public void before () {
System.out.println("proxy before...");
}
/**
* 后置通知
*/
@AfterReturning(value = "execution(* com.example.User.add())")
public void afterReturning() {
System.out.println("proxy afterReturning...");
}
/**
* 最終通知
*/
@After(value = "execution(* com.example.User.add())")
public void after() {
System.out.println("proxy after...");
}
/**
* 異常通知
*/
@AfterThrowing(value = "execution(* com.example.User.add())")
public void afterThrowing() {
System.out.println("proxy afterThrowing...");
}
/**
* 環(huán)繞通知
*/
@Around(value = "execution(* com.example.User.add())")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 環(huán)繞之前
System.out.println("proxy around before...");
proceedingJoinPoint.proceed();
// 環(huán)繞之后
System.out.println("proxy around after...");
}
}執(zhí)行結(jié)果如下:
proxy around before...
proxy before...
user add method...
proxy around after...
proxy after...
proxy afterReturning...
相同的切入點(diǎn)抽取
上面代碼可以看到我們的通知value是相同的,這時(shí)候可以抽取出來(lái)公用,改寫代理類如下代碼如下:
package com.example;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserProxy {
@Pointcut(value = "execution(* com.example.User.add())")
public void pointDemo() {}
/**
* 前置通知
*/
@Before(value = "pointDemo()")
public void before () {
System.out.println("proxy before...");
}
/**
* 后置通知
*/
@AfterReturning(value = "pointDemo()")
public void afterReturning() {
System.out.println("proxy afterReturning...");
}
/**
* 最終通知
*/
@After(value = "pointDemo()")
public void after() {
System.out.println("proxy after...");
}
/**
* 異常通知
*/
@AfterThrowing(value = "pointDemo()")
public void afterThrowing() {
System.out.println("proxy afterThrowing...");
}
/**
* 環(huán)繞通知
*/
@Around(value = "pointDemo()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 環(huán)繞之前
System.out.println("proxy around before...");
proceedingJoinPoint.proceed();
// 環(huán)繞之后
System.out.println("proxy around after...");
}
}增強(qiáng)類優(yōu)先級(jí)
有多個(gè)增強(qiáng)類多同一個(gè)方法進(jìn)行增強(qiáng),設(shè)置增強(qiáng)類優(yōu)先級(jí)。
在增強(qiáng)類上面添加注解 @Order(數(shù)字類型值),數(shù)字類型值越小優(yōu)先級(jí)越高。
@Component @Aspect @Order(1) public class UserProxy
完全使用注解開發(fā)
創(chuàng)建配置類,不需要?jiǎng)?chuàng)建 xml 配置文件。
package com.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan(basePackages = {"com.example"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}
測(cè)試類:
package com.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopTest {
public static void main(String[] args) {
ApplicationContext ap = new AnnotationConfigApplicationContext(AopConfig.class);
User user = ap.getBean(User.class);
user.add();
}
}到此這篇關(guān)于Spring超詳細(xì)講解AOP面向切面的文章就介紹到這了,更多相關(guān)Spring面向切面內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC的組件之HandlerExceptionResolver詳解
這篇文章主要介紹了SpringMVC的組件之HandlerExceptionResolver詳解,不管是在處理請(qǐng)求映射(HandlerMapping),還是在請(qǐng)求被處理(Handler)時(shí)拋出的異常,DispatcherServlet都會(huì)委托給HandlerExceptionResolver進(jìn)行異常處理,該接口只有一個(gè)方法,需要的朋友可以參考下2023-10-10
Java數(shù)據(jù)結(jié)構(gòu)之棧與綜合計(jì)算器的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)中棧與綜合計(jì)算器的實(shí)現(xiàn),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2022-10-10
Java中實(shí)現(xiàn)接口與繼承的區(qū)別及說(shuō)明
這篇文章主要介紹了Java中實(shí)現(xiàn)接口與繼承的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Java多線程程序中synchronized修飾方法的使用實(shí)例
synchronized關(guān)鍵字主要北用來(lái)進(jìn)行線程同步,這里我們主要來(lái)演示Java多線程程序中synchronized修飾方法的使用實(shí)例,需要的朋友可以參考下:2016-06-06
Idea之沒(méi)有網(wǎng)絡(luò)的情況下創(chuàng)建SpringBoot項(xiàng)目的方法實(shí)現(xiàn)
本文主要介紹了Idea之沒(méi)有網(wǎng)絡(luò)的情況下創(chuàng)建SpringBoot項(xiàng)目的方法實(shí)現(xiàn),文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09

