javascript
理解SpringAOP-菜鸟新手入门
SpringFrameworkAOP學習筆記
- 某一天
- 筆記背景
- 什么是AOP
- JDK實例
- 為什么AOP
- java 動態代理
- CGLib動態代理實例
- JDK 動態代理與CGLib動態代理
- 術語
- AspectJ
某一天
文章內容部分來自于
https://gitee.com/zhongfucheng/Java3y#spring%E5%AE%B6%E6%97%8F
http://c.biancheng.net/view/4241.html
筆記背景
本人在學習Spring玩轉全家桶時,發現自己對AOP以及切片編程等基礎Spring知識有些模糊不清,故寫翻看網上各種文章,網上文章有的抽象有的例子鮮明但是喧賓奪主,故還是我自己寫個文章梳理一下我自己學到的內容
什么是AOP
面向切面編程(AOP)和面向對象編程(OOP)類似,也是一種編程模式,全稱是“Aspect Oriented Programming”,使開發人員在編寫業務邏輯時可以專心于核心業務,從而提高了開發效率。
目前最流行的 AOP 框架有兩個,分別為 Spring AOP 和 AspectJ
舉例說明
JDK實例
在包下 com.mengma.dao 創建CustomerDao 、CustomerDaoImpl的兩個類文件
package com.mengma.dao; public interface CustomerDao {public void add(); // 添加public void update(); // 修改public void delete(); // 刪除public void find(); // 查詢 }package com.mengma.dao; public class CustomerDaoImpl implements CustomerDao {@Overridepublic void add() {System.out.println("添加客戶...");}@Overridepublic void update() {System.out.println("修改客戶...");}@Overridepublic void delete() {System.out.println("刪除客戶...");}@Overridepublic void find() {System.out.println("修改客戶...");} }在包 com.mengma.jdk 下,分別創建MyAspect、MyBeanFactory 、JDKProxyTest三個類
package com.mengma.jdk; public class MyAspect {public void myBefore() {System.out.println("方法執行之前");}public void myAfter() {System.out.println("方法執行之后");} }package com.mengma.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.mengma.dao.CustomerDao; import com.mengma.dao.CustomerDaoImpl; public class MyBeanFactory {public static CustomerDao getBean() {// 準備目標類final CustomerDao customerDao = new CustomerDaoImpl();// 創建切面類實例final MyAspect myAspect = new MyAspect();// 使用代理類,進行增強return (CustomerDao) Proxy.newProxyInstance(MyBeanFactory.class.getClassLoader(),new Class[] { CustomerDao.class }, new InvocationHandler() {public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {myAspect.myBefore(); // 前增強Object obj = method.invoke(customerDao, args);myAspect.myAfter(); // 后增強return obj;}});} }package com.mengma.jdk; import org.junit.Test; import com.mengma.dao.CustomerDao; public class JDKProxyTest {@Testpublic void test() {// 從工廠獲得指定的內容(相當于spring獲得,但此內容時代理對象)CustomerDao customerDao = MyBeanFactory.getBean();// 執行方法customerDao.add();customerDao.update();customerDao.delete();customerDao.find();} }程序要想運行同樣需要上篇文章列出的5個spring包
結果如下
其中 Proxy 的 newProxyInstance() 方法的第一個參數是當前類的類加載器,第二參數是所創建實例的實現類的接口,第三個參數就是需要增強的方法
上述例子是,JDK 動態代理,是通過 JDK 中的 java.lang.reflect.Proxy 類實現的
為什么AOP
Spring AOP主要做的事情就是:「把重復的代碼抽取,在運行的時候往業務方法上動態植入“切面類代碼”」
通過動態代理,我們可以把對象「增強」,將非業務代碼寫在要「增強」的邏輯上
完了以后,我們就可以通過「增強后的對象」去調用方法,最終屏蔽掉「重復代碼」
AOP編程可以簡單理解成:在執行某些代碼前,執行另外的代碼
其實Spring AOP的底層原理就是動態代理!Spring AOP使用純Java實現,它不需要專門的編譯過程,也不需要特殊的類裝載器,它在運行期通過代理方式向目標類織入增強代碼。在Spring中可以無縫地將Spring AOP、IoC和AspectJ整合在一起。
java 動態代理
在Java中動態代理有兩種方式:
JDK動態代理
上述例子就是jdk的動態代理,下面再舉例子說明CGLib動態代理
CGLib動態代理實例
在 com.mengma.dao 包下創建類 GoodsDao
package com.mengma.dao; public class GoodsDao {public void add() {System.out.println("添加商品...");}public void update() {System.out.println("修改商品...");}public void delete() {System.out.println("刪除商品...");}public void find() {System.out.println("修改商品...");} }在 com.mengma.cglib 包下創建類 MyBeanFactory和CGLIBProxyTest
package com.mengma.cglib; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import com.mengma.dao.GoodsDao; import com.mengma.jdk.MyAspect; public class MyBeanFactory {public static GoodsDao getBean() {// 準備目標類final GoodsDao goodsDao = new GoodsDao();// 創建切面類實例final MyAspect myAspect = new MyAspect();// 生成代理類,CGLIB在運行時,生成指定對象的子類,增強Enhancer enhancer = new Enhancer();// 確定需要增強的類enhancer.setSuperclass(goodsDao.getClass());// 添加回調函數enhancer.setCallback(new MethodInterceptor() {// intercept 相當于 jdk invoke,前三個參數與 jdk invoke—致@Overridepublic Object intercept(Object proxy, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {myAspect.myBefore(); // 前增強Object obj = method.invoke(goodsDao, args); // 目標方法執行myAspect.myAfter(); // 后增強return obj;}});// 創建代理類GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();return goodsDaoProxy;} }package com.mengma.cglib; import org.junit.Test; import com.mengma.dao.GoodsDao; public class CGLIBProxyTest {@Testpublic void test() {// 從工廠獲得指定的內容(相當于spring獲得,但此內容時代理對象)GoodsDao goodsDao = MyBeanFactory.getBean();// 執行方法goodsDao.add();goodsDao.update();goodsDao.delete();goodsDao.find();} }上述代碼中,應用了 CGLIB 的核心類 Enhancer
intercept() 方法相當于 JDK 動態代理方式中的 invoke() 方法,該方法會在目標方法執行的前后,對切面類中的方法進行增強;之后調用 Enhancer 類的 create() 方法創建代理類,最后將代理類返回
JDK 動態代理與CGLib動態代理
動態代理也有個約束:目標對象一定是要有接口的,沒有接口就不能實現動態代理……----->因此出現了cglib代理
cglib代理也叫子類代理,從內存中構建出一個子類來擴展目標對象的功能
Spring AOP默認是使用JDK動態代理,如果代理的類沒有接口則會使用CGLib代理
如果是單例的我們最好使用CGLib代理,如果是多例的我們最好使用JDK代理
JDK在創建代理對象時的性能要高于CGLib代理,而生成代理對象的運行性能卻比CGLib的低。
如果是單例的代理,推薦使用CGLib
看到這里我們就應該知道什么是Spring AOP(面向切面編程)了:將相同邏輯的重復代碼橫向抽取出來,使用動態代理技術將這些重復代碼織入到目標對象方法中,實現和原來一樣的功能
現在應用更多更流行的是另一種方式@AspectJ注解驅動的切面
術語
連接點(Join point):
能夠被攔截的地方:Spring AOP是基于動態代理的,所以是方法攔截的。每個成員方法都可以稱之為連接點~
切點(Poincut):
具體定位的連接點:上面也說了,每個方法都可以稱之為連接點,我們具體定位到某一個方法就成為切點。
增強/通知(Advice):
表示添加到切點的一段邏輯代碼,并定位連接點的方位信息。
簡單來說就定義了是干什么的,具體是在哪干
Spring AOP提供了5種Advice類型給我們:前置、后置、返回、異常、環繞給我們使用!
org.springframework.aop.MethodBeforeAdvice(前置通知) 在方法之前自動執行的通知稱為前置通知,可以應用于權限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知) 在方法之后自動執行的通知稱為后置通知,可以應用于關閉流、上傳文件、刪除臨時文件等功能。
org.aopalliance.intercept.MethodInterceptor(環繞通知) 在方法前后自動執行的通知稱為環繞通知,可以應用于日志、事務管理等功能。
org.springframework.aop.ThrowsAdvice(異常通知) 在方法拋出異常時自動執行的通知稱為異常通知,可以應用于處理異常記錄日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知) 在目標類中添加一些新的方法和屬性,可以應用于修改舊版本程序(增強類)。
織入(Weaving):
將增強/通知添加到目標類的具體連接點上的過程。
引入/引介(Introduction):
引入/引介允許我們向現有的類添加新方法或屬性。是一種特殊的增強!
切面(Aspect):
切面由切點和增強/通知組成,它既包括了橫切邏輯的定義、也包括了連接點的定義
AspectJ
使用 AspectJ 開發 AOP 通常有兩種方式:
基于 XML 的聲明式
基于 Annotation 的聲明式
xml由于隨著bean增多,會特別臃腫,所以主流更多采用注解
xml存在錯誤,不知道怎么回事
注釋掉就可以正常運行了,不過注掉的事務代碼不了
所以下面是注解的樣例
package com.mengma.dao; public interface CustomerDao {public void add(); // 添加public void update(); // 修改public void delete(); // 刪除public void find(); // 查詢 }package com.mengma.dao; import org.springframework.stereotype.Repository;@Repository("customerDao") public class CustomerDaoImpl implements CustomerDao {@Overridepublic void add() {System.out.println("添加客戶...");}@Overridepublic void update() {System.out.println("修改客戶...");}@Overridepublic void delete() {System.out.println("刪除客戶...");}@Overridepublic void find() {System.out.println("修改客戶...");} }package com.aspectJ.annotation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; //切面類 @Aspect @Component public class MyAspect {// 用于取代:<aop:pointcut// expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>// 要求:方法必須是private,沒有值,名稱自定義,沒有參數@Pointcut("execution(* com.mengma.dao..*.*(..))")private void myPointCut() {}// 前置通知@Before("myPointCut()")public void myBefore(JoinPoint joinPoint) {System.out.print("前置通知,目標:");System.out.print(joinPoint.getTarget() + "方法名稱:");System.out.println(joinPoint.getSignature().getName());}// 后置通知@AfterReturning(value = "myPointCut()")public void myAfterReturning(JoinPoint joinPoint) {System.out.print("后置通知,方法名稱:" + joinPoint.getSignature().getName());}// 環繞通知@Around("myPointCut()")public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {System.out.println("環繞開始"); // 開始Object obj = proceedingJoinPoint.proceed(); // 執行當前目標方法System.out.println("環繞結束"); // 結束return obj;}// 異常通知@AfterThrowing(value = "myPointCut()", throwing = "e")public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {System.out.println("異常通知" + "出錯了" + e.getMessage());}// 最終通知@After("myPointCut()")public void myAfter() {System.out.println("最終通知");} }package com.aspectJ.annotation; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.mengma.dao.CustomerDao;public class AnnotationTest {@Testpublic void test() {String xmlPath = "com/aspectJ/annotation/applicationContext.xml";ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);System.out.println(applicationContext);// 從spring容器獲取實例CustomerDao customerDao = (CustomerDao) applicationContext.getBean("customerDao");// 執行方法customerDao.add();} } <?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:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--掃描含com.mengma包下的所有注解--><context:component-scan base-package="com.mengma.dao"/><context:component-scan base-package="com.aspectJ.annotation"/><!-- 使切面開啟自動代理 --><aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>上述代碼中, @Aspect 注解用于聲明這是一個切面類,該類作為組件使用,所以要添加 @Component 注解才能生效。 @Poincut 注解用于配置切入點,取代 XML 文件中配置切入點的代碼。
在每個通知相應的方法上都添加了注解聲明,并且將切入點方法名“myPointCut”作為參數傳遞給要執行的方法,如需其他參數(如異常通知的異常參數),可以根據代碼提示傳遞相應的屬性值。
導入了 AOP 命名空間及其配套的約束,使切面類中的 @Aspect注解能夠正常工作;
添加了掃描包,使注解生效;切面開啟自動代理。
總結
以上是生活随笔為你收集整理的理解SpringAOP-菜鸟新手入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SpringCloud入门教程(全集)
- 下一篇: IDEA Spring环境搭建+简单入门