Spring 實(shí)踐
標(biāo)簽: Java與設(shè)計(jì)模式
AOP引介
AOP(Aspect Oriented Programing)面向切面編程采用橫向抽取機(jī)制,以取代傳統(tǒng)的縱向繼承體系的重復(fù)性代碼(如性能監(jiān)控/事務(wù)管理/安全檢查/緩存實(shí)現(xiàn)等).
橫向抽取代碼復(fù)用: 基于代理技術(shù),在不修改原來代碼的前提下,對(duì)原有方法進(jìn)行增強(qiáng).
Spring AOP 歷史
- 1.2開始, Spring開始支持AOP技術(shù)(Spring AOP)
Spring AOP使用純Java實(shí)現(xiàn),不需要專門的編譯過程和類加載器,在運(yùn)行期通過代理方式向目標(biāo)類織入增強(qiáng)代碼. - 2.0之后, 為了簡(jiǎn)化AOP開發(fā), Spring開始支持AspectJ(一個(gè)基于Java的AOP框架)框架.
AOP相關(guān)術(shù)語
術(shù)語中文描述
| Joinpoint | 連接點(diǎn) | 指那些被攔截到的點(diǎn).在Spring中,這些點(diǎn)指方法(因?yàn)镾pring只支持方法類型的連接點(diǎn)). |
| Pointcut | 切入點(diǎn) | 指需要(配置)被增強(qiáng)的Joinpoint. |
| Advice | 通知/增強(qiáng) | 指攔截到Joinpoint后要做的操作.通知分為前置通知/后置通知/異常通知/最終通知/環(huán)繞通知等. |
| Aspect | 切面 | 切入點(diǎn)和通知的結(jié)合. |
| Target | 目標(biāo)對(duì)象 | 需要被代理(增強(qiáng))的對(duì)象. |
| Proxy | 代理對(duì)象 | 目標(biāo)對(duì)象被AOP 織入 增強(qiáng)/通知后,產(chǎn)生的對(duì)象. |
| Weaving | 織入 | 指把增強(qiáng)/通知應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建代理對(duì)象的過程(Spring采用動(dòng)態(tài)代理織入,AspectJ采用編譯期織入和類裝載期織入). |
| Introduction | 引介 | 一種特殊通知,在不修改類代碼的前提下,可以在運(yùn)行期為類動(dòng)態(tài)地添加一些Method/Field(不常用). |
其他關(guān)于AOP理論知識(shí)可參考AOP技術(shù)研究.
AOP實(shí)現(xiàn)
Spring AOP代理實(shí)現(xiàn)有兩種:JDK動(dòng)態(tài)代理和Cglib框架動(dòng)態(tài)代理, JDK動(dòng)態(tài)代理可以參考博客代理模式的動(dòng)態(tài)代理部分, 在這里僅介紹CGLib框架實(shí)現(xiàn).
cglib 動(dòng)態(tài)代理
cglib(Code Generation Library)是一個(gè)開源/高性能/高質(zhì)量的Code生成類庫,可以在運(yùn)行期動(dòng)態(tài)擴(kuò)展Java類與實(shí)現(xiàn)Java接口.
cglib比java.lang.reflect.Proxy更強(qiáng)的在于它不僅可以接管接口類的方法,還可以接管普通類的方法(cglib項(xiàng)目).從3.2開始, spring-core包中內(nèi)置cglib類,因此可以不用添加額外依賴.
- UserDAO(并沒有實(shí)現(xiàn)接口)
/*** @author jifang* @since 16/3/3 上午11:16.*/
public class UserDAO {public void add(Object o) {System.out.println(
"UserDAO -> Add: " + o.toString());}
public void get(Object o) {System.out.println(
"UserDAO -> Get: " + o.toString());}
}
public class CGLibProxyFactory {private Object target;
public CGLibProxyFactory(Object target) {
this.target = target;}
private Callback callback =
new MethodInterceptor() {
/**** @param obj 代理對(duì)象* @param method 當(dāng)期調(diào)用方法* @param args 方法參數(shù)* @param proxy 被調(diào)用方法的代理對(duì)象(用于執(zhí)行父類的方法)* @return* @throws Throwable*/@Overridepublic Object
intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {System.out.println(
"+ Before Advice ...");Object result = proxy.invoke(target, args);System.out.println(
"+ After Advice ...");
return result;}};
public Object
createProxy() {Enhancer enhancer =
new Enhancer();enhancer.setSuperclass(target.getClass());enhancer.setCallback(callback);
return enhancer.create();}
public static void main(String[] args) {UserDAO proxy = (UserDAO)
new CGLibProxyFactory(
new UserDAO()).createProxy();proxy.get(
"hello");proxy.add(
"world");}
}
AOP小結(jié)
- Spring AOP的底層通過JDK/cglib動(dòng)態(tài)代理為目標(biāo)對(duì)象進(jìn)行橫向織入:
1) 若目標(biāo)對(duì)象實(shí)現(xiàn)了接口,則Spring使用JDK的java.lang.reflect.Proxy代理.
2) 若目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,則Spring使用cglib庫生成目標(biāo)對(duì)象的子類. - Spring只支持方法連接點(diǎn),不提供屬性連接.
- 標(biāo)記為final的方法不能被代理,因?yàn)闊o法進(jìn)行覆蓋.
- 程序應(yīng)優(yōu)先對(duì)針對(duì)接口代理,這樣便于程序解耦/維護(hù).
Spring AOP
AOP聯(lián)盟為通知Advice定義了org.aopalliance.aop.Advice接口, Spring在Advice的基礎(chǔ)上,根據(jù)通知在目標(biāo)方法的連接點(diǎn)位置,擴(kuò)充為以下五類:
通知接口描述
| 前置通知 | MethodBeforeAdvice | 在目標(biāo)方法執(zhí)行前實(shí)施增強(qiáng) |
| 后置通知 | AfterReturningAdvice | …執(zhí)行后實(shí)施增強(qiáng) |
| 環(huán)繞通知 | MethodInterceptor | ..執(zhí)行前后實(shí)施增強(qiáng) |
| 異常拋出通知 | ThrowsAdvice | …拋出異常后實(shí)施增強(qiáng) |
| 引介通知 | IntroductionInterceptor | 在目標(biāo)類中添加新的方法和屬性(少用) |
- 添加Spring的AOP依賴
使用Spring的AOP和AspectJ需要在pom.xml中添加如下依賴:
<dependency><groupId>org.springframework
</groupId><artifactId>spring-aop
</artifactId><version>${spring.version}
</version>
</dependency>
<dependency><groupId>org.springframework
</groupId><artifactId>spring-aspects
</artifactId><version>${spring.version}
</version>
</dependency>
/*** @author jifang* @since 16/3/3 下午2:50.*/
public interface OrderService {void save();Integer delete(Integer param);
}
public class OrderServiceImpl implements OrderService {@Overridepublic void save() {System.out.println(
"添加...");}
@Overridepublic Integer
delete(Integer param) {System.out.println(
"刪除...");
return param;}
}
/*** 實(shí)現(xiàn)MethodInterceptor接口定義環(huán)繞通知** @author jifang* @since 16/3/6 下午2:54.*/
public class ConcreteInterceptor implements MethodInterceptor {@Overridepublic Object
invoke(MethodInvocation invocation)
throws Throwable {System.out.println(
"前置通知 -> ");Object result = invocation.proceed();System.out.println(
"<- 后置通知");
return result;}
}
Spring手動(dòng)代理
- 配置代理
Spring最原始的AOP支持, 手動(dòng)指定目標(biāo)對(duì)象與通知(沒有使用AOP名稱空間).
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="service" class="com.fq.service.impl.OrderServiceImpl"/><bean id="advice" class="com.fq.advice.ConcreteInterceptor"/><bean id="serviceProxy" class="org.springframework.aop.framework.ProxyFactoryBean"><property name="target" ref="service"/><property name="interceptorNames" value="advice"/><property name="proxyTargetClass" value="false"/></bean>
</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =
"classpath:spring/applicationContext.xml")
public class AOPClient {@Autowired@Qualifier(
"serviceProxy")
private OrderService service;
@Testpublic void client() {service.save();service.delete(
88);}
}
這種方式的缺陷在于每個(gè)Target都必須手動(dòng)指定ProxyFactoryBean對(duì)其代理(不能批量指定),而且這種方式會(huì)在Spring容器中存在兩份Target對(duì)象(代理前/代理后),浪費(fèi)資源,且容易出錯(cuò)(比如沒有指定@Qualifier).
Spring自動(dòng)代理 - 引入AspectJ
通過AspectJ引入Pointcut切點(diǎn)定義
- Target/Advice同前
- 定義切面表達(dá)式
通過execution函數(shù)定義切點(diǎn)表達(dá)式(定義切點(diǎn)的方法切入)
execution(<訪問修飾符> <返回類型><方法名>(<參數(shù)>)<異常>)
如:
1) execution(public * *(..)) # 匹配所有public方法.
2) execution(* com.fq.dao.*(..)) # 匹配指定包下所有類方法(不包含子包)
3) execution(* com.fq.dao..*(..)) # 匹配指定包下所有類方法(包含子包)
4) execution(* com.fq.service.impl.OrderServiceImple.*(..)) # 匹配指定類所有方法
5) execution(* com.fq.service.OrderService+.*(..)) # 匹配實(shí)現(xiàn)特定接口所有類方法
6) execution(* save*(..)) # 匹配所有save開頭的方法
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="service" class="com.fq.service.impl.OrderServiceImpl"/><bean id="advice" class="com.fq.advice.ConcreteInterceptor"/><aop:config proxy-target-class="true"><aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/><aop:advisor advice-ref="advice" pointcut-ref="pointcut"/></aop:config></beans>
AspectJ AOP
AspectJ是一個(gè)基于Java的AOP框架,提供了強(qiáng)大的AOP功能,其他很多AOP框架都借鑒或采納了AspectJ的一些思想,Spring2.0以后增加了對(duì)AspectJ切點(diǎn)表達(dá)式支持(如上),并在Spring3.0之后與AspectJ進(jìn)行了很好的集成.
在Java領(lǐng)域,AspectJ中的很多語法結(jié)構(gòu)基本上已成為AOP領(lǐng)域的標(biāo)準(zhǔn), 他定義了如下幾類通知類型:
通知接口描述
| 前置通知 | @Before | 相當(dāng)于BeforeAdvice |
| 后置通知 | @AfterReturning | 相當(dāng)于AfterReturningAdvice |
| 環(huán)繞通知 | @Around | 相當(dāng)于MethodInterceptor |
| 拋出通知 | @AfterThrowing | 相當(dāng)于ThrowAdvice |
| 引介通知 | @DeclareParents | 相當(dāng)于IntroductionInterceptor |
| 最終final通知 | @After | 不管是否異常,該通知都會(huì)執(zhí)行 |
新版本Spring,建議使用AspectJ方式開發(fā)以簡(jiǎn)化AOP配置.
AspectJ-XML-AOP
使用AspectJ編寫Advice無需實(shí)現(xiàn)任何接口,而且可以將多個(gè)通知寫入一個(gè)切面類.
前置通知
/*** @author jifang* @since 16/3/3 下午5:38.*/
public class Aspect {/*** 無返回值*/public void before1() {System.out.println(
"前置增強(qiáng)before1");}
/*** 還可以傳入連接點(diǎn)參數(shù) JoinPoint** @param point*/public void before2(JoinPoint point) {System.out.printf(
"前置增強(qiáng)before2 %s%n", point.getKind());}
}
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.fq.service"/><bean id="advice" class="com.fq.advice.Aspect"/><aop:config><aop:aspect ref="advice"><aop:pointcut id="pointcut" expression="execution(* com.fq.service.impl.OrderServiceImpl.*(..))"/><aop:before method="before1" pointcut-ref="pointcut"/><aop:before method="before2" pointcut-ref="pointcut"/></aop:aspect></aop:config></beans>
- 前置通知小結(jié)
- 前置通知會(huì)保證在目標(biāo)方法執(zhí)行前執(zhí)行;
- 前置通知默認(rèn)不能阻止目標(biāo)方法執(zhí)行(但如果通知拋出異常,則目標(biāo)方法無法執(zhí)行);
- 可以通過JoinPoint參數(shù)獲得當(dāng)前攔截對(duì)象和方法等信息.
后置通知
public void afterReturning(JoinPoint point, Object result) {System.
out.printf(
"后置增強(qiáng), 結(jié)果為 %s%n", result);
}
<aop:after-returning
method="afterReturning" returning="result" pointcut-ref="pointcut"/>
后置通知可以獲得方法返回值,但在配置文件定義返回值參數(shù)名必須與后置通知方法參數(shù)名一致(如result).
環(huán)繞通知
public Object around(ProceedingJoinPoint point) throws Throwable {System
.out.printf(
"環(huán)繞前置增強(qiáng) method: %s, args: %s%n", point
.toShortString(), Arrays
.toString(point
.getArgs()))Object result = point
.proceed(point
.getArgs())System
.out.printf(
"環(huán)繞后置增強(qiáng) result: %s%n", result)return result
}
<aop:around
method="around" arg-names="point" pointcut-ref="pointcut"/>
環(huán)繞通知可以實(shí)現(xiàn)任何通知的效果, 甚至可以阻止目標(biāo)方法的執(zhí)行.
拋出通知
private static final Logger LOGGER = LoggerFactory
.getLogger(Aspect
.class)public void afterThrowing(JoinPoint point, Throwable ex) {String message = new StringBuilder(
"method ")
.append(point
.getSignature()
.getName())
.append(
" error")
.toString()System
.out.println(message)LOGGER
.error(
"{},", message, ex)
}
<aop:after-throwing
method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
throwing屬性指定異常對(duì)象名, 該名稱應(yīng)和方法定義參數(shù)名一致.
最終通知
public void after(JoinPoint point) {System.
out.println(
"最終通知, 釋放資源");
}
<aop:after
method="after" pointcut-ref="pointcut"/>
無論目標(biāo)方法是否出現(xiàn)異常,該通知都會(huì)執(zhí)行(類似finally代碼塊, 應(yīng)用場(chǎng)景為釋放資源).
AspectJ-Annotation-AOP
@AspectJ是AspectJ 1.5新增功能,可以通過JDK注解技術(shù),直接在Bean類中定義切面.
AspectJ預(yù)定義的注解有:@Before/@AfterReturning/@Around/@AfterThrowing/@DeclareParents/@After.描述同前.
使用AspectJ注解AOP需要在applicationContext.xml文件中開啟注解自動(dòng)代理功能:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.fq"/><aop:aspectj-autoproxy/>
</beans>
@Before
/*** @Aspect: 指定是一個(gè)切面* @Component: 指定可以被Spring容器掃描到*/
@Aspect
@Component
public class CustomAspect {@Before(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void before(JoinPoint point) {System.out.printf(
"前置增強(qiáng)before2 %s%n", point.getKind());}
}
@AfterReturning
@AfterReturning(
value =
"execution(* com.fq.service.impl.OrderServiceImpl.d*(..))", returning =
"result")
public void afterReturning(JoinPoint point, Object result) {System.
out.printf(
"后置增強(qiáng), 結(jié)果為 %s%n", result);
}
@Around
@Around(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public Object
around(ProceedingJoinPoint point)
throws Throwable {
long start = System.currentTimeMillis();Object result = point.proceed(point.getArgs());
long time = System.currentTimeMillis() - start;System.out.printf(
"method %s invoke consuming %d ms%n", point.toLongString(), time);
return result;
}
如果不調(diào)用ProceedingJoinPoint的proceed方法,那么目標(biāo)方法就不執(zhí)行了.
@AfterThrowing
@AfterThrowing(value =
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))", throwing =
"ex")
public void afterThrowing(JoinPoint point, Throwable ex) {String message = new StringBuilder(
"method ")
.append(point
.getSignature()
.getName())
.append(
" error")
.toString()System
.out.println(message)LOGGER
.error(
"{},", message, ex)
}
@After
@After(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void after(JoinPoint point) {System.
out.println(
"最終通知, 釋放資源");
}
@Pointcut定義切點(diǎn)
對(duì)于重復(fù)的切點(diǎn),可以使用@Pointcut進(jìn)行定義, 然后在通知注解內(nèi)引用.
- 定義切點(diǎn)方法
無參/無返回值/方法名為切點(diǎn)名:
/*** @author jifang* @since 16/3/4 上午11:47.*/
public class OrderServicePointcut {@Pointcut(
"execution(* com.fq.service.impl.OrderServiceImpl.*(..))")
public void pointcut() {}
}
- 引用切點(diǎn)
在Advice上像調(diào)用方法一樣引用切點(diǎn):
@After(
"OrderServicePointcut.pointcut()")
public void after(JoinPoint point) {System.
out.println(
"最終通知, 釋放資源");
}
1) 如果切點(diǎn)與切面在同一個(gè)類內(nèi), 可省去類名前綴;
2) 當(dāng)需要通知多個(gè)切點(diǎn)時(shí),可以使用||/&&進(jìn)行連接.
小結(jié)
通知描述
| 前置通知 | 權(quán)限控制(少用) |
| 后置通知 | 少用 |
| 環(huán)繞通知 | 權(quán)限控制/性能監(jiān)控/緩存實(shí)現(xiàn)/事務(wù)管理 |
| 異常通知 | 發(fā)生異常后,記錄錯(cuò)誤日志 |
| 最終通知 | 釋放資源 |
總結(jié)
以上是生活随笔為你收集整理的Spring 实践 -AOP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。