轉載AOP
Spring之AOP篇:
AOP框架是Spring的一個重要組成部分.但是Spring IOC 並不依賴於AOP,這就意味著你有權力選擇是否使用AOP,AOP作為Spring IOC容器的一個補充,使它成為一個強大的中介軟體解決方案。
一、AOP(Aspect-Oriented Programming)
AOP的目標:橫切關注點 這種問題用物件導向很難解決
AOP是用來解決什麼問題的呢?
橫越多個模組的業務關注點,比如記錄日誌,事務管理
1、AOP的解決方案——攔截
1)由誰來攔截?--代理類Proxy 和本類實現同一個介面
就像是這樣:action發出命令--代理類(處理一些操作)--本類
2)攔截的過程:
I.將Proxy(如UserServiceProxy)交給呼叫者(如UserAction),呼叫者呼叫需要的方法
II.Proxy先攔截該方法,執行攔截邏輯
III.然後執行呼叫者真正的實現類(如UserServiceImp)
3)核心概念:
a、代理物件Proxy:是核心,負責攔截,由工具自動建立
b、攔截器(Interceptor):實現攔截邏輯的物件
c、目標物件(Target):是Proxy所代理的物件,是已有的類
以上三個類實現了一個“金三角”
Proxy--攔截-->Interceptor--實現攔截邏輯--處理目標物件-->Target
2、代理如何實現?
有兩種方式:
1)JDK動態代理(使用這個比較多)
由JDK自帶的動態程式碼生成技術,可以對實現了介面的類進行處理
2)CGLib
對沒有實現任何介面的類進行處理
這兩種方式的共同點:(都是在後臺自動生成的)
在程式執行期間,動態的生成程式碼並進行動態編譯和載入
問題:Spring的AOP是如何實現代理的?是自己實現的嗎?
注:Spring借用了JDK動態代理和CGLib來實現代理物件。
Spring進行如下判斷來實現代理:
i、如果目標類實現了介面,Spring則呼叫JDK動態代理;
ii、反之,呼叫CGLib
Proxy是核心:
建立Proxy的方法:ProxyFactoryBean
<bean id="arithProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="arith"/>
<property name="interceptorNames">
<list>
<!--<value>logBefore</value>
<value>logAfter</value>
<value>logThrows</value> -->
<value>logAround</value>
</list>
</property>
JDK動態代理的實現方式:(工廠模式)
UserDao userDao = new UserDaoImp();
UserDao proxy = (UserDao)
Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
new LogHandler(userDao));
二、Spring中的AOP方式(在Spring中將攔截稱為通知)
攔截邏輯放在Advice之中,Advice按時間將攔截器分為四類:
1)Before Advice:方法執行之前進行攔截
public class LogBeforeAdvice implements MethodBeforeAdvice{
//Method method攔截的方法
//Object[] args 方法中的引數
//target 目標物件
@Override
public void before
(Method method, Object[] args, Object target)
throws Throwable {
MyLogger logger = new MyLogger();
System.out.println("Methods:"+method.getName()+
" begins, args:"+Arrays.toString(args));
}
}
2)After Advice:方法執行之後進行攔截
public class LogAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object resultValue, Method method, Object[] args,
Object target) throws Throwable {
MyLogger logger = new MyLogger();
System.out.println("Methods:"+method.getName()+
" ends, result:"+resultValue);
}
}
3)AfterThrowing Advice:方法異常結束之後進行攔截
public class LogThrowingAdvice implements ThrowsAdvice {
//ThrowsAdvice 沒有方法,但方法名必須是afterThrowing
public void afterThrowing(IllegalArgumentException e)
throws Throwable{
MyLogger logger = new MyLogger();
logger.log("IllegalArgumentException!");
}
}
4)Around Advice:環繞通知
public class LogAroundAdvice implements MethodInterceptor {
//MethodInvocation相當於Method的包裝
@Override
public Object invoke(MethodInvocation methodInvocation)
throws Throwable {
MyLogger logger = new MyLogger();
try {
//methodInvocation.getMethod()獲得方法
//methodInvocation.getArguments()獲得方法的引數
logger.log("before:"+
methodInvocation.getMethod().getName()+
", args:"+methodInvocation.getArguments());
//在環繞通知裡面,必須執行目標方法!!!!!!!!!!!!!!
Object result = methodInvocation.proceed();
logger.log("ends:"+
methodInvocation.getMethod().getName());
return result;
} catch (Exception e) {
logger.log("Exception:"+e.getMessage());
throw e;
}
}
注意:雖然環繞通知包含了另外三種,但還是要依據業務邏輯來選擇,這樣有利於程式碼的程式設計量
並且環繞通知最好不要和另外三種混用,並且並許執行目標方法proceed().
也可以大致分成兩組——
i、BeforeAdvice, AfterReturning, Throwing
ii、AroundAdvice:需要在內部負責呼叫目標類的方法。
注意:AroundAdvice應單獨使用
<!-- Target Object -->
<bean id="arith" class="myspring.calculator.ArithmeticCalculatorImp"></bean>
<!-- Advice -->
<bean id="logBefore" class="myspring.aop.LogBeforeAdvice"/>
<bean id="logAfter" class="myspring.aop.LogAfterAdvice"/>
<bean id="logThrows" class="myspring.aop.LogThrowingAdvice"/>
<bean id="logAround" class="myspring.aop.LogAroundAdvice"/>
<!-- Proxy -->
<bean id="arithProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="arith"/>
<property name="interceptorNames">
<list>
<!-- <value>logBefore</value>
<value>logAfter</value>
<value>logThrows</value> -->
<value>logAround</value>
</list>
</property>
</bean>
在test類中這樣用:ApplicationContext ac =
new ClassPathXmlApplicationContext("aop-base.xml");
IArithmeticCalculator proxy =
(IArithmeticCalculator)ac.getBean("arithProxy");
面試中可能會問到:
proxyTargetClass屬性
將其加入到代理bean中去,則Spring將呼叫CGLib來實現建立Proxy物件。
(無論Target有無實現介面)
形如
<bean id="" class="....ProxyFactoryBean">
.....
<property name="proxyTargetClass" value="true" />
</bean>
三、Spring AOP基本方式 AspectJ Spring2.5之後用的
Spring對AspectJ的支援
應用於有多個target,不想一個一個的配置時,用AspectJ
1)POJO類(Aspect類),使用@Aspect
基本資訊:Advice和Pointcut
2)定義Bean:在Bean定義檔案中定義這個Aspect類
3)啟用Spring的自動代理
<aop:aspectj-autoproxy />
1、JoinPoint(連線點):被攔截的方法
程式執行過程中的點,就好像方法中的一個呼叫,
或者一個特別的被丟擲的異常
在Spring AOP中,一個連線點通常是方法呼叫.
Spring並不明顯地使用"連線點"作為術語,
連線點資訊可以通過MathodInvocation的引數穿過攔截器訪問,
相當於實現org.springframework.aop.Pointcut介面.
2、JoinPoint物件
獲取連線點資訊(獲取目標方法以及目標物件的資訊)
1)目標方法:
i、getSignature():方法簽名
joinPoint.getSignature().getName()
ii、getArgs():方法的實參(方法的實際引數)
joinPoint.getArgs()
列印輸出時:Arrays.toString(joinPoint.getArgs())
2)目標物件: getTarget()
目標物件實際型別:getTarget().getClass()
3、PointCut(切入點):一組連線點。
作用:定義了一組被攔截的方法
PointCut(切入點):當一個通知被啟用的時候,
會指定一些連線點.一個AOP框架必須允許開發者指定切入點。
一個AOP框架必須允許開發者指定切入點:例如使用正規表示式.
只有引入了切入點,才能很好的進行攔截
PointCut的意義:可以一次性定義多個類的
多個方法作為攔截目標(即JoinPoint)
PointCut代表了Target 一組Target組成了PointCut
一組Target可以理解為一個類的多個方法,或者多個類的多個方法
代理、攔截器、目標
PointCut
在AspectJ中,切入點是用PointCut Expression來
定義的。(切入點表示式可以理解為過濾條件)
PointCut表示式
格式 execution(表示式)
表示式 一個表示式就是一個匹配規則
* myspring.calculator.IArithmeticCalculator.add(..)
*理解為public void myspring.calculator.IArithmeticCalculator.add(int a,int b)
* 取代public void 通配一切字元
Spring自動代理(即Spring自動生成Proxy物件)
用<aop:aspectj-autoproxy />來啟用
Pointcut表示式語法
1)方法級別 —— execution
execution(* *.*(..)) :所有類的方法
execution(public * somepackage.SomeClass.someMethod(..))
總結:execution是對方法簽名進行匹配
2)物件級別(類級別)—— within
within(somepackage.*):somepackage包下所有的類
within(com.dang.service.*Service)
即 com.dang.service包下所有以Service結尾的類
within(somepackage..*):somepackage以及所有子包中的類
within(somepackage.SomeInterface+):SomeInterface介面的所有實現類
總結:within是對類名(帶包名)進行匹配。 攔截這個類的所有方法
4、Aspect類的組成 一般都用註解 重點
1)Advice(AspectJ中的通知型別)
i、Before
//@Before("LogPointcuts.logAdd()") 方法1
@Before("execution(* *.*(..))") 方法2
public void logBefore(JoinPoint joinPoint){
logger.log("{before} the method: "
+joinPoint.getSignature().getName()+
" args:"+Arrays.toString(joinPoint.getArgs()));
}
注:方法1和方法2都是可以的,方法1的話需要在定義一個方法.
在同一個類或不同的類都是可以的 直接呼叫這個方法
@Pointcut("execution(* *.*(..))")
public void logOperation(){}
ii、AfterReturning
@AfterReturning(pointcut="execution(* *.*(..))",
returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
logger.log("{AfterReturning} the method: "
+joinPoint.getSignature().getName()
+" result:"+result);
}
注意:方法有兩個引數,在註解中的returning的值必須和Object的值完全一樣
iii、AfterThrowing
@AfterThrowing(pointcut="execution(* *.*(..))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint,IllegalArgumentException e){
logger.log("{AfterThrowing} the method: "
+joinPoint.getSignature().getName()
+" e:"+e.getMessage());
}
iv、After:相當於finally,它在AfterReturning
和AfterThrowing之後執行。
它的作用——一般為釋放資源(如關閉Connection)
@After("execution(* *.*(..))")
public void after(JoinPoint joinPoint){
logger.log("{After} method:"+joinPoint.getSignature().getName());
}
注意:After是肯定是要在最後執行的,
v、Around
@Around("execution(* *.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable{
logger.log("{Arount}Before method:"
+joinPoint.getSignature().getName());
try{
//一定不要忘記呼叫目標方法
Object result = joinPoint.proceed();
logger.log("{Arount}After method:"
+joinPoint.getSignature().getName()
+" result:"+result);
//返回目標方法的返回值
return result;
}
catch(IllegalArgumentException e){
logger.log("{Around}Exception:Illegal Argument"
+Arrays.toString(joinPoint.getArgs()));
throw e;
}
注意:Around最好不要和其他四種同時使用
把處理日誌的程式碼放到一起就是切面,模組化(Aspect)
5.Introduction(引入)
1)Introduction的定義
給正在執行的程式中的物件新增方法。
其實是一種動態技術
2)問題:如何給ArithmeticCalculatorImp類加入
求最大值和最小值的方法
假設:最大值和最小值的方法已經存在,且分別屬於不同的類。
extends MaxCalculatorImp, MinCalculatorImp 不行,java不允許多繼承
3)要點:
a、實現一個POJO類
定義@DeclareParents來"繼承"的父類
@DeclareParents(
value="myspring.calculator.ArithmeticCalculatorImp",
defaultImpl=MaxCalculatorImp.class
)
private MaxCalculator maxCalculator;
@DeclareParents(
value="myspring.calculator.ArithmeticCalculatorImp",
defaultImpl=MinCalculatorImp.class
)
private MinCalculator minCalculator;
b、定義這個Bean:在Bean定義檔案中宣告一下。
<bean class="myspring.calculator.CalculatorIntroduction"></bean>
在test類中測試時:ApplicationContext ac =
new ClassPathXmlApplicationContext("aop.xml");
IArithmeticCalculator cal = (IArithmeticCalculator)
ac.getBean("arith");
cal.add(2, 3);
cal.div(4, 3);
MaxCalculator max = (MaxCalculator)cal;
max.max(3, 6);
MinCalculator min = (MinCalculator)cal;
min.min(3, 6);
相關文章
- [轉載]Spring AOP的實現機制Spring
- [轉載] spring aop 環繞通知around和其他通知的區別Spring
- 原始碼解析Spring AOP的載入與生效原始碼Spring
- 轉載方法
- [轉載] ebxmlXML
- 【轉載】ElasticsearchElasticsearch
- FPGA【轉載】FPGA
- AOP
- 基於SpringBoot 、AOP與自定義註解轉義字典值Spring Boot
- AOP詳解之三-建立AOP代理後記,建立AOP代理
- 【轉載】RenderTransform特效ORM特效
- 轉載快取快取
- DataGridView使用 --轉載View
- 【轉載】gulp minimist
- AOP - AspectJ
- AOP - Advisor
- [Spring]AOPSpring
- spring AOPSpring
- Sping AOP
- ioc aop
- IOC,AOP
- AOP概念
- Spring AOP學習筆記01:AOP概述Spring筆記
- AOP原始碼解析之二-建立AOP代理前傳,獲取AOP資訊原始碼
- [轉載]fsdb dump技巧
- VsCode 快捷鍵[轉載]VSCode
- Cilium Ingress 特性(轉載)
- Web【轉載學習】Web
- flex佈局——轉載Flex
- 轉載 - 常用 mock 工具Mock
- Linux磁碟掛載 轉Linux
- glog 使用教程 轉載
- openERP課程轉載
- Redis 與 memcache(轉載)Redis
- 死磕Spring之AOP篇 - Spring AOP總覽Spring
- AOP隨筆
- spring-aopSpring
- Spring AOP APISSpringAPI