生活随笔
收集整理的這篇文章主要介紹了
Spring 实践:AOP
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
AOP引介
AOP(Aspect Oriented Programing)面向切面編程采用橫向抽取機(jī)制,以取代傳統(tǒng)的縱向繼承體系的重復(fù)性代碼(如性能監(jiān)控/事務(wù)管理/安全檢查/緩存實(shí)現(xiàn)等).
橫向抽取代碼復(fù)用: 基于代理技術(shù),在不修改原來(lái)代碼的前提下,對(duì)原有方法進(jìn)行增強(qiáng).
Spring AOP 歷史
- 1.2開(kāi)始, Spring開(kāi)始支持AOP技術(shù)(Spring AOP)
Spring AOP使用純Java實(shí)現(xiàn),不需要專門的編譯過(guò)程和類加載器,在運(yùn)行期通過(guò)代理方式向目標(biāo)類織入增強(qiáng)代碼. - 2.0之后, 為了簡(jiǎn)化AOP開(kāi)發(fā), Spring開(kāi)始支持AspectJ(一個(gè)基于Java的AOP框架)框架.
AOP相關(guān)術(shù)語(yǔ)
術(shù)語(yǔ)中文描述
| 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ì)象來(lái)創(chuàng)建代理對(duì)象的過(guò)程(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è)開(kāi)源/高性能/高質(zhì)量的Code生成類庫(kù),可以在運(yùn)行期動(dòng)態(tài)擴(kuò)展Java類與實(shí)現(xiàn)Java接口.
cglib比java.lang.reflect.Proxy更強(qiáng)的在于它不僅可以接管接口類的方法,還可以接管普通類的方法(cglib項(xiàng)目).從3.2開(kāi)始, spring-core包中內(nèi)置cglib類,因此可以不用添加額外依賴.
- UserDAO(并沒(méi)有實(shí)現(xiàn)接口)
| 1234567891011121314 | /**?* @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());????}} |
| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 | 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?????????*/????????@Override????????public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {????????????// 前置增強(qiáng)????????????System.out.println("+ Before Advice ...");????????????// 執(zhí)行目標(biāo)方法????????????//Object result = method.invoke(target, args);????????????Object result = proxy.invoke(target, args);????????????// 后置增強(qiáng)????????????System.out.println("+ After Advice ...");????????????return result;????????}????};????public Object createProxy() {????????// 1. 創(chuàng)建Enhancer對(duì)象????????Enhancer enhancer = new Enhancer();????????// 2. cglib創(chuàng)建代理, 對(duì)目標(biāo)對(duì)象創(chuàng)建子對(duì)象????????enhancer.setSuperclass(target.getClass());????????// 3. 傳入回調(diào)接口, 對(duì)目標(biāo)增強(qiáng)????????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的底層通過(guò)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ì)象沒(méi)有實(shí)現(xiàn)接口,則Spring使用cglib庫(kù)生成目標(biāo)對(duì)象的子類. - Spring只支持方法連接點(diǎn),不提供屬性連接.
- 標(biāo)記為final的方法不能被代理,因?yàn)闊o(wú)法進(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中添加如下依賴:
| 12345678910 | <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> |
| 12345678910 | /**?* @author jifang?* @since 16/3/3 下午2:50.?*/public interface OrderService {????void save();????Integer delete(Integer param);} |
| 12345678910111213 | public class OrderServiceImpl implements OrderService {????@Override????public void save() {????????System.out.println("添加...");????}????@Override????public Integer delete(Integer param) {????????System.out.println("刪除...");????????return param;????}} |
| 12345678910111213141516171819 | /**?* 實(shí)現(xiàn)MethodInterceptor接口定義環(huán)繞通知?*?* @author jifang?* @since 16/3/6 下午2:54.?*/public class ConcreteInterceptor implements MethodInterceptor {????@Override????public 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ì)象與通知(沒(méi)有使用AOP名稱空間).
| 123456789101112131415161718 | <?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">????<!-- target -->????<bean id="service" class="com.fq.service.impl.OrderServiceImpl"/>????<!-- advice -->????<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> |
| 123456789101112131415 | @RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:spring/applicationContext.xml")public class AOPClient {????@Autowired????// 必須指定使用代理對(duì)象名稱, 否則不予代理????@Qualifier("serviceProxy")????private OrderService service;????@Test????public void client() {????????service.save();????????service.delete(88);????}} |
這種方式的缺陷在于每個(gè)Target都必須手動(dòng)指定ProxyFactoryBean對(duì)其代理(不能批量指定),而且這種方式會(huì)在Spring容器中存在兩份Target對(duì)象(代理前/代理后),浪費(fèi)資源,且容易出錯(cuò)(比如沒(méi)有指定@Qualifier).
Spring自動(dòng)代理 – 引入AspectJ
通過(guò)AspectJ引入Pointcut切點(diǎn)定義
| 12345678910111213141516171819202122232425262728293031 | <?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">????<!-- target -->????<bean id="service" class="com.fq.service.impl.OrderServiceImpl"/>????<!-- advice -->????<bean id="advice" class="com.fq.advice.ConcreteInterceptor"/>????<!-- 配置切面 : proxy-target-class確定是否使用CGLIB -->????<aop:config proxy-target-class="true">????????<!--????????????aop:pointcut : 切點(diǎn)定義????????????aop:advisor: 定義Spring傳統(tǒng)AOP的切面,只支持一個(gè)pointcut/一個(gè)advice????????????aop:aspect : 定義AspectJ切面的,可以包含多個(gè)pointcut/多個(gè)advice????????-->????????<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中的很多語(yǔ)法結(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方式開(kāi)發(fā)以簡(jiǎn)化AOP配置.
AspectJ-XML-AOP
使用AspectJ編寫Advice無(wú)需實(shí)現(xiàn)任何接口,而且可以將多個(gè)通知寫入一個(gè)切面類.
前置通知
| 12345678910111213141516171819202122 | /**?* @author jifang?* @since 16/3/3 下午5:38.?*/public class Aspect {????/**?????* 無(wú)返回值?????*/????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());????}} |
| 123456789101112131415161718192021222324252627282930313233343536 | <?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切面配置 -->????<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)方法無(wú)法執(zhí)行);
- 可以通過(guò)JoinPoint參數(shù)獲得當(dāng)前攔截對(duì)象和方法等信息.
后置通知
| 123 | public void afterReturning(JoinPoint point, Object result) {????System.out.printf("后置增強(qiáng), 結(jié)果為 %s%n", result);} |
| 1 | <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/> |
后置通知可以獲得方法返回值,但在配置文件定義返回值參數(shù)名必須與后置通知方法參數(shù)名一致(如result).
環(huán)繞通知
| 123456789 | 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;} |
| 1 | <aop:around method="around" arg-names="point" pointcut-ref="pointcut"/> |
環(huán)繞通知可以實(shí)現(xiàn)任何通知的效果, 甚至可以阻止目標(biāo)方法的執(zhí)行.
拋出通知
| 12345678 | 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);} |
| 1 | <aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/> |
throwing屬性指定異常對(duì)象名, 該名稱應(yīng)和方法定義參數(shù)名一致.
最終通知
| 123 | public void after(JoinPoint point) {????System.out.println("最終通知, 釋放資源");} |
| 1 | <aop:after method="after" pointcut-ref="pointcut"/> |
無(wú)論目標(biāo)方法是否出現(xiàn)異常,該通知都會(huì)執(zhí)行(類似finally代碼塊, 應(yīng)用場(chǎng)景為釋放資源).
AspectJ-Annotation-AOP
@AspectJ是AspectJ 1.5新增功能,可以通過(guò)JDK注解技術(shù),直接在Bean類中定義切面.
AspectJ預(yù)定義的注解有:@Before/@AfterReturning/@Around/@AfterThrowing/@DeclareParents/@After.描述同前.
使用AspectJ注解AOP需要在applicationContext.xml文件中開(kāi)啟注解自動(dòng)代理功能:
| 1234567891011121314151617181920212223242526 | <?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">????<!-- 批量掃描@Component -->????<context:component-scan base-package="com.fq"/>????<!-- 啟用注解自動(dòng)代理@Aspect-->????<aop:aspectj-autoproxy/></beans> |
@Before
| 12345678910111213 | /**?* @Aspect: 指定是一個(gè)切面?* @Component: 指定可以被Spring容器掃描到?*/@Aspect@Componentpublic 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
| 1234 | @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
| 12345678910 | @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
| 1234567 | @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
| 1234 | @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)方法
無(wú)參/無(wú)返回值/方法名為切點(diǎn)名:
| 12345678910 | /**?* @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):
| 1234 | @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ò)誤日志 |
| 最終通知 | 釋放資源 |
from:?http://www.importnew.com/19041.html
總結(jié)
以上是生活随笔為你收集整理的Spring 实践:AOP的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。