一、第五種方式
1邀桑、AOP的相關(guān)概念
橫切關(guān)注點(diǎn):對(duì)哪些方法進(jìn)行攔截,攔截后怎么處理科乎,這些關(guān)注點(diǎn)稱(chēng)之為橫切關(guān)注點(diǎn)
Aspect(切面):通常是一個(gè)類(lèi)壁畸,里面可以定義切入點(diǎn)和通知
JointPoint(連接點(diǎn)):程序執(zhí)行過(guò)程中明確的點(diǎn),一般是方法的調(diào)用茅茂。被攔截到的點(diǎn)捏萍,因?yàn)镾pring只支持方法類(lèi)型的連接點(diǎn),所以在Spring中連接點(diǎn)指的就是被攔截到的方法空闲,實(shí)際上連接點(diǎn)還可以是字段或者構(gòu)造器
Advice(通知):AOP在特定的切入點(diǎn)上執(zhí)行的增強(qiáng)處理令杈,有before(前置),after(后置),afterReturning(最終),afterThrowing(異常),around(環(huán)繞)
Pointcut(切入點(diǎn)):就是帶有通知的連接點(diǎn),在程序中主要體現(xiàn)為書(shū)寫(xiě)切入點(diǎn)表達(dá)式
weave(織入):將切面應(yīng)用到目標(biāo)對(duì)象并導(dǎo)致代理對(duì)象創(chuàng)建的過(guò)程
introduction(引入):在不修改代碼的前提下碴倾,引入可以在運(yùn)行期為類(lèi)動(dòng)態(tài)地添加一些方法或字段
AOP代理(AOP Proxy):AOP框架創(chuàng)建的對(duì)象逗噩,代理就是目標(biāo)對(duì)象的加強(qiáng)。Spring中的AOP代理可以使JDK動(dòng)態(tài)代理跌榔,也可以是CGLIB代理异雁,前者基于接口,后者基于子類(lèi)
目標(biāo)對(duì)象(Target Object): 包含連接點(diǎn)的對(duì)象僧须。也被稱(chēng)作被通知或被代理對(duì)象纲刀。POJO
2、第五種方式
Student.class
package com.qianfeng.aop05;
public class Student implements Person {
@Override
public boolean eat() {
System.out.println("I can eat");
return true;
}
@Override
public String drink() {
System.out.println(1/0);
System.out.println("I can drink");
return null;
}
}
beans5.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="st" class="com.qianfeng.aop05.Student" />
<bean id="my" class="com.qianfeng.aop05.MyAspect" />
<aop:config proxy-target-class="true">
<aop:aspect ref="my">
<aop:pointcut id="pt" expression="(execution(boolean com.qianfeng.aop05..(..))) or (execution(java.lang.String com.qianfeng.aop05..(..)))"/>
<aop:around method="myAround" pointcut-ref="pt"/>
<aop:before method="myBefore" pointcut-ref="pt"/>
<aop:after method="myAfter" pointcut-ref="pt"/>
<aop:after-returning method="myReturn" pointcut-ref="pt" returning="object"/>
<aop:after-throwing method="myThrow" pointcut-ref="pt" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
aop:point-cut標(biāo)簽指定了切點(diǎn)担平,返回值示绊、包、類(lèi)暂论、方法與參數(shù)面褐,只匹配符合條件的方法,也就是對(duì)這些方法進(jìn)行代理空另,條件可以進(jìn)行與或非操作盆耽,分別使用and或&&、or或||、!進(jìn)行多條件匹配摄杂。
aop:before標(biāo)簽是前置通知坝咐,在切點(diǎn)方法執(zhí)行前通知
aop:around標(biāo)簽是環(huán)繞通知,在方法執(zhí)行前后都通知
aop:after標(biāo)簽是后置通知析恢,在方法執(zhí)行后通知
aop:after-returning標(biāo)簽是后置返回通知墨坚,方法執(zhí)行后的返回值進(jìn)行通知,returning參數(shù)所對(duì)應(yīng)的值是方法返回的參數(shù)用于放在method方法中的形參映挂。
aop:after-throwing是異常拋出通知泽篮,只有方法的執(zhí)行過(guò)程中出現(xiàn)了異常并且自己沒(méi)有處理的話(huà)才會(huì)執(zhí)行通知,throwing是捕獲到的異常用于放在對(duì)應(yīng)method方法中的形參
執(zhí)行順序:before >around before > 業(yè)務(wù)方法 > (after-throwing)(after returning > around after)>after柑船。不過(guò)順序也不固定:使用注解的話(huà)是環(huán)繞通知proceed方法之前部分先執(zhí)行帽撑,使用xml配置的話(huà)取決于aop:before和aop:around的配置順序
MyAspect.java
package com.qianfeng.aop05;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.Arrays;
public class MyAspect{
public void myBefore(JoinPoint jp){
System.out.println("args:"+ Arrays.toString(jp.getArgs()));
System.out.println("toString:"+jp.toString());
System.out.println("getTarget:"+jp.getTarget());
System.out.println("---------before----------");
}
public void myAfter(){
System.out.println("---------after----------");
}
public Object myAround(ProceedingJoinPoint pjp){
System.out.println("this is around-before");
try {
Object obj = pjp.proceed();
System.out.println("this is around-after"+obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
public void myReturn(JoinPoint jp,Object object){
System.out.println("this is after returning "+object);
}
public void myThrow(JoinPoint jp,Throwable e){
System.out.println("this is after throwing "+e.getMessage());
}
}
JoinPoint是連接點(diǎn)凹耙,會(huì)包含連接點(diǎn)的基本消息旱幼,通知所在的類(lèi)、通知的方法名坐桩、通知方法的入?yún)⒓夏嫖 etArgs()方法獲取方法的參數(shù)數(shù)組及塘,getTarget()獲取目標(biāo)對(duì)象信息
ProceedingJoinPoint是JoinPoint的子接口,該對(duì)象只用在A(yíng)round的切面方法中。
二锐极、第六種方式
第五種方式使用xml文件配置較多步驟笙僚,比較麻煩,第六種方式使用注解更為簡(jiǎn)便
beans6.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.qianfeng.aop06"/>
<aop:aspectj-autoproxy/>
</beans>
相較于第五種方式灵再,第六種方式主要的不同就是可以實(shí)現(xiàn)自動(dòng)代理肋层,xml文件的配置有以下不同:
引入了三個(gè)命名空間,和context有關(guān)
添加了context:component-scan標(biāo)簽翎迁,用于掃描組件槽驶,base-package指定了需要進(jìn)行組件掃描的包
aop:aspectj-autoproxy標(biāo)簽指定了實(shí)現(xiàn)自動(dòng)代理
MyAspect.java
package com.qianfeng.aop06;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Aspect
public class MyAspect{
@Pointcut(value = "execution(boolean com.qianfeng.aop06..(..))")
public void setAll(){}
@Before(value = "execution(java.lang.String com.qianfeng.aop06..(..))")
public void myBefore(JoinPoint jp){
System.out.println("args:"+ Arrays.toString(jp.getArgs()));
System.out.println("toString:"+jp.toString());
System.out.println("getTarget:"+jp.getTarget());
System.out.println("---------before----------");
}
@After("setAll()")
public void myAfter(){
System.out.println("---------after----------");
}
@Around("setAll()")
public Object myAround(ProceedingJoinPoint pjp){
System.out.println("this is around-before");
try {
Object obj = pjp.proceed();
System.out.println("this is around-after"+obj);
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
@AfterReturning(value = "setAll()",returning = "object")
public void myReturn(JoinPoint jp,Object object){
System.out.println("this is after returning "+object);
}
@AfterThrowing(value = "setAll()",throwing = "e")
public void myThrow(JoinPoint jp,Throwable e){
System.out.println("this is after throwing "+e.getMessage());
}
}
@Component注解標(biāo)注當(dāng)前類(lèi)是一個(gè)組件,在掃描時(shí)會(huì)被掃描進(jìn)來(lái)
@Aspect標(biāo)注當(dāng)前類(lèi)是一個(gè)切面類(lèi)
@PointCut標(biāo)注切點(diǎn)鸳兽,為了避免相同的匹配規(guī)則被定義多處掂铐,專(zhuān)門(mén)定義該方法設(shè)置執(zhí)行的匹配規(guī)則,各個(gè)方法自行調(diào)用即可
@Before標(biāo)注前置通知
@After標(biāo)注后置通知
@Around標(biāo)注環(huán)繞通知
@AfterReturning標(biāo)注后置返回通知
@AfterThorwing標(biāo)注拋出異常通知
注解的執(zhí)行順序稍有不同:(after throwing)around before>before > 業(yè)務(wù)方法 > around-after > after >after returning
三揍异、第七種方式
第七種方式使用BeanPostProcessor方式實(shí)現(xiàn)全陨。該接口我們也叫后置處理器,作用是在Bean對(duì)象在實(shí)例化和依賴(lài)注入完畢后衷掷,在顯示調(diào)用初始化方法的前后添加我們自己的邏輯辱姨。注意是Bean實(shí)例化完畢后及依賴(lài)注入完成后觸發(fā)的。
Spring中Bean的運(yùn)行順序:
Spring IoC容器實(shí)例化Bean(注意實(shí)例化與初始化的區(qū)別戚嗅,實(shí)例化是在內(nèi)存中開(kāi)辟空間雨涛,初始化是對(duì)變量賦值)
調(diào)用BeanPostProcesson的postProcessBeforeInitialization方法
調(diào)用Bean實(shí)例的初始化方法
調(diào)用BeanPostProcesson的postProcessAfterInitialization方法
beans7.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.qianfeng.aop07"/>
<bean class="com.qianfeng.aop07.MyBeanPostProcessor"/>
</beans>
context:component-scan用于指定組件掃描枢舶,base-package指定需要掃描的包,只有打上@component注解的類(lèi)才會(huì)被掃描
bean class="com.qianfeng.aop07.MyBeanPostProcessor"指定BeanPostProcessor的Factory hook替久,讓每個(gè)bean對(duì)象初始化是自動(dòng)回調(diào)該對(duì)象中的回調(diào)方法
MyBeanPostProcessor.java
package com.qianfeng.aop07;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("this is before "+bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("this is after");
return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyAspect ma = new MyAspect();
ma.before();
Object obj = method.invoke(bean, args);
ma.after();
return obj;
}
});
}
}
這個(gè)類(lèi)實(shí)現(xiàn)了BeanPostProcessor接口凉泄,重寫(xiě)了postProcessBeforeInitialization()與postProcessAfterInitialization()方法,這兩個(gè)方法的第一個(gè)參數(shù)是目標(biāo)對(duì)象的bean蚯根,返回值都是Object類(lèi)型后众,因?yàn)榘裝ean從IoC容器中取出來(lái)還需要放回去,如果返回null的話(huà)會(huì)報(bào)異常
postProcessBeforeInitialization()是在初始化方法之前執(zhí)行颅拦,postProcessAfterInitialization()是在初始化方法之后執(zhí)行
配置了包掃描之后蒂誉,該類(lèi)會(huì)初始化兩個(gè)對(duì)象EventListenerMethodProcessor和DefaultEventListenerFactory,再外加我們自己的組件對(duì)象 距帅, 所以會(huì)發(fā)現(xiàn)有三個(gè)before打印右锨。
return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() ...)這條語(yǔ)句的作用就是將代理后的對(duì)象放進(jìn)IoC容器中。