將公共的交叉業務邏輯(如添加日誌、權限校驗、性能統計,事務處理,異常處理等)提取出來封裝爲切面,不改變原有的代碼狀況下,在適當的時候(編譯期,運行期)動態加入到代碼中。(指擴展功能不修改源代碼,將功能代碼從業務邏輯代碼中分離出來)
(編譯期:改變class文件。運行期:在調用業務前先調用切面)2.AOP好處:
1.封裝爲切面的過程使咱們的代碼模塊化.(任務分配簡單。 讓代碼的複用率變高(減小維護的成本))。
2.更好的知足了開閉原則。(不改變原有的代碼動態的添加新功能)。3.AOP術語:
原理:代理模式
靜態代理:本身編寫建立代理類,而後再進行編譯,在程序運行前,代理類的.class文件就已經存在了。
動態代理:在實現階段不用關心代理誰,而在運行階段(經過反射機制)才指定代理哪個對象。
①JDK動態代理:java
只能對實現了接口的類生成代理,而不是針對類,該目標類型實現的接口都將被代理。原理是經過在運行期間建立一個接口的實現類來完成對目標對象的代理。步驟以下:
node
1. 定義一個實現接口InvocationHandler的類spring
2. 經過構造函數,注入被代理類express
3. 實現invoke( Object proxy, Method method, Object[] args)方法編程
4. 在主函數中得到被代理類的類加載器ide
5. 使用Proxy.newProxyInstance( )產生一個代理對象模塊化
6. 經過代理對象調用各類方法函數
Proxy.newProxyInstance(
classLoader, //目標類的類加載器
interfaces, //目標類的實現接口列表
h, //交叉業務邏輯
)
InvocationHandler
invoke(Object proxy, //代理類的實例
Method method,//代理方法
Object[] args //代理方法的參數列表
)
示例:
LoginServiceI loginServiceI =(LoginServiceI) Proxy.newProxyInstance(性能
LoginServiceImpl.class.getClassLoader(),//目標類的類加載器this
LoginServiceImpl.class.getInterfaces(), // 目標類的實現接口列表
newInvocationHandler(){ // 交叉業務邏輯
//invoke參數:代理的實例(可理解爲loginServiceI使用時可忽略),目標類代理方法,代理方法參數列表
public Objectinvoke(Object proxy,Method method,Object[]args) throws Throwable {
System.out.println(method.getName()+"開始執行:"+new Date());//交叉業務邏輯
//交叉業務邏輯執行結束調用目標類的方法:方法.invoke(方法所在對象,方法參數);相似:對象.方法(參數);
return method.invoke(new LoginServiceImpl(),args);
}
});//該方法變成目標類的統一入口,每一個方法調用前都會進入該方法。
②Cglib動態代理:採用的繼承方式/或實現方式
針對類實現代理,對是否實現接口無要求。原理是對指定的類生成一個子類,覆蓋其中的方法,由於是繼承,因此被代理的類或方法最好不要聲明爲final類型。步驟以下:
1. 定義一個實現了MethodInterceptor接口的類
2. 實現其intercept()方法,在其中調用proxy.invokeSuper( )
示例:
publicclass LogCglibAop implements MethodInterceptor{
public ObjectgetProxy(Class<?> clazz) {
Enhancerenhancer =newEnhancer();
if(clazz.getInterfaces() !=null && clazz.getInterfaces().length > 0) {
returnenhancer.create(clazz, clazz.getInterfaces(),this);
}
returnenhancer.create(clazz,this);
}
//目標對象,目標對象原代理方法,方法參數,spring生成的方法代理,此時原方法已無效
public Objectintercept(Object target, Method method, Object[] args,MethodProxymethodProxy)throws Throwable {
System.out.println(method.getName()+"執行:" +new Date());
//調用父類原生態的方法
returnmethodProxy.invokeSuper(target, args);
}
}
5.使用Spring-AOP步驟:處理方式的選擇:
Spring-aop默認採用的JDK動態代理,若是沒有實現接口,它採用的就是Cglib技術,也就是說若是你的類沒有實現接口,而且被final修飾,那麼這個類就不能使用aop技術。
1. 若是目標對象實現了接口,默認狀況下回採用JDK的動態代理實現AOP,也能夠強制使用cglib實現AOP
2. 若是目標對象沒有實現接口,必須採用cglib庫,Spring會自動在JDK動態代理和cglib之間轉換
1.導入jar
IOC jar包: spring-beans-4.2.5.RELEASE.jar
AOP jar包:spring-aop-4.2.5.RELEASE.jar
spring-context-4.2.5.RELEASE.jar spring-aspects-4.2.5.RELEASE.jar
spring-core-4.2.5.RELEASE.jar 核心包 aopalliance.jar aop編程思想包
spring-expression-4.2.5.RELEASE.jar aspectjweaver-1.6.10.jar AspectJ表達式配置切點包
spring-instrument-4.2.5.RELEASE.jar
第三方jar包:commons-logging-1.2.jar cglib-nodep-2.2.jar
2. 通知類型 實現接口
前置通知:在方法以前執行 MethodBeforeAdvice
後置通知:在方法以後執行 AfterReturningAdvice
異常通知:方法出現異常執行 ThrowsAdvice
環繞通知:在方法以前和以後執行 org.aopalliance.intercept.MethodInterceptor
3.編寫通知
①前置通知://實現MethodBeforeAdvice接口
publicclass LoginAdviceimplements MethodBeforeAdvice {
//before(目標對象的代理方法,方法參數列表,目標對象)-->對象.方法(參數)
publicvoid before(Methodmethod, Object[] args, Object target)throws Throwable {
}System.out.println(method.getName()+newDate()); //只負責交叉業務邏輯
}
②後置通知://實現AfterReturningAdvice接口
publicclass GoodByeAdviceimplements AfterReturningAdvice {
//afterReturning(方法的返回值,目標對象的代理方法,方法參數列表,目標對象);
publicvoidafterReturning(Object returnValue,Method method,Object[] args, Object target)throws Throwable {
}System.out.println(method.getName()+returnValue);//調用業務邏輯代碼
}
③異常通知:
publicclass ExceptionAdviceimplements ThrowsAdvice {
publicvoidafterThrowing(Exception ex) {
System.out.println(ex);
}
publicvoid afterThrowing(LoginExceptionex) {
System.out.println(ex);
}
publicvoid afterThrowing(Methodmethod, Object[] args, Object target,Exception ex) {
System.out.println(method.getName()+"args:"+Arrays.toString(args)+ex);
}
}
④環繞通知://實現MethodInterceptor接口
publicclass TimeAdviceimplements MethodInterceptor{
public Object invoke(MethodInvocationinvocation)throws Throwable {
}Method method =invocation.getMethod();//代理方法
Object target =invocation.getThis(); //目標對象
Object[] args =invocation.getArguments();//代理方法參數列表
//調用業務邏輯代碼
Object returnValue =invocation.proceed();
System.out.println(method.getName()+"花費時間:"+(endTime-beginTime));
return returnValue;
}
4.織入(四種織如方式:①②基礎,③④經常使用)(重點)
①採用FactoryBean(實現類ProxyFactoryBean)
<!--目標對象-->
<bean id="loginServiceTarget" class="aop04.Impl.LoginServiceImpl"></bean>
<!--通知(負責交叉業務邏輯)(前置,後置,環繞)-->
<bean id="loginAdvice" class="aop04.advice.LoginAdvice"></bean>
<bean id="goodByeAdvice" class="aop04.advice.GoodByeAdvice"></bean>
<bean id="timeAdvice" class="aop04.advice.TimeAdvice"></bean>
<!--採用FactoryBean接口的ProxyFactoryBean實現類織入-->
<bean id="loginServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--注入目標對象-->
<property name="target" ref="loginServiceTarget"></property>
<!--注入目標對象的實現接口列表-->
<property name="proxyInterfaces">
<list>
<value>aop04.LoginServiceI</value>
</list>
</property>
<!--注入交叉業務邏輯-->
<property name="interceptorNames">
<list>
<value>loginAdvice</value>
<value>goodByeAdvice</value>
<value>timeAdvice</value>
</list>
</property>
</bean>
②採用後處理器:自動代理:(適用條件:兩個目標類要添加的功能相同,使用一個配置在一塊兒(如給兩個類添加事物))
org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator後處理器。
<!--自動代理-->
<!--目標類-->
<bean id="loginService" class="aop9.impl.LoginServiceImpl"></bean>
<bean id="userService" class="aop9.impl.UserServiceImpl"></bean>
<!--通知-->
<bean id="transactionAdvice" class="aop9.advice.TransactionAdvice">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<bean id="transactionManager" class="aop9.transaction.impl.TransactionManager"></bean>
<!--使用BeanNameAutoProxyCreator自動代理,該類已實現BeanPostProcess接口,原理是後處理器-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</property></list>
<property name="interceptorNames">
<list>
<value>transactionAdvice</value>
</list>
</property>
</bean>
自定義Pointcut和Advisor步驟:(Advisor = pointcut+advice)
1.自定義Pointcut須要實現接口 Pointcut
public ClassFiltergetClassFilter(){} //匹配類的類型
public MethodMatchergetMethodMatcher(){}//匹配方法
MethodMatcher 執行流程以下圖:
matches(..)
matches()
isRuntime
2.整合pointcut和advice ===advisor
自定義一個Advisor須要實現接口PointcutAdvisor採用依賴注入的方式將advice和pointcut注入。
3.織入:將advice替換爲advisor。
預約義的advisor:org.springframework.aop.support。
NameMatchMethodPointcut
NameMatchMethodPointcutAdvisor
DefaultIntroductionAdvisor
③採用命名空間的方式:(原理還是後處理器)
1.採用命名空間的標籤替代了bean的配置。
2.採用AspectJ表達式替代了pointcut的配置。
3.advice不須要實現任何接口。(低侵入性)
AspectJ書寫:within(包名.類名) 表示匹配該類中全部的方法
<aop:before> 前置通知
<aop:after-returning> 後置通知
<aop:after-throwing 異常通知
<aop:around> 環繞通知
通知的寫法:
publicvoid methodName(){} //既能夠當成前置通知,又能夠爲後置通知同時還能夠爲異常通知
publicvoid methodName(JoinPointjoinPoint){}//既能夠當成前置通知,又能夠爲後置通知同時還能夠爲異常通知
publicvoid methodName(JoinPointjoinpoint, Object returnValue){}//只能爲後置通知
publicvoid methodName(JoinPointjoinpoint, Exception ex){}//只能爲異常通知
環繞通知:
1.須要有一個Object類型返回值.
2.須要有一個參數ProceedingJoinPoint.
3.須要拋出一個異常Throwable
如:
public Object time(ProceedingJoinPointjoinPoint) throws Throwable {
Object target = joinPoint.getThis();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
long startTime = System.currentTimeMillis();
Object returnValue = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println(methodName+"cost "+(endTime-startTime)+"ms.");
return returnValue;
}
示例:
<bean id="loginService" class="aop10.impl.LoginServiceImpl"></bean> //目標類
<bean id="logAdvice" class="aop10.advice.LogAdvice"></bean> //通知
<!--織入-->
<aop:config>
<!--AspectJ表達式within,表示*ServiceImpl內全部方法均代理-->
<aop:pointcut expression="within(aop10.impl.*ServiceImpl)" id="pc"/>
//execution(方法的返回值 類.方法名 方法的參數)
<aop:pointcut expression="execution(*log*(..,java.lang.String))" id="pc1"/>
<aop:aspect ref="logAdvice">//切面
<aop:before method="log1" pointcut-ref="pc1"/>method指目標類中的代理方法 //前置通知
<aop:after-returning method="afterReturning" pointcut-ref="pc" returning="returnValue"/> //後置通知
<aop:after-throwing method="afterThrowing" pointcut-ref="pc" throwing="ex"/>//異常通知
<aop:around method="time" pointcut-ref="pc"/>//環繞通知
</aop:aspect>
</aop:config>