目錄
1. Web MVC發(fā)展史歷程
2.Spring概要
3.Spring-依賴注入概要(IOC)
4.屬性注入的三種實現方式
5.Spring-IoC XML裝配
6.Spring-XML設置Bean的值
7.Spring-IoC 注解(1)
8.Spring-IoC 注解(2)
9.Spring-AOP切面編程(1)
10.Spring-AOP切面編程(2)
未完待續(xù)...
一官硝、概要
Spring也加入了對AOP編程支持逢倍,利用AOP的思想結合Spring的一些API可以實現核心業(yè)務與輔助業(yè)務的分離,即可以在執(zhí)行核心業(yè)務時,將一些輔助的業(yè)務加進來,而輔助業(yè)務(如日志,權限控制等)一般是一些公共業(yè)務,這樣就實現了兩者的分離,使得核心業(yè)務的代碼更加純粹,而且輔助業(yè)務也能得到復用,面向切面編程(AOP)是對面向對象編程(oop)的補充沸柔,
還有eclipse基金會的AspectJ開源項目
Spring AOP Spring AOP默認是使用JDK動態(tài)代理,如果代理的類沒有接口則會使用CGLib代理
AspectJ是語言級別的AOP實現,擴展了Java語言轧铁,定義了AOP語法拱礁,能夠在編譯期提供橫切代碼的織入衣屏,所以它有專門的編譯器用來生成遵守Java字節(jié)碼規(guī)范的Class文件
所以Spring除了自身實現了AOP外還對也對AspectJ進行了支持减俏,
目前Spring提供了4種實現AOP的方式
- 基于代理的AOP
- 純POJO切面
- @AspectJ注解驅動的切面(重點)
- 注入式AspectJ切面(與Spring并無多大的關系倍试,這個就是使用AspectJ這個框架實現AOP編程)
二耿芹、AOP使用場景
- Authentication 權限管理
- Log 日志記錄
- Transactions 事務處理
- Exception 異常處理
- safety control 安全控制
- Caching 緩存
- performance 性能統(tǒng)計 等等
三峦嗤、AOP的基本概念
1嬉荆、術語
術語 | 描述 |
---|---|
方面/切面(Aspect ) |
通常是一個類入蛆,里面可以定義切入點和通知 |
增強(Advice ) |
有時候也翻譯成通知响蓉,這是在方法執(zhí)行之前或之后采取的實際操作。Spring提供了有5種類型的通知 |
切入點(Pointcut ) |
就是帶有通知的連接點哨毁,在程序中主要體現為書寫切入點表達式枫甲,負責往"什么地方"插入代碼,"通知"負責插入"什么代碼 |
加入點(Join point ) |
程序執(zhí)行過程中明確的點,一般是方法的調用, |
介紹或者引入(Introduction ) |
允許我們向現有的類添加新的方法或者屬性 |
編織(Weaving ) |
把切面應用到目標對象來創(chuàng)建新的代理對象的過程(由框架完成)扼褪∠牖茫織入一般發(fā)生在如下幾個時機: 1.編譯時(AspectJ的織入編譯器), 2.運行時完成(動態(tài)代理技術)迎捺。 3.類加載時(使用特殊的ClassLoader在目標類被加載到程序之前增強類的字節(jié)代碼) |
目標對象(Target object ) |
對象被一個或多個方面通知(Advice )举畸,該對象將始終是代理的對象。也稱為通知(Advice )對象凳枝。 |
2抄沮、5種通知(Advice
)
通知節(jié)點 | 對應的接口 | 描述 |
---|---|---|
Before(前) | org.apringframework.aop.MethodBeforeAdvice | 在方法執(zhí)行之前運行通知。 |
After-returning(返回通知) | org.springframework.aop.AfterReturningAdvice | 只有方法成功完成后才能在方法執(zhí)行后運行通知岖瑰。 |
After-throwing(拋出后) | org.springframework.aop.ThrowsAdvice | 只有在方法通過拋出異常而退出方法執(zhí)行之后才能運行通知叛买。 |
Arround(周圍) | org.aopaliance.intercept.MethodInterceptor | 環(huán)繞通知,在目標方法完成前后做增強處理,能在方法調用前后自定義一些操作蹋订。環(huán)繞通知還需要負責決定是繼續(xù)處理join point(調用ProceedingJoinPoint的proceed方法)還是中斷執(zhí)行率挣。 |
After(finally)(最終通知) | org.springframework.aop.IntroductionInterceptor | 在目標方法執(zhí)行完成后執(zhí)行,不管是正常執(zhí)行完成露戒,還是拋出異常椒功,都會執(zhí)行返回通知中的內容 |
3、示例圖
四智什、基于代理的AOP
1动漾、概要
橫切關注點:跨越應用程序多個模塊的方法或功能。(軟件系統(tǒng)荠锭,可以看做由一組關注點即業(yè)務或功能或方法組成旱眯。其中,直接的業(yè)務關注點是直切關注點,而為直切關注點服務的删豺,就是橫切關注點共虑。)即是,與我們業(yè)務邏輯無關的呀页,但是我們需要關注的部分妈拌,就是橫切關注點。
將增強類和攔截條件組合在一起赔桌,然后將這個切面配置到 ProxyFactory 中供炎,從而生成代理。
3疾党、導入包
需要引入spring-aop包,只需要引入spring-context自動會引入spring-aop
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.4.RELEASE</version> </dependency>
4音诫、定義連接點
- 示例代碼
public interface UserDao { public void save(User user); public void testException() throws Exception; }
5、定義實現類
- 示例代碼
public class UserDaoImpl implements UserDao { @Override public void save(User user) { System.out.println("3.保存用戶"); } @Override public void testException() throws Exception { System.out.println("3.拋出異常"); throw new Exception(" Exception happened!"); } }
6雪位、定義切面
- 示例代碼
import org.springframework.aop.MethodBeforeAdvice; /** * 前置通知 */ public class TestBeforeAdvice implements MethodBeforeAdvice { /** * @param method 被調用的方法 * @param args 方法參數 * @param target 代理對象實例 * @throws Throwable */ @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("1.前置通知"); } }
/** * 后置通知 */ import org.springframework.aop.AfterReturningAdvice; public class TestAfterAdvice implements AfterReturningAdvice { /** * @param returnValue 返回值 * @param method 被調用的方法 * @param args 方法參數 * @param target 被代理對象 * @throws Throwable */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("5.后置通知"); } }
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; /** * 環(huán)繞通知 */ public class TestSurroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { //前置橫切邏輯 //方法調用 Object ret = invocation.proceed(); //后置橫切邏輯 return ret; } }
import org.springframework.aop.ThrowsAdvice; /** * 該接口上沒有任何方法竭钝,但是實現了這個接口的類必須至少實現以下4個方法中的一個 * 1.public void afterThrowing(Exception ex) * 2.public void afterThrowing(RemoteException) * 3.public void afterThrowing(Method method, Object[] args, Object target, Exception ex) * 4.public void afterThrowing(Method method, Object[] args, Object target, ServletException ex) */ public class TestThrowingAdvice implements ThrowsAdvice { public void afterThrowing(Exception ex) { System.out.println("5.拋出異常" + ex.getMessage()); } public void afterThrowing(RemoteException re) { System.out.println("5.拋出異常" + re.getMessage()); } /** * @param method 執(zhí)行的方法 * @param args 方法參數 * @param target 代理的目標對象 * @param ex 產生的異常 */ public void afterThrowing(Method method, Object[] args, Object target, Exception ex) { System.out.println("5.出錯的方法是: " + method.getName()); for (Object o : args) { System.out.println("5.方法的參數: " + o.toString()); } System.out.println("5.目標對象的名稱: " + target.getClass().getSimpleName()); System.out.println("5.錯誤的信息" + ex.getMessage()); } // public void afterThrowing(Method method, Object[] args, Object target, ServletException ex){ // } }
7、測試代碼
- 示例代碼
public class TestBaseAdvice { public static void main(String[] args) { //實例化Spring代理工廠 ProxyFactory factory = new ProxyFactory(); //設置被代理的對象 factory.setTarget(new UserDaoImpl()); //添加通知雹洗,橫切邏輯 factory.addAdvice(new TestAfterAdvice()); factory.addAdvice(new TestBeforeAdvice()); factory.addAdvice(new TestSurroundAdvice()); factory.addAdvice(new TestThrowingAdvice()); //從代理工廠中獲得代理對象 UserDao dao = (UserDao) factory.getProxy(); dao.save(new User()); dao.testException(); } }
8香罐、使用IOC配置AOP
- 說明
方法與類與上面相同 - 在resources下新建spring-aop-base.xml
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!-- 注冊被代理的目標對象 --> <bean id="target" class="com.wener.example.AOP.base.UserDaoImpl"/> <!-- 注冊一組通知--> <bean id="afterAdvice" class="com.wener.example.AOP.base.TestAfterAdvice"/> <bean id="beforeAdvice" class="com.wener.example.AOP.base.TestBeforeAdvice"/> <bean id="surroundAdvice" class="com.wener.example.AOP.base.TestSurroundAdvice"/> <bean id="throwingAdvice" class="com.wener.example.AOP.base.TestThrowingAdvice"/> <!--注冊代理對象 --> <!--interceptorNames 通知數組 可以是多個也可以是單個 --> <!--p:target-ref 被代理的對象--> <!--p:proxyTargetClass 被代理對象是否為類,如果是類必須設置true--> <bean id="proxy" class="org.springframework.AOP.framework.ProxyFactoryBean" p:interceptorNames-ref="advices" p:target-ref="target" p:proxyTargetClass="fasle"> </bean> <util:list id="advices"> <value>afterAdvice</value> <value>surroundAdvice</value> <value>surroundAdvice</value> <value>throwingAdvice</value> </util:list> </beans>
- 測試代碼
private static void testAop() { ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop-base.xml"); UserDao dao = (UserDao) context.getBean("proxy"); // 獲取FactoryBean本身 //ProxyFactoryBean bean = (ProxyFactoryBean) context.getBean("$proxy"); dao.save(new User()); }
- 注意
- ProxyFactoryBean,就是一個bean對象时肿,不要被前面的Factory擾亂誤導庇茫,也是要放入BeanFactory被spring管理。
- ProxyFactoryBean特殊在通過常規(guī)的ApplicationContext.getBean(bean Id) 獲取的不是FactoryBean這個直接對象螃成,而是調用FactoryBean.getObject()生成的對象旦签,返回給你。
- ApplicationContext.getBean(&bean Id) 寸宏,加上&才能取得FactoryBean這個對象宁炫。
- FactoryBean這樣的過程,就是為了方便你定義生成【復雜bean】對象氮凝,就是這個bean對象不是簡單的new 羔巢,設置幾個參數,有其他初始化才能完整被使用罩阵,比如ProxyFactoryBean竿秆。具體執(zhí)行代碼如下:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop-base.xml"); ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); proxyFactoryBean.setBeanFactory(ac.getBeanFactory()); //AOP攔截處理類 proxyFactoryBean.setInterceptorNames("afterAdvice"); //代理的接口 proxyFactoryBean.setInterfaces(UserDao.class); //被代理對象 proxyFactoryBean.setTarget(ac.getBean(UserDao.class)); //放入bean工廠,實際開發(fā)是在config下使用注解稿壁,設置多個proxyFactoryBean代理幽钢,設置不同bean id ac.getBeanFactory().registerSingleton("proxy",proxyFactoryBean); UserDao userDao = (UserDao) ac.getBean("proxy"); userDao.save(); //獲取直接的ProxyFactoryBean對象,加& System.out.println(ac.getBean("&proxy"));
五常摧、純POJO切面編程(了解)
1搅吁、說明
開發(fā)步驟
- 創(chuàng)建目標類:定義接口和和接口實現類(jdk動態(tài)代理)或者定義類(cglib)
- 定義通知
- 配置Spring IOC容器
2、示例代碼
- 添加引用spring-aspects包
<!--提供對AspectJ的支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.1.4.RELEASE</version> </dependency>
- 定義接口
public interface PojoDao { public void test(); }
- 定義接口實現類
public class PojoDaoImpl implements PojoDao { @Override public void test() { System.out.println("核心測試方法"); } }
- 定義切面類(橫切處理類)
package com.wener.example.aop.pojo; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * 普通的java bean 該類不再需要實現任何接口或繼承抽象類 */ public class PojoAspect { /** * 前置通知 * @param jp */ public void before(JoinPoint jp) { System.out.println("前置通知"); System.out.println("方法名:" + jp.getSignature()); System.out.println("落午,參數:" + jp.getArgs().length); System.out.println("代理對象:" + jp.getTarget()); } /** * 后置通知 * @param jp */ public void after(JoinPoint jp) { System.out.println("后置通知"); } /** * 返回值通知 * @param joinPoint */ public void afterReturning(JoinPoint joinPoint) { System.out.println(joinPoint); } /** * 拋出異常通知 * * @param joinPoint */ public void afterThrowing(JoinPoint joinPoint) { } /** * 環(huán)繞通知 * * @param pjd ProceedingJoinPoint類型的參數可以決定是否執(zhí)行目標方法谎懦。 * 而且環(huán)繞通知必須有返回值,返回值即為目標方法的返回值 * @return */ public Object surround(ProceedingJoinPoint pjd) { return null; } }
- 在spring配置文件中配置
aop:advisor溃斋,是有順序的界拦,必須放在aop:pointcut之后 <?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 注意 1> 導入 xmlns:aop="http://www.springframework.org/schema/aop" 空間 2> Spring AOP 配置都必須定義在 <aop:config>元素內部 3> 在 <aop:config> 中,注冊切面<aop:aspect> 注冊切入點 4> 聲明切入點<aop:pointcut> 5> 在<aop:aspect>中,配置5種通知類型 <aop:after> <aop:around> <aop:after-returning> <aop:after-throwing> <aop:before> --> <!-- 注冊被代理的目標對象 --> <bean id="pojoDao" class="com.wener.example.aop.pojo.PojoDaoImpl"/> <!--注冊通知類--> <bean id="aspect" class="com.wener.example.aop.pojo.PojoAspect"/> <!-- AOP配置 --> <!--聲明切面--> <aop:config> <!--聲明切入點--> <aop:pointcut id="pointcut" expression="execution(* com.wener.example.aop.pojo.*.*(..))"/> <aop:aspect ref="aspect"> <aop:after method="after" pointcut-ref="pointcut"/> <aop:around method="surround" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/> <aop:before method="before" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
- 舉例說明
1>在spring配置文件中注冊 <bean id="sleepHelper" class="com.werner.webapp.aop.base.SleepHelper"> </bean> 2>第二步配置切入點 Spring使用org.springframework.aop.support.JdkRegexpMethodPointcut來定義正則表達式切點 1.使用正則表達式 2.使用AspectJ表達式 <bean id="spleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> <property name="pattern" value=".*sleep"/> //pattern屬性指定了正則表達式梗劫,它匹配所有的sleep方法 </bean> 3>配置通知 org.springframework.aop.support.DefaultPointcutAdvisor <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="sleepHelper"/> <property name="pointcut" ref="sleepPointcut"/> </bean> 4>切點僅僅是定義了故事發(fā)生的地點享甸,還有故事發(fā)生的時間以及最重要的故事的內容,就是通知了,我們需要把通知跟切點結合起來梳侨,我們要使用的通知者是: org.springframework.aop.support.DefaultPointcutAdvisor <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" ref="sleepHelper"/> <property name="pointcut" ref="sleepPointcut"/> </bean> 5> 切入點和通知都配置完成蛉威,接下來該調用ProxyFactoryBean產生代理對象了 <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="human"/> <property name="interceptorNames" value="sleepHelperAdvisor" /> <property name="proxyInterfaces" value="test.spring.AOP.bean.Sleepable" /> </bean>
六、基于@Aspect注解編程(重點)
1走哺、說明
Spring 使用了和AspectJ 一樣的注解并使用AspectJ來做切入點解析和匹配蚯嫌。但是,AOP在運行時仍舊是純的Spring AOP丙躏,并不依賴于AspectJ的編譯器或者織入器(weaver)(編譯器與織入器暫時不要管)
2择示、啟用@AspectJ支持
- 說明
為了在Spring中使用@AspectJ切面,你首先必須啟用Spring對@AspectJ切面配置的支持晒旅,并確保開啟自動代理栅盲。自動代理是指Spring會判斷一個bean是否使用了一個或多個切面通知,并據此自動生成相應的代理以攔截其方法調用废恋,并且確保通知在需要時執(zhí)行 - 新建spring-aspect.xml配置文件
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.wener.example.aop.aspect"/> <!-- 有了這個Spring就能夠自動掃描被@Aspect標注的切面了 --> <!-- 開啟自動代理 --> <aop:aspectj-autoproxy/> </beans>
2谈秫、聲明一個切面
- 說明
在代碼中定義一個類任意在類上使用@Aspect注解 - 示例代碼
import org.aspectj.lang.annotation.Aspect; @Aspect public class LogAspect { }
3、聲明一個切入點
- 說明
切入點決定了連接點關注的內容拴签,使得我們可以控制通知什么時候執(zhí)行孝常。Spring AOP只支持Spring bean的方法執(zhí)行連接點。所以你可以把切入點看做是Spring bean上方法執(zhí)行的匹配蚓哩。一個切入點聲明有兩個部分:-
包含名字和任意參數的簽名:一個切入點簽名通過一個普通的方法定義來提供构灸,并且切入點表達式使用
@Pointcut
注解來表示(作為切入點簽名的方法必須返回void
類型) - 切入點表達式:切入點表達式決定了我們關注哪些方法的執(zhí)行,詳細表達式語法后面在說。
-
包含名字和任意參數的簽名:一個切入點簽名通過一個普通的方法定義來提供构灸,并且切入點表達式使用
- 語法格式
@Pointcut(value="", argNames = "")
- 參數說明
- value
指定切入點表達式 - argNames
指定命名切入點方法參數列表參數名字岸梨,可以有多個用“喜颁,”分隔,這些參數將傳遞給通知方法同名的參數
- value
- 示例代碼
@Aspect public class LogAspect { // 也可以在通知上定義,當需要復用切入點的時候 @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") // 返回值 必須是void類型 public void log() { } }
- 備注
切入點的定義是非必要的,也可以直接在通知上使用切入點表達式
4曹阔、聲明通知
4.1半开、說明
通知是跟一個切入點表達式關聯(lián)起來的,并且在切入點匹配的方法執(zhí)行之前或者之后或者前后運行赃份。 切入點表達式可能是指向已命名的切入點的簡單引用或者是一個已經聲明過的切入點表達式寂拆,通知的類型就是我們前面提到過的類型
4.2奢米、前置通知
- 說明
在關注點執(zhí)行前運行的方法,切面里使用@Before
注解聲明前置通知 - 語法格式
@Before(value = "", argNames = "")
- 參數說明
- value :指定切入點表達式或切入點名字纠永;
- argNames: 用來接收AspectJ表達式中的參數,并指定通知方法中的參數
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.Before; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點表達式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @Before 前置通知 * value:指定切入點表達式或命名切入點鬓长; * argNames:與Schema方式配置中的同義; */ @Before("logPointcut()") public void before() { System.out.println("前置通知"); } }
4.3尝江、后置通知(最終通知)
- 說明
不論一個方法是如何結束的涉波,最終通知都會運行。使用@After
注解來聲明炭序。最終通知必須準備處理正常返回和異常返回兩種情況啤覆。通常用它來釋放資源。相當于異常處理里finally的代碼 - 語法格式
@After(value = "", argNames = "")
- 參數
- value :指定切入點表達式或切入點名字惭聂;
- **argNames: **用來接收AspectJ表達式中的參數,并指定通知方法中的參數
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點表達式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @After 后置通知 */ @After(value = "logPointcut()") public void after() { System.out.println("后置通知"); } }
4.4窗声、返回通知
- 說明
返回后通知通常在一個匹配的方法返回的時候執(zhí)行。使用@AfterReturning
注解來聲明 - 語法格式
@AfterReturning(value="",pointcut="",returning="",argNames="")
- 參數說明
- value:指定切入點表達式或切入點名字辜纲;
- pointcut:指定切入點表達式或命名切入點嫌佑,如果指定了將覆蓋value屬性的,pointcut具有高優(yōu)先級侨歉;
- returning:如果你想獲取方法的返回值可以使用該參數,在通知方法中定義參數就可以了
- argNames:用來接收AspectJ表達式中的參數,并指定通知方法中的參數
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點表達式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * 不獲取方法的返回值 */ @AfterReturning(value = "logPointcut()") public void AfterReturning1() { System.out.println("異常通知"); } /** * 獲取方法的返回值 * returning的賦值的名字,必須跟通知方法中參數的名字保持一致 */ @AfterReturning(value = "logPointcut()", returning = "val") public Object afterReturning(Object val) { System.out.println("返回后通知"); return val; } }
4.5屋摇、異常通知
- 說明
拋出異常通知在一個方法拋出異常后執(zhí)行。使用@AfterThrowing
注解來聲明 - 語法格式
@AfterThrowing(value="",pointcut="",throwing="",argNames="")
- 參數說明
- value:指定切入點表達式或命名切入點幽邓;
- pointcut:指定切入點表達式或命名切入點炮温,如果指定了將覆蓋value屬性的,pointcut具有高優(yōu)先級牵舵;
- throwing:異常類型柒啤;并且在通知方法中定義異常參數;
- argNames:用來接收AspectJ表達式中的參數,并指定通知方法中的參數畸颅;
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點表達式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @AfterThrowing 異常通知 * value:指定切入點表達式或命名切入點担巩; * throwing:異常類型。 */ @AfterThrowing("logPointcut()") public void afterThrowing() { System.out.println("異常通知"); } /** * 如果想要限制通知只在某種特定的異常被拋出的時候匹配没炒,同時還想知道異常的一些信息涛癌。 * 那我們就需要使用throwing屬性聲明響應 */ @AfterThrowing(value = "logPointcut()", throwing = "exception") public void afterThrowing(Exception exception) { System.out.println("異常通知"); } }
4.6、環(huán)繞通知
- 說明
環(huán)繞通知在一個方法執(zhí)行之前和之后執(zhí)行送火。它使得通知有機會 在一個方法執(zhí)行之前和執(zhí)行之后運行拳话。而且它可以決定這個方法在什么時候執(zhí)行,如何執(zhí)行种吸,甚至是否執(zhí)行弃衍。 環(huán)繞通知經常在某線程安全的環(huán)境下,你需要在一個方法執(zhí)行之前和之后共享某種狀態(tài)的時候使用坚俗。 請盡量使用最簡單的滿足你需求的通知镜盯。(比如如果簡單的前置通知也可以適用的情況下不要使用環(huán)繞通知)岸裙。- 使用
@Around
注解; - 環(huán)繞通知需要攜帶ProceedingJoinPoint類型的參數速缆;
- 且環(huán)繞通知必須有返回值哥桥,返回值即為有目標方法的返回值。
- 使用
- 語法格式
@Around(value = "", argNames = "")
- 參數
- value :指定切入點表達式或切入點名字激涤;
- **argNames: **用來接收AspectJ表達式中的參數,并指定通知方法中的參數
- 示例代碼
import org.springframework.stereotype.Component; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint; @Aspect @Component public class LogAspect { /** * @Pointcut() 切入點表達式 */ @Pointcut("execution(* com.wener.example.aop.aspect.*.*(..))") public void logPointcut() { } /** * @Around 環(huán)繞通知 * 比如 緩存切面,如果緩存中有值判呕,就返回該值倦踢,否則調用proceed()方法 * value:指定切入點表達式或命名切入點; * 注意 第一個參數必須是 ProceedingJoinPoint對象 具體這個類的更多詳細使用看附錄: */ @Around(value = "logPointcut()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("環(huán)繞通知1"); Object obj = pjp.proceed(); System.out.println("環(huán)繞通知2"); return obj; } }
4.7侠草、通知參數
- 說明
若想要在通知方法獲取被通知方法的參數共有兩種方式:自動獲取辱挥、手動指定- 自動獲取參數:通知類型可以通過參數JoinPoint或者 ProceedingJoinPoint 自動獲取被通知方法的參數值并調用該方法
- 手動指定參數:即在配置切面時,需在切面的通知與切面的切點中明確指定參數边涕。
- 手動指定
- 在@pointcut中切入表達式中使用args聲明匹配的參數,注意使用&&連接args
- 在@pointcut中切入表達式中使用參數argNames用來接收AspectJ表達式中的參數晤碘,
argNames屬性是用于指定在表達式中應用的參數名與Advice方法參數是如何對應的 - 在通知方法中定義參數
- 手動獲取指定參數
import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdviceParamsAspect { // 注意參數的個數必須一致,否則匹配不到 @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } }
- 混用使用
當同時采用自動獲取參數與手動指定參數時,自動獲取參數必須是第一個參數功蜓,即ProceedingJoinPoint 等參數并需是通知方法定義的第一個參數import org.aopalliance.intercept.Joinpoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogAdviceParamsAspect { // args园爷、argNames的參數名與testArgs()方法中參數名 保持一致 @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)", argNames = "id,name") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } // 也可以不用argNames @Before(value = "execution(* com.wener.example.aop.aspect.*.*(..))&& args(id,name)") public void testArgs(Object id, Object name) { System.out.println(id); System.out.println(name); } @Around(value = "execution(* com.wener.example.aop.aspect.*.*(..))&&(args(id,name,..))", argNames = "pjp,id,name") public Object testAroundArgs(ProceedingJoinPoint pjp, Object id, Object name) throws Throwable { System.out.println("Around之前"); Object obj = pjp.proceed(); System.out.println(); return obj; } }
4.8 、引入
- 說明
有時候有一組共享公共行為類式撼。在OOP中童社,它們必須擴展相同的基類或者實現相同的接口。此外著隆,Java的單繼承機制僅允許一個類最多擴展一個基類扰楼。所以,不能同時從多個實現類中繼承行為美浦。
解決方案:引入是AOP中的一種特殊的通知弦赖。它允許為一個接口提供實現類,使對象動態(tài)的實現接口浦辨。就像對象在運行時擴展了實現類蹬竖。而且,可以用多個實現類將多個接口同時引入對象流酬。這可以實現與多重繼承相同的效果案腺。 - 在開發(fā)中用的不是很多,所以不做過多的分析
5、聲明代理類
- 說明
被代理的對象,跟前面說的一樣,代理接口或者類都可以 - 示例代碼
public interface AspectDao { public void test(); public void testParams(int id, String name); public void testParams(Joinpoint jp, int id, String name); } @Component("aspectDao") public class AspectDaoImpl implements AspectDao { @Override public void test() { System.out.println("核心測試方法"); } @Override public void testParams(int id, String name) { System.out.println("帶參數的方法:" + "ID:" + id + "name:" + name); } }
6康吵、測試
- 示例代碼
ApplicationContext context = new ClassPathXmlApplicationContext("spring-aspect.xml"); AspectDao dao = (AspectDao) context.getBean("aspectDao"); dao.test(); dao.testParams(1,"hello");
7劈榨、總結
- 使用@Aspect將POJO聲明為切面;
- 在切面類中使用@Pointcut進行命名切入點聲明晦嵌;
- 定義通知方法,使用5中注解聲明同辣,其中value用于定義切入點表達式或引用命名切入點拷姿;
- 配置文件需要使用
<aop:aspectj-autoproxy/>
來開啟注解風格的@AspectJ支持; - 將切面類和POJO類注冊到Spring容器中