Spring超詳細講解AOP面向切面
說明:基于atguigu學習筆記。
簡介
AOP(Aspect Oriented Programming)是一種面向切面的編程思想。不同于面向對象里的繼承思想,當需要為多個不具有繼承關系的對象引人同一個公共行為時,也就是把程序橫向看,尋找切面,插入公共行為。
AOP目的是為了些把影響了多個類的公共行為抽取到一個可重用模塊里,不通過修改源代碼方式,在主干功能里面添加新功能,降低模塊間的耦合度,增強代碼的可操作性和可維護性。
例如,每次用戶請求我們的服務接口,都要進行權限認證,看看是否登錄,就可以在不改變原來接口代碼的情況下,假如認證這個新功能。
Spring AOP底層使用了代理模式。下面具體了解一下。
AOP底層原理
代理概念
所謂代理,也就是讓我們的代理對象持有原對象,在執(zhí)行原對象目標方法的前后可以執(zhí)行額外的增強代碼。
代理對象需要是原對象接口的實現(xiàn)或原對象的子類,這樣就可以在對象引用處直接替換原對象。
代理方式分靜態(tài)代理和動態(tài)代理,區(qū)別在于代理對象生成方式不同
靜態(tài)代理:在編譯期增強,生成可見的代理class,使用代理類替換原有類進行調用。
動態(tài)代理:在運行期增強,內存中動態(tài)生成代理類,使用反射動態(tài)調用原對象方法。
在spring中使用的是JDK、CGLIB動態(tài)代理對象。
JDK動態(tài)代理:必須基于接口,即生成的代理對象是對原對象接口的實現(xiàn),相當于替換了實現(xiàn)類,面向對象中接口可以替換實現(xiàn)類。
CGLIB動態(tài)代:理基于繼承,即生成的代理對象是原對象的子類,面向對象中子類可以替換父類。
JDK動態(tài)代理實現(xiàn)
使用 JDK 動態(tài)代理,使用反射包里 java.lang.refelft.Proxy
類的 newProxyInstance
方法創(chuàng)建代理對象。
源碼如下
@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); }
方法有三個參數(shù):
第一參數(shù),類加載器
第二參數(shù),增強方法所在的類,這個類實現(xiàn)的接口,支持多個接口
第三參數(shù),實現(xiàn)這個接口 InvocationHandler,創(chuàng)建代理對象,寫增強的部分
下面以JDK動態(tài)代理為例,具體步驟。
1.創(chuàng)建接口,定義方法
public interface UserDao { public int add(int a,int b); public String update(String id); }
2.創(chuàng)建接口實現(xiàn)類,實現(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)建接口代理對象
public class JDKProxy { public static void main(String[] args) { //創(chuàng)建接口實現(xiàn)類代理對象 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)建代理對象代碼 class UserDaoProxy implements InvocationHandler { //1 把創(chuàng)建的是誰的代理對象,把誰傳遞過來 //有參數(shù)構造傳遞 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } //增強的邏輯 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前執(zhí)行...."+method.getName()+" :傳遞的參數(shù)..."+ Arrays.toString(args)); //被增強的方法執(zhí)行 Object res = method.invoke(obj, args); //方法之后 System.out.println("方法之后執(zhí)行...."+obj); return res; } }
Spring中的AOP
相關術語
1.連接點(Join point): 類里面可以被增強的方法。
2.切入點:真正被增強的方法。
3.通知:實際增強處理的邏輯。
AOP框架匯總通知分為以下幾種:
- 前置通知@Before
- 后置通知@AfterReturning
- 環(huán)繞通知@Around
- 異常通知@AfterThrowing
- 最終通知@After
4.切面:把通知應用到切入點的過程,是一個動作。
AspectJ
AspectJ 不是 Spring 組成部分,獨立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,進行 AOP 操作。
基于 AspectJ 實現(xiàn) AOP 操作可以有兩種方式:基于xml配置文件、基于注解。
要使用AspectJ,首先要引入相關依賴:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency>
使用AspectJ時,會尋找切入點,這時候會用到切入點表示,為了知道對哪個類里面的哪個方法進行增強。
語法結構: execution([權限修飾符] [返回類型] [類全路徑] [方法名稱]([參數(shù)列表]) )
舉例 1:對 com.example.dao.BookDao 類里面的 add 進行增強
execution(* com.example.dao.BookDao.add(..))舉例 2:對 com.example.dao.BookDao 類里面的所有的方法進行增強
execution(* com.example.dao.BookDao.* (..))舉例 3:對 com.example.dao 包里面所有類,類里面所有方法進行增強
execution(* com.example.dao.*.* (..))
實現(xiàn)AOP
1.創(chuà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)建一個自己的類,寫一個要增強的方法,并使用注解管理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)建代理增強類
創(chuàng)建增強類,使用@Aspect注解。
在增強類里面,創(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 生成代理對象
<?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 生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
5.測試類
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(); } }
結果先輸出proxy before…,再輸出user add method…。說明我們的前置通知確實再被增強方法之前執(zhí)行成功。
不同通知類型實現(xiàn)
下面把五種通知都實現(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í)行結果如下:
proxy around before...
proxy before...
user add method...
proxy around after...
proxy after...
proxy afterReturning...
相同的切入點抽取
上面代碼可以看到我們的通知value是相同的,這時候可以抽取出來公用,改寫代理類如下代碼如下:
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..."); } }
增強類優(yōu)先級
有多個增強類多同一個方法進行增強,設置增強類優(yōu)先級。
在增強類上面添加注解 @Order(數(shù)字類型值),數(shù)字類型值越小優(yōu)先級越高。
@Component @Aspect @Order(1) public class UserProxy
完全使用注解開發(fā)
創(chuà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 { }
測試類:
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(); } }
到此這篇關于Spring超詳細講解AOP面向切面的文章就介紹到這了,更多相關Spring面向切面內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot?整合?clickhouse的實現(xiàn)示例
本文主要介紹了springboot?整合?clickhouse的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02java+mysql實現(xiàn)圖書館管理系統(tǒng)實戰(zhàn)
這篇文章主要為大家詳細介紹了java+mysql實現(xiàn)圖書館管理系統(tǒng)實戰(zhàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12