參考:https://mp.weixin.qq.com/s/vfPKWYcUa4yjJ4cnmIEaxQspring
AOP特色:
express
面向切面編程, 利用AOP對業務邏輯的各個部分進行抽取公共代碼, 下降耦合度, 提升代碼重用性, 同時提升開發效率.編程
採起橫向抽取, 取代傳統縱向繼承體系重複性代碼緩存
解決事務管理, 性能監視, 安全檢查, 緩存, 日誌等問題安全
Spring AOP在運行期, 經過反向代理的方式解決類加載, 屬性注入bash
AspectJ是基於Java的AOP框架, 在Spring使用AspectJ實現AOPapp
AOP實現機制:
底層採用代理機制實現AOP.框架
2種代理機制:
2.0、採用JDK的的動態代理Proxy;
2.一、採用CGLIB字節碼加強ide
AOP專業術語:
Target: 目標類 ( 須要被代理的類 )
Joinpoint: 鏈接點 ( 可能須要使用的目標類方法 )
Advice: 加強代碼 ( 對鏈接點加強的代碼 )
PointCut: 切入點 ( 可能須要 Advice 加強的鏈接點 )
Weaving: 織入 ( 建立代理對象 proxy 執行切入點的方法 )
Aspect: 切面 ( Advice 與 PointCust的結合 )函數
下面經過JDK動態代理和CGLIB字節碼加強兩種方式實現AOP操做:
當目標類沒有實現接口或者須要更好的性能的時候就須要考慮使用CGLIB實現動態Proxy
JDK動態代理:
1.目標類: Service層
2.切面類: 使用JDK動態代理對Service層代碼加強
3.工廠類: 得到proxy對象
目標類:
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
複製代碼
切面, 加強鏈接點:
public class MyAspect {
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
}
複製代碼
靜態代理對象工廠:
public class MyProxyBeanFactory {
public static UserService createService(){
final UserService userService = new UserServiceImpl();
final MyAspect myAspect = new MyAspect();
//經過userService得到代理對象
UserService proxyService = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler(){
//Proxy代理對象, method代理類的目標方法, args目標方法參數
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
//織入橫向代碼
myAspect.before();
//執行代理類的方法
Object obj = method.invoke(userService, args);
myAspect.after();
//返回執行代理方法的返回值
return obj;
}
});
//返回代理對象
return proxyService;
}
}
複製代碼
applicationContext.xml:
<bean id="userService" class="com.f_aop.jdkproxy.MyProxyBeanFactory" factory-method="createService"></bean>
複製代碼
測試方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/jdkproxy/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}複製代碼
CGLIB字節碼加強動態代理:
原理: cglib動態生成一個代理類的子類, 子類重寫代理類的全部不是final的方法, 在子類中採用方法攔截技術攔截全部父類的方法調用, 順勢織入切面邏輯, 實現AOP, 它比JDK動態代理要快。但其操做流程與JDK動態代理一致。
下面只給出靜態代理工廠的代碼:
public class MyProxyBeanFactory {
public static UserService createService(){
final UserService userService = new UserServiceImpl();
final MyAspect myAspect = new MyAspect();
//建立代理
Enhancer enhancer = new Enhancer();
//肯定父類
enhancer.setSuperclass(userService.getClass());
//向代理對象的方法中織入切面代碼
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.before();
//執行目標方法
//Object obj = method.invoke(userService, args);
//執行目標方法, 效果與method.invoke(userService, args);
//通常執行這個方法, 速度要快一些
Object obj = methodProxy.invoke(proxy, args);
myAspect.after();
//返回目標方法返回值
return obj;
}
});
//使用enhancer建立代理對象
return (UserService) enhancer.create();
}
}
複製代碼
cglib的整個流程與JDK的動態代理都是同樣的, 就在底層處理接口和加載字節碼文件有區別。
AOP聯盟定義Advice規範, 編寫Advice代碼需實現Advice接口。
Spring按照Advice在目標類中方法的鏈接點的位置, 分爲5類:
前置通知:
實現接口: org.springframework.aop.MethodBeforeAdvice
只在目標方法前進行代碼加強;
後置通知:
實現接口: org.springframework.aop.AfterReturningAdvice
只在目標方法後進行代碼加強;
環繞通知( 必須手動執行目標方法 ):
實現接口: org.springframework.aop.MethodInterceptor
只在目標方法先後進行代碼加強; 效果等同於JDK的Proxy/cglib
異常通知:
實現接口: org.springframework.aop.ThrowsAdvice
在拋出異常的時候進行代碼加強;
引介通知:
實現接口: org.springframework.aop.IntroductionInterceptor
只在目標類中增添一些新的方法和屬性;
複製代碼
使用Spring提供的ProxyFactoryBean模擬代理過程, 實現Spring的AOP:
使用環繞型通知進行演示(目標類與前面的同樣):
1.導入aop, aopalliance jar包
2.切面類(MyAspect)實現MethodInterceptor接口
3.實現MethodInterceptor中invoke方法, 手動織入橫向代碼
4.在applicationContext.xml中配置, 使用Spring提供的ProxyFactoryBean對目標類實現代理
複製代碼
演示代碼:
切面類:
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("前");
//手動執行目標方法
Object obj = mi.proceed();
System.out.println("後");
//返回目標方法執行的返回值
return obj;
}
}
複製代碼
配置applicationContext.xml:
<!-- 得到目標類對象 -->
<bean id="userService" class="com.f_aop.methodInterceptor.UserServiceImpl"></bean>
<!-- 建立切面類 -->
<bean id="myAspect" class="com.f_aop.methodInterceptor.MyAspect"></bean>
<!-- 建立代理類, 使用Spring配備的代理工廠 -->
<bean id="proxyService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定接口 -->
<property name="interfaces" value="com.f_aop.methodInterceptor.UserService"></property>
<!-- 肯定目標類對象 -->
<property name="target" ref="userService"></property>
<!-- 肯定Aspect, 因爲interceptorNames的形參值是String[], 因此使用value, 而非ref -->
<property name="interceptorNames" value="myAspect"></property>
<property name="optimize" value="true"></property>
</bean>
複製代碼
測試方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/methodInterceptor/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
//使用proxyService, 而非userService
//經過代理對象執行Advice
UserService userService = (UserService) applicationContext.getBean("proxyService");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}複製代碼
運行結果:
前
add User
後
前
update User
後
前
delete User
後
複製代碼
applicationContext.xml中建立代理類標籤詳解:
ProxyFactoryBean: Spring的代理工廠,生成代理對象
interfaces: 目標類實現的接口, 多個值使用<array><value>肯定每一個值
單個值的時候直接使用value
target: 肯定目標類
interceptorNames: 肯定切面類的名稱, 類型爲String[], 使用value, 切記不使用ref
optimize: 強制底層使用cglib
當沒有設置optimize的值時:
Spring自動判斷, 沒有接口使用cglib, 有接口使用jdk
顯式設置optimize, 若是聲明optimize=true,不管是否有接口,都採用cglib
複製代碼
上面這種代理實現, 是在applicationContext.xml配置文件中模擬代理工廠產生代理對象, 在測試函數中得到是容器產生的代理對象proxyService.
利用AspectJ簡化Spring中ProxyFactoryBean的配置:
使用環繞型通知進行演示, 編寫流程:
1.導入aspectj.weaver jar包.
2.在applicationContext.xml配置文件中添加aop的xmlns和xsi限制
3.在配置文件中配置切面類(MyAspect)的切入點(PointCut), 特殊切面(包含advice與PointCut).
首先使用expression表達式配置切入點(PointCut), 即目標類中哪些方法須要加強.
而後配置特殊切面, 對配置好的切入點, 使用加強點advice進行加強.
複製代碼
下面使用代碼演示, 由於只需修改配置文件與測試類, 只給出配置文件代碼:
<?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">
<bean id="userService" class="com.f_aop.aspectJ.UserServiceImpl"></bean>
<!-- 建立切面類 -->
<bean id="myAspect" class="com.f_aop.aspectJ.MyAspect"></bean>
<!-- 配置特殊切面 -->
<!-- proxy-target-class配置是否使用cglib -->
<aop:config proxy-target-class="true">
<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJ.*.*(..))"/>
<aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
</aop:config>
</beans>
<!--
aop:config: 配置AOP
proxy-target-class: 配置是否強行使用cglib, 效果與前面的optimize同樣
pointcut: 配置切入點.
expression: 配置切入點表達式,用於得到目標類中須要加強的目標方法.
advisor: 配置切入點與切面類, 指明哪些方法須要加強.
advice-ref: 切入類對象引用.
pointcut-ref: 切入點引用.
-->
複製代碼
相比於Spring提供的ProxyFactoryBean, AspectJ更加便捷。
AspectJ是基於Java的AOP框架, 用於自定義AOP開發.
切入點表達式
用於描述目標類中的目標方法, 指定哪些方法可做爲切入點.
下面說明切入點表達式寫法:
語法: expression = " execution( 修飾符 返回值 包.類.方法名(參數) throws 異常 ) "
切入表達式針對每一個部分的編寫規則以下
修飾符(通常省略):
public 公共方法
* 任意方法
返回值(不能省略):
void 沒有返回值
String 返回值爲字符串
* 返回值任意
包(可省略):
com.demo 固定包
com.demo.* demo下任意子包,例如:com.demo.aop
com.demo.. demo下全部包(包含本身,也包含多級子包)
com.demo.*.service.. demo下任意子包, 子包中包含固定包service,service下全部包
類(可省略):
UserServiceImpl 指定類
*Impl 以Impl結尾的類
User* 以User開頭的類
* 任意類
方法名(不能省略):
addUser 指定方法
add* 以add開頭的方法
*User 以User結尾的方法
* 任意方法
參數:
() 無參
(int) 一個int型參數
(int, int) 兩個int型參數
(..) 任意參數
throws(可省略, 通常不寫)
複製代碼
下面給出一個例子:
1.execution(* com.demo.*.service..*.*(..))
指定com.demo下具備固定service包的任意子包中任意類中的任意方法,方法返回值任意.
其餘種類的expression表達式:
1.within: 匹配包或子包中的方法.
within(com.demo..*) demo下全部包中任意類中任意方法
2.this: 匹配實現接口的類的代理對象中方法:
this(com.demo.aop.user.UserDAO) 匹配UserDAO中實現類代理對象任意方法.
3.target: 匹配實現接口的類的目標對象中方法:
target(com.demo.aop.user.UserDAO) 匹配UserDAO中實現類目標對象任意方法.
4.args: 匹配參數格式符合標準的方法
args(int, int) 匹配形參值類型爲int, int的任意方法.
5.bean(id): 匹配指定bean全部方法
bean('userService') 匹配userService中全部方法
複製代碼
AspectJ通知類型
與AOP聯盟同樣, AspectJ也定義了多種通知類型.
AspectJ總共6中通知類型:
1.before: 前置通知,用於校驗數據
在目標方法以前執行, 若拋出異常, 組織目標方法運行.
2.afterReturning: 後置通知,常規數據處理
目標方法執行後執行, 可得到目標方法的返回值.目標方法出現異常, 方法不執行.
3.around: 環繞通知
目標方法先後, 可阻止目標方法執行, 必須手動執行目標方法.
4.afterThrowing: 拋出異常通知
目標方法出現異常後執行, 沒有出現異常就不執行.
5.after: 最終通知, 資源回收, 相似finally方法
方法執行完, 不管方法中是否出現異常, 都將執行.
複製代碼
環繞通知與其餘通知之間的聯繫:
try{
//前置: before
//手動執行目標方法
//後置: afterReturning
} catch(){
//捕獲異常: afterThrowing
} finally{
//最終通知: after
}
複製代碼
從上面看出, 徹底可使用環繞通知模擬前置通知, 後置通知, 環繞通知結合AfterThrowing, After實現AOP.
aop標籤對應的通知類型種類:
使用AOP聯盟進行切面類編寫, 須要定義通知類型, 切面類必須實現特定接口(MethodInterceptor), 而後爲目標方法添加加強代碼, 相比於AOP聯盟, AspectJ只要定義切面類, 加強代碼的使用徹底交給配置文件, 避免代碼污染, 簡化操做。
基於xml配置通知類型的開發流程:
1.導入AOP聯盟, AspectJ, AOP依賴, Aspect規範 jar包.
2.編寫目標類: 接口與實現類.
3.編寫切面類: 編寫AspectJ的通知類型方法, 方法名任意, 無需實現什麼接口.
4.配置xml: 配置通知類型.
5.測試.
下面給出演示代碼, 代碼中已經給出註釋加以說明(如有不懂請在評論區留言):
目標類 ( 接口與實現類 ):
public interface UserService {
void addUser();
void updateUser();
void deleteUser();
}
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
複製代碼
切面類:
package com.f_aop.aspectJFinal;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect{
// 測試前置通知與後置通知
// public void myBefore(JoinPoint jPoint){
// System.out.println("前置通知"+jPoint.getSignature().getName());
// }
//
// public void myAfterReturning(JoinPoint jPoint, Object ret){
// System.out.println("後置通知"+jPoint.getSignature().getName()+"--"+ret);
// }
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("前置通知");
//手動執行目標方法
Object obj = joinPoint.proceed();
// 環繞通知與拋出異常通知的測試結果:
// int i = 1/0;
// 前置通知
// add User
// 拋出異常通知/ by zero
// 最終通知
System.out.println("後置通知");
return obj;
}
public void myAfterThrowing(JoinPoint jPoint, Throwable e){
System.out.println("拋出異常通知"+e.getMessage());
}
public void myAfter(JoinPoint jPoint){
System.out.println("最終通知");
}
}
複製代碼
applicationContext.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"
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">
<!-- 建立目標類對象 -->
<bean id="userService" class="com.f_aop.aspectJFinal.UserServiceImpl"></bean>
<!-- 建立切面類 -->
<bean id="myAspect" class="com.f_aop.aspectJFinal.MyAspect"></bean>
<!-- 使用 config 配置AspectJ的AOP -->
<aop:config>
<!-- 聲明切入面 -->
<aop:aspect ref="myAspect">
<!-- 配置目標方法的切入點 -->
<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJFinal.UserServiceImpl.*(..))"/>
<!--
配置通知類型的時候, method表明切入類方法, pointcut-ref表明目標類切入點.
兩者結合的意思就是目標類中哪些切入點須要切入方法進行加強.
-->
<!-- 前置通知
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
後置通知, returning用於接收目標方法執行完後的返回值
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
-->
<!-- 拋出異常通知要配合環繞通知使用, 環繞通知拋出的異常使用拋出異常通知接收 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 拋出異常, throwing="e" 表明執行目標方法後,可能會拋出的異常經過 e 進行接收 -->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 最終通知 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
複製代碼
測試方法:
@Test
public void f1(){
String XMLPATH="com/f_aop/aspectJFinal/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(XMLPATH);
UserService userService = (UserService) applicationContext.getBean("userService");
//測試AOP
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
複製代碼
基於註解的通知類型開發流程:
1.在剛開始配置註解的時候, 能夠按照 xml 中bean, aop的配置信息來給類/屬性添加註解, 這樣不容易把邏輯搞混.
2.測試, 其實整個開發過程與 xml 配置沒什麼區別, 都是同樣的, 只是形式上有區別。
在給各類類添加註解之間, 必定要牢記:
1.在 xml 配置文件中添加掃描, 掃描註解類:
<context:component-scan base-package="com.demo.aspectJAnnotation"></context:component-scan>
複製代碼
2.肯定AOP註解生效:
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
複製代碼
AspectJ中通知類型的註解種類:
1.@Aspect
聲明切面類, 不須要指定切面類名稱.
等同於<aop:aspect ref="myAspect">, 通常與 @Component 結合使用, Component表明myAspect對象
2.@Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
聲明公共切入點, 經過"方法名"得到切入點引用.
等同於<aop:pointcut id="myPointCut" expression="execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))"/>
2.@Before(value="execution(* com.demo..service.*.*(..))")
前置通知, 直接添加在切面類方法前.
等同於<aop:before method="myBefore" pointcut-ref="myPointCut"/>
或者上面 @Before 也可寫作 @Before(value="myPointCut()") myPointCut是方法名
此時要先在切面類中聲明公共切入點方法:
@Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
private void myPointCut(){}
這樣寫的做用就是爲了少寫代碼, 避免在多個切面類通知方法前都要加execution=(...).
而且若是切入點表達式寫錯了, 也很難排查問題.(不懂請看下面的演示代碼)
3.@AfterReturning(value="myPointCut()", returning="ret")
後置通知, 直接添加在後置通知方法前.
等同於<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret"/>
ret表示接收的返回值名稱, 含有與標籤中的ret同樣.
4.@Around("myPointCut()")
環繞通知, 添加在環繞方法前面.
等同於<aop:around method="myAround" pointcut-ref="myPointCut"/>
5.@AfterThrowing(value="myPointCut()", throwing="e")
拋出異常通知, 添加在拋出異常通知方法前.
等同於<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
6.@After("myPointCut()")
最終通知, 添加在最終通知以前.
等同於<aop:after method="myAfter" pointcut-ref="myPointCut"/>
複製代碼
接下來給出演示代碼:
目標類:
package com.f_aop.aspectJFinalAnnotation;
import org.springframework.stereotype.Service;
//生成UserService的bean: userService
@Service("userService")
public class UserServiceImpl implements UserService {
public void addUser() {
System.out.println("add User");
}
public void updateUser() {
System.out.println("update User");
}
public void deleteUser() {
System.out.println("delete User");
}
}
複製代碼
xml 配置文件 applicationContext.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.f_aop.aspectJFinalAnnotation"></context:component-scan>
<!-- 肯定AOP註解生效 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
複製代碼
切面類:
package com.f_aop.aspectJFinalAnnotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//得到切面類Bean
@Component
//聲明切面類
@Aspect
//因爲兩者都修飾同一個類, 因此不加id
public class MyAspect{
//直接設置切入點, 不使用自定義的公共切入點
// @Before("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))")
// public void myBefore(JoinPoint jPoint){
// System.out.println("前置通知"+jPoint.getSignature().getName());
// }
// 設置切入點, 經過returning得到返回值
// @AfterReturning(value="myPointCut()", returning="ret) // public void myAfterReturning(JoinPoint jPoint, Object ret){ // System.out.println("後置通知"+jPoint.getSignature().getName()+"--"+ret); // } @Pointcut("execution(* com.f_aop.aspectJFinalAnnotation.UserServiceImpl.*(..))") private void myPointCut(){ //配置空方法,用於聲明公共切入點 } @Around("myPointCut()") public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("前置通知"); //手動執行目標方法 Object obj = joinPoint.proceed(); int i = 1/0; // 前置通知 // add User // 拋出異常通知/ by zero // 最終通知 System.out.println("後置通知"); return obj; } @AfterThrowing(value="myPointCut()", throwing="e") public void myAfterThrowing(JoinPoint jPoint, Throwable e){ System.out.println("拋出異常通知"+e.getMessage()); } @After("myPointCut()") public void myAfter(JoinPoint jPoint){ System.out.println("最終通知"); } } 複製代碼