AOP
面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術
主要的功能是:日誌記錄,性能統計,安全控制,事務處理,異常處理等等
實現方式
預編譯:AspectJ
運行期動態代理(JDK動態代理,CGLib動態代理):SpringAOP,JbossAOP
AOP的幾個概念
切面(Aspect):一個關注點的模塊化,這個關注點可能會橫切多個對象;
連接點(Joinpoint):程序執行過程中的某個特定的點
通知(Advice):在切面的某個特定的連接點上的執行的動作
切入點(Pointcut):匹配連接點的斷言,在AOP中通知和一個切入點表達式關聯
引入(Introduction):在不修改類代碼的前提下,爲類添加新的方法和屬性
目標對象(Target Object):被一個或者多個切面所通知的對象
AOP代理(AOP Proxy):AOP框架創建的對象,用來實現切面契約
織入(Weaving):把切面連接到其它的應用程序類型或者對象上,並創建一個被通知的對象
Spring所有的切面和通知器都必須放在一個< aop:config >內(可以配置多個),每一個< aop:config >可以包含pointcut,advisor和aspect元素,它們必須按照這個順序進行配置
PointCut
expression表達式:
execution(* com.xyz.A.*(..)) 切入點執行A類中的所有方法
execution(* com.xyz..(..)) 切入點執行xyz包下的所有方法
execution(* com.xyz…(..)) 切入點執行xyz包及其自包下的所有方法
(* com.evan.crm.service..(..))中幾個通配符的含義:
參數 | 說明 |
---|---|
第一個 * | 通配 隨便率性返回值類型 |
第二個 * | 通配包com.evan.crm.service下的隨便率性class |
第三個 * | 通配包com.evan.crm.service下的隨便率性class的隨便率性辦法 |
第四個 .. | 通配 辦法可以有0個或多個參數 |
Advice的類型
前置通知(Before advice):在某連接點(join point)之前執行的通知,但不能阻止連接點前的執行(除非它拋出一個異常)
返回後通知(After returning advice):在某連接點(join point)正常完成後執行的通知
拋出異常後通知(After throwing advice):在方法拋出異常退出時執行的通知
後通知(After advice):當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)
環繞通知(Around advice):包圍一個連接點(join point)的通知。通知方法的第一個參數必須是ProceedingJoinPoint類型,通過使用其的proceed()方法來執行正常的業務方法
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="aspectConfig" class="com.lmr.spring.aop.advice.AspectConfig"></bean>
<bean id="adviceService" class="com.lmr.spring.aop.advice.AdviceService"></bean>
<aop:config>
<aop:aspect id="aspectAOP" ref="aspectConfig">
<aop:pointcut expression="execution(* com.lmr.spring.aop.advice.AdviceService.*(..))" id="adviceServicePointCut"/>
<aop:before method="before" pointcut-ref="adviceServicePointCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="adviceServicePointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="adviceServicePointCut"/>
<aop:after method="after" pointcut-ref="adviceServicePointCut"/>
<aop:around method="around" pointcut-ref="adviceServicePointCut"/>
<!-- <aop:around method="aroundParameter" pointcut="execution(* com.lmr.spring.aop.advice.AdviceService.sayParameter(String, int)) and args(name, count)"/> -->
</aop:aspect>
</aop:config>
</beans>
public class AspectConfig {
public void before(){
System.out.println("AspectConfig before");
}
public void afterReturning(){
System.out.println("AspectConfig afterReturning");
}
public void afterThrowing(){
System.out.println("AspectConfig afterThrowing");
}
public void after(){
System.out.println("AspectConfig after");
}
public Object around(ProceedingJoinPoint pjp){
Object obj=null;
try {
System.out.println("AspectConfig around 1");
obj = pjp.proceed();
System.out.println("AspectConfig around 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
public Object aroundParameter(ProceedingJoinPoint pjp,String name,int count){
System.out.println("AspectConfig aroundParameter "+name+" "+count);
Object obj=null;
try {
System.out.println("AspectConfig aroundParameter 1");
obj = pjp.proceed();
System.out.println("AspectConfig aroundParameter 2");
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj;
}
}
public class AdviceService {
public void say(){
System.out.println("AdviceService say");
// throw new RuntimeException();
}
public void sayParameter(String name, int count){
System.out.println("AdviceService sayParameter "+name+" "+count);
}
}
@Test
public void TestAdvice(){
AdviceService service=super.getBean("adviceService");
service.say();
}
結果:消息顯示的順序跟其在xml中配置的順序有關
AspectConfig before
AspectConfig around 1
AdviceService say
AspectConfig around 2
AspectConfig after
AspectConfig afterReturning
Introductions:允許一個切面聲明一個實現指定接口的通知對象,並且提供了一個接口實現類來代表這些對象;即是爲這個對象指定了一個新的父類(接口實現類)
<aop:declare-parents types-matching="com.lmr.spring.aop.advice.AdviceService+" implement-interface="com.lmr.spring.aop.advice.Filter" default-impl="com.lmr.spring.aop.advice.FilterImpl"/>
public interface Filter {
public void control();
}
public class FilterImpl implements Filter {
@Override
public void control() {
// TODO Auto-generated method stub
System.out.println("FilterImpl control");
}
}
@Test
public void TestDeclareparents(){
Filter filter=(Filter)super.getBean("adviceService");
filter.control();
}
結果:爲AdviceService指定了一個新的父類Filter
FilterImpl control
Spring AOP API
Pointcut
實現之一:NameMatchMethodPointcut,根據方法名字進行匹配
成員變量:mappedNames,匹配的方法名集合
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
Before Advice
一個簡單的通知了類型
只是在進入方法之前被調用,不需要MethodInvocation對象
前置通知可以在連接點執行之前插入自定義行爲,但不能改變返回值
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyBeforeAdvice "+method.getName()+" "+target.getClass().getName());
}
}
Throws Advice
如果連接點拋出異常,throws advice在連接點返回後被調用
如果throws-advice的方法拋出異常,那麼它將覆蓋原有異常
接口org.springframework.aop.ThrowsAdvice不包含任何方法,僅僅是一個聲明,實現類需要實現對應的方法
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception e)throws Throwable{
System.out.println("MyThrowsAdvice afterThrowing1 ");
}
public void afterThrowing(Method method, Object[] args, Object target, Exception e)throws Throwable{
System.out.println("MyThrowsAdvice afterThrowing2 "+method.getName()+" "+target.getClass().getName());
}
}
AfterReturing Advice
後置通知必須實現org.springframework.aop.AfterReturningAdvice接口
可以訪問返回值(但不能進行修改)、被調用的方法、方法的參數和目標
如果拋出異常,將會拋出攔截器鏈,替代返回值
public class MyAfterReturnAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyAfterReturnAdvice "+method.getName()+" "+target.getClass().getName()+" "+returnValue);
}
}
Interception Around Advice
1、自定義切入點,直接繼承Interceptor接口,實現invoke函數。
2、自定義調試攔截器,這個攔截器和相應的切入點互相工作,將匹配到的函數執行前後都打印控制檯
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyAroundAdvice 1 "+invocation.getMethod().getName()+" "+invocation.getStaticPart().getClass().getName());
Object object=invocation.proceed();
System.out.println("MyAroundAdvice 2 " + object);
return object;
}
}
Advisor通知常用bean:DefaultPointcutAdvisor
*必備屬性:advice和pointcut
*可以使用構造注入或設置注入的形式注入兩個屬性
ProxyFactoryBean
創建Spring AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean
- 使用ProxyFactoryBean或者其他IoC相關類來創建AOP代理的最重要好處是通知和切入點可以由IOC容器管理
- 被代理類沒有實現任何接口,使用CGLIB動態代理,否則使用的是JDK代理
- ProxyFactoryBean爲true,無論是否實現了接口,可強制使用CGLIB
- 如果proxyInterfaces屬性設置爲一個或者多個全限定接口名,使用的是JDK代理
如果ProxyFactoryBean的proxyInterfaces屬性沒有被設置,但是目標類實現了一個(或者更多)接口,那麼ProxyFactoryBean將自動檢測到這個目標類已經實現了至少一個接口,創建一個基於JDK的代理。
*必備屬性:代理目標類(target,可以使用引用bean或匿名bean,推薦後者)、通知(advisor或advice)
*可選屬性:代理接口(proxyInterfaces,使用JDK動態代理)、代理類屬性(proxyTargetClass的值爲true時,則使用cglib代理)
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myBeforeAdvice" class="com.lmr.spring.aop.api.MyBeforeAdvice"></bean>
<bean id="myThrowsAdvice" class="com.lmr.spring.aop.api.MyThrowsAdvice"></bean>
<bean id="myAfterReturnAdvice" class="com.lmr.spring.aop.api.MyAfterReturnAdvice"></bean>
<bean id="myAroundAdvice" class="com.lmr.spring.aop.api.MyAroundAdvice"></bean>
<bean id="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>sa*</value>
</list>
</property>
</bean>
<bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="myBeforeAdvice"></property>
<property name="pointcut" ref="pointcutBean"></property>
</bean>
<!-- <bean id="myLogicImplTarget" class="com.lmr.spring.aop.api.MyLogicImpl"></bean> -->
<bean id="baseProxyBean" class="org.springframework.aop.framework.ProxyFactoryBean" lazy-init="true" abstract="true"></bean>
<!-- 使用父子bean定義以及內部bean定義 -->
<!-- <bean id="myLogicImpl" parent="baseProxyBean"> -->
<bean id="myLogicImpl" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.lmr.spring.aop.api.MyLogic</value>
</property>
<property name="target">
<!-- <ref bean="myLogicImplTarget" /> -->
<bean class="com.lmr.spring.aop.api.MyLogicImpl"></bean>
<!-- 通過使用匿名內部bean來隱藏目標和代理之間的區別 -->
</property>
<property name="interceptorNames">
<list>
<!-- <value>my*</value> -->
<!-- 用*做通配,匹配所有攔截器加入通知鏈,注意:只能適用於擁有攔截器接口(Interceptor),不適用於Advice -->
<value>defaultAdvisor</value>
<value>myAfterReturnAdvice</value>
<value>myAroundAdvice</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
</beans>
public class MyLogicImpl implements MyLogic{
@Override
public String save() {
// TODO Auto-generated method stub
System.out.println("MyLogicImpl save");
return "MyLogicImpl save";
}
}
@Test
public void TestLogicSave(){
MyLogic logic=super.getBean("myLogicImpl");
logic.save();
}
結果:
MyBeforeAdvice save com.lmr.spring.aop.api.MyLogicImpl
MyAroundAdvice 1 save java.lang.reflect.Method
MyLogicImpl save
MyAroundAdvice 2 MyLogicImpl save
MyAfterReturnAdvice save com.lmr.spring.aop.api.MyLogicImpl MyLogicImpl save
AspectJ
AspectJ是編譯期的AOP
@AspectJ的風格類似純java註解的普通Java類
對@AspectJ支持可以使用XML或Java風格的配置,要確保AspectJ的aspectjweaver.jar庫存在
//兩種配置方式
@Configuration
@EnableAspectJAutoProxy
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@AspectJ切面使用@Aspect註解配置,擁有@Aspect的任何bean將被Spring自動識別並應用。
用@Aspect註解的類可以有方法和字段,也可能包括切入點(pointcut),通知(Advice)和引入(introduction)聲明
@Aspect註解是不能夠通過類路徑自動檢測發現的,所以需要配合使用@Component註釋或者在xml中配置bean
pointcut
一個切入點通過一個普通的方法定義來提供,並且切入點表達式使用@Pointcut註解,方法返回類型必須爲void
execution 匹配方法執行的連接點
within 限定匹配特定類型的連接點
this 匹配特定連接點的bean引用是指定類型的實例的限制
target 限定匹配特定連接點的目標對象是指定類型的實例
args 限定匹配特定連接點的參數是指定類型的實例
切入點表達式可以通過&&、||和!進行組合,也可以通過名字引用切入點表達式
@Component
@Aspect
public class AspectConfig {
@Pointcut("execution(* com.lmr.spring.aop.aspectj.MyBiz.*(..))")
public void pointCut(){
}
@Pointcut("winthin(* com.lmr.spring.aop.aspectj.*)")
public void bizpointCut(){
}
@Before(value = "pointCut()")
public void before(){
System.out.println("AspectConfig before");
}
@Before("pointCut() && args(arg)")
public void beforewithparam(String arg){
System.out.println("AspectConfig beforewithparam : "+arg);
}
@AfterReturning(pointcut="pointCut()", returning="returnValue")
public void afterreturning(Object returnValue){
System.out.println("AspectConfig afterreturning : "+returnValue);
}
@AfterThrowing(pointcut="pointCut()", throwing="e")
public void afterthrowing(Exception e){
System.out.println("AspectConfig afterthrowing : "+e.getMessage());
}
@After("pointCut()")
public void after(){
System.out.println("AspectConfig after");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("AspectConfig around1");
Object obj=pjp.proceed();
System.out.println("AspectConfig around2");
return obj;
}
}
@Service
public class MyBiz {
public String save(String word){
System.out.println("MyBiz save "+word);
// throw new RuntimeException("MyBiz save RuntimeException");
return "MyBiz save "+word;
}
}
@Test
public void TestSave(){
MyBiz biz=super.getBean("myBiz");
biz.save("123456");
}
結果:
AspectConfig around1
AspectConfig before
AspectConfig beforewithparam : 123456
MyBiz save 123456
AspectConfig around2
AspectConfig after
AspectConfig afterreturning : MyBiz save 123456