1.前置通知
在目標方法執行以前執行執行的通知。spring
前置通知方法,能夠沒有參數,也能夠額外接收一個JoinPoint,Spring會自動將該對象傳入,表明當前的鏈接點,經過該對象能夠獲取目標對象 和 目標方法相關的信息。express
注意,若是接收JoinPoint,必須保證其爲方法的第一個參數,不然報錯。設計模式
配置方式:app
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd "> <context:annotation-config></context:annotation-config> <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan> <aop:config proxy-target-class="true"> <!-- 配置切入點 --> <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImpl.addUser(..))" id="pc01"/> <!-- 配置切面 --> <aop:aspect ref="firstAspect">
<<!-- 前置通知 --> <aop:before method="before" pointcut-ref="pc01"/> </aop:aspect> </aop:config> </beans>
package cn.tedu.service; import org.springframework.stereotype.Service; /** * UserServiceImple:目標對象 */ @Service("userService") public class UserServiceImple implements UserService { @Override public void addUser(String name) { System.out.println("增長用戶。。"); } @Override public void updateUser() { System.out.println("修改用戶。。"); } @Override public void deleteUser() { System.out.println("刪除用戶。。"); } @Override public void query() { System.out.println("查詢用戶。。"); } }
package cn.tedu.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.springframework.stereotype.Component; /** * FirstAspect:切面代碼 */ @Component public class FirstAspect { public void before(JoinPoint jp){ // 能夠選擇額外的傳入一個JoinPoint鏈接點對象,必須用方法的第一個參數接收。 Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); // 經過JoinPoint對象獲取更多信息 String name = signature.getName(); System.out.println("1 -- before...["+clz+"]...["+name+"]..."); } }
package cn.tedu.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.tedu.service.UserService; /** * AOPTest:測試代碼 */ public class AOPTest { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); userService.addUser("cjj"); // 一個鏈接點 } }
執行結果:ide
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]... 增長用戶。。
2.環繞通知
在目標方法執行以前和以後均可以執行額外代碼的通知。post
在環繞通知中必須顯式的調用目標方法,目標方法纔會執行,這個顯式調用時經過ProceedingJoinPoint來實現的,能夠在環繞通知中接收一個此類型的形參,spring容器會自動將該對象傳入,注意這個參數必須處在環繞通知的第一個形參位置。測試
**要注意,只有環繞通知能夠接收ProceedingJoinPoint,而其餘通知只能接收JoinPoint。spa
環繞通知須要返回返回值,不然真正調用者將拿不到返回值,只能獲得一個null。設計
環繞通知有控制目標方法是否執行、有控制是否返回值、有改變返回值的能力。代理
環繞通知雖然有這樣的能力,但必定要慎用,不是技術上不可行,而是要當心不要破壞了軟件分層的「高內聚 低耦合」的目標。
配置方式:
<!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pc1"/>
public Object around(ProceedingJoinPoint jp) throws Throwable{ System.out.println("1 -- around before..."); Object obj = jp.proceed(); //--顯式的調用目標方法 System.out.println("1 -- around after..."); return obj; }
運行結果:
1 -- around before... 增長用戶。。 1 -- around after...
3.後置通知
在目標方法執行以後執行的通知。
在後置通知中也能夠選擇性的接收一個JoinPoint來獲取鏈接點的額外信息,可是這個參數必須處在參數列表的第一個。
配置方式:
<!-- 後置通知 --> <aop:after-returning method="afterReturn" pointcut-ref="pc1"/>
public void afterReturn(JoinPoint jp){ Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); String name = signature.getName(); System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]..."); }
執行結果:
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]... 1 -- around before... 增長用戶。。 1 -- around after... 1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...
在後置通知中,還能夠經過配置獲取返回值
必定要保證JoinPoint處在參數列表的第一位,不然拋異常
配置方式:
<!-- 後置通知 --> <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/>
public void afterReturn(JoinPoint jp, Object msg){ Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); String name = signature.getName(); System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]..."); }
執行結果:
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]... 1 -- around before... 增長用戶。。 1 -- around after... 1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]...
4.異常通知
在目標方法拋出異常時執行的通知
能夠配置傳入JoinPoint獲取目標對象和目標方法相關信息,但必須處在參數列表第一位
另外,還能夠配置參數,讓異常通知能夠接收到目標方法拋出的異常對象。
配置方法:
<!-- 異常通知 --> <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/>
public void afterThrow(JoinPoint jp,Throwable e){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1afterThrow..["+clz+"]..["+name+"].."+e.getMessage()); }
代碼報異常後
執行結果:
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]... 1 -- around before... 1 -- afterThrow..[class cn.tedu.service.UserServiceImple]..[addUser]../ by zero
5.最終通知
是在目標方法執行以後執行的通知。
和後置通知不一樣之處在於,後置通知是在方法正常返回後執行的通知,若是方法沒有正常返-例如拋出異常,則後置通知不會執行。
而最終通知不管如何都會在目標方法調用事後執行,即便目標方法沒有正常的執行完成。
另外,後置通知能夠經過配置獲得返回值,而最終通知沒法獲得。
最終通知也能夠額外接收一個JoinPoint參數,來獲取目標對象和目標方法相關信息,但必定要保證必須是第一個參數。
配置方式:
<!-- 最終通知 --> <aop:after method="after" pointcut-ref="pc1" />
public void after(JoinPoint jp){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1 -- after..["+clz+"]..["+name+"]..."); }
執行結果:
1 -- before...[class cn.tedu.service.UserServiceImple]...[addUser]... 1 -- around before... 增長用戶。。 1 -- around after... 1 -- afterReturn...[class cn.tedu.service.UserServiceImple]...[addUser]...[cjj]... 1 -- after..[class cn.tedu.service.UserServiceImple]..[addUser]... cjj
源碼
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd " > <context:annotation-config></context:annotation-config> <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan> <!-- proxy-target-class屬性值決定是基於接口的仍是基於類的代理被建立 --> <aop:config proxy-target-class="true"> <!-- 配置切入點 --> <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImple.addUser(..))" id="pc1"/> <!-- 配置切入面 --> <aop:aspect ref="firstAspect"> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="pc1"/> <!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pc1"/> <!-- 後置通知 --> <!-- <aop:after-returning method="afterReturn" pointcut-ref="pc1"/> --> <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/> <!-- 異常通知 --> <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/> <!-- 最終通知 --> <aop:after method="after" pointcut-ref="pc1" /> </aop:aspect> </aop:config> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd " > <context:annotation-config></context:annotation-config> <context:component-scan base-package="cn.tedu.service,cn.tedu.aop"></context:component-scan> <!-- proxy-target-class屬性值決定是基於接口的仍是基於類的代理被建立 --> <aop:config proxy-target-class="true"> <!-- 配置切入點 --> <aop:pointcut expression="execution(* cn.tedu.service.UserServiceImple.addUser(..))" id="pc1"/> <!-- 配置切入面 --> <aop:aspect ref="firstAspect"> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="pc1"/> <!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pc1"/> <!-- 後置通知 --> <!-- <aop:after-returning method="afterReturn" pointcut-ref="pc1"/> --> <aop:after-returning method="afterReturn" pointcut-ref="pc1" returning="msg"/> <!-- 異常通知 --> <aop:after-throwing method="afterThrow" pointcut-ref="pc1" throwing="e"/> <!-- 最終通知 --> <aop:after method="after" pointcut-ref="pc1" /> </aop:aspect> </aop:config> </beans>
package cn.tedu.service; /** * 接口 */ public interface UserService { public String addUser(String name); public void updateUser(); public void deleteUser(); public void query(); }
package cn.tedu.service; /** * 接口 */ public interface UserService { public String addUser(String name); public void updateUser(); public void deleteUser(); public void query(); }
package cn.tedu.service; import org.springframework.stereotype.Service; /** * UserServiceImple:目標對象 */ @Service("userService") public class UserServiceImple implements UserService { @Override public String addUser(String name) { // int i = 1/0; System.out.println("增長用戶。。"); return "cjj"; } @Override public void updateUser() { System.out.println("修改用戶。。"); } @Override public void deleteUser() { System.out.println("刪除用戶。。"); } @Override public void query() { System.out.println("查詢用戶。。"); } }
package cn.tedu.service; import org.springframework.stereotype.Service; /** * UserServiceImple:目標對象 */ @Service("userService") public class UserServiceImple implements UserService { @Override public String addUser(String name) { // int i = 1/0; System.out.println("增長用戶。。"); return "cjj"; } @Override public void updateUser() { System.out.println("修改用戶。。"); } @Override public void deleteUser() { System.out.println("刪除用戶。。"); } @Override public void query() { System.out.println("查詢用戶。。"); } }
package cn.tedu.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.springframework.stereotype.Component; /** * FirstAspect:切面代碼 */ @Component public class FirstAspect { public void before(JoinPoint jp){ // 能夠選擇額外的傳入一個JoinPoint鏈接點對象,必須用方法的第一個參數接收。 Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); // 經過JoinPoint對象獲取更多信息 String name = signature.getName(); System.out.println("1 -- before...["+clz+"]...["+name+"]..."); } public Object around(ProceedingJoinPoint jp) throws Throwable{ System.out.println("1 -- around before..."); Object obj = jp.proceed(); //--顯式的調用目標方法 System.out.println("1 -- around after..."); return obj; } public void afterReturn(JoinPoint jp, Object msg){ Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); String name = signature.getName(); System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]..."); } public void afterThrow(JoinPoint jp,Throwable e){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1 -- afterThrow..["+clz+"]..["+name+"].."+e.getMessage()); } public void after(JoinPoint jp){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1 -- after..["+clz+"]..["+name+"]..."); } }
package cn.tedu.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.springframework.stereotype.Component; /** * FirstAspect:切面代碼 */ @Component public class FirstAspect { public void before(JoinPoint jp){ // 能夠選擇額外的傳入一個JoinPoint鏈接點對象,必須用方法的第一個參數接收。 Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); // 經過JoinPoint對象獲取更多信息 String name = signature.getName(); System.out.println("1 -- before...["+clz+"]...["+name+"]..."); } public Object around(ProceedingJoinPoint jp) throws Throwable{ System.out.println("1 -- around before..."); Object obj = jp.proceed(); //--顯式的調用目標方法 System.out.println("1 -- around after..."); return obj; } public void afterReturn(JoinPoint jp, Object msg){ Class clz = jp.getTarget().getClass(); Signature signature = jp.getSignature(); String name = signature.getName(); System.out.println("1 -- afterReturn...["+clz+"]...["+name+"]...["+msg+"]..."); } public void afterThrow(JoinPoint jp,Throwable e){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1 -- afterThrow..["+clz+"]..["+name+"].."+e.getMessage()); } public void after(JoinPoint jp){ Class clz = jp.getTarget().getClass(); String name = jp.getSignature().getName(); System.out.println("1 -- after..["+clz+"]..["+name+"]..."); } }
package cn.tedu.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.tedu.service.UserService; /** * AOPTest:測試代碼 */ public class AOPTest { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); String result = userService.addUser("cjj"); // 一個鏈接點 System.out.println(result); } }
package cn.tedu.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.tedu.service.UserService; /** * AOPTest:測試代碼 */ public class AOPTest { @Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); String result = userService.addUser("cjj"); // 一個鏈接點 System.out.println(result); } }
五種通知的執行順序
1.在目標方法沒有拋出異常的狀況下
前置通知
環繞通知的調用目標方法以前的代碼
目標方法
環繞通知的調用目標方法以後的代碼
後置通知
最終通知
2.在目標方法拋出異常的狀況下
前置通知
環繞通知的調用目標方法以前的代碼
目標方法 拋出異常 異常通知
最終通知
3.若是存在多個切面
多切面執行時,採用了責任鏈設計模式。
切面的配置順序決定了切面的執行順序,多個切面執行的過程,相似於方法調用的過程,在環繞通知的proceed()執行時,去執行下一個切面或若是沒有下一個切面執行目標方法,從而達成了以下的執行過程:
若是目標方法拋出異常:
五種通知的常見使用場景
環繞通知 |
控制事務 權限控制 |
後置通知 |
記錄日誌(方法已經成功調用) |
異常通知 |
異常處理 控制事務 |
最終通知 |
記錄日誌(方法已經調用,但不必定成功) |