生活随笔
收集整理的這篇文章主要介紹了
Spring 实践:AOP
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
AOP引介
AOP(Aspect Oriented Programing)面向切面編程采用橫向抽取機制,以取代傳統的縱向繼承體系的重復性代碼(如性能監控/事務管理/安全檢查/緩存實現等).
橫向抽取代碼復用: 基于代理技術,在不修改原來代碼的前提下,對原有方法進行增強.
Spring AOP 歷史
- 1.2開始, Spring開始支持AOP技術(Spring AOP)
Spring AOP使用純Java實現,不需要專門的編譯過程和類加載器,在運行期通過代理方式向目標類織入增強代碼. - 2.0之后, 為了簡化AOP開發, Spring開始支持AspectJ(一個基于Java的AOP框架)框架.
AOP相關術語
術語中文描述
| Joinpoint | 連接點 | 指那些被攔截到的點.在Spring中,這些點指方法(因為Spring只支持方法類型的連接點). |
| Pointcut | 切入點 | 指需要(配置)被增強的Joinpoint. |
| Advice | 通知/增強 | 指攔截到Joinpoint后要做的操作.通知分為前置通知/后置通知/異常通知/最終通知/環繞通知等. |
| Aspect | 切面 | 切入點和通知的結合. |
| Target | 目標對象 | 需要被代理(增強)的對象. |
| Proxy | 代理對象 | 目標對象被AOP?織入?增強/通知后,產生的對象. |
| Weaving | 織入 | 指把增強/通知應用到目標對象來創建代理對象的過程(Spring采用動態代理織入,AspectJ采用編譯期織入和類裝載期織入). |
| Introduction | 引介 | 一種特殊通知,在不修改類代碼的前提下,可以在運行期為類動態地添加一些Method/Field(不常用). |
其他關于AOP理論知識可參考AOP技術研究.
AOP實現
Spring AOP代理實現有兩種:JDK動態代理和Cglib框架動態代理, JDK動態代理可以參考博客代理模式的動態代理部分, 在這里僅介紹CGLib框架實現.
cglib 動態代理
cglib(Code Generation Library)是一個開源/高性能/高質量的Code生成類庫,可以在運行期動態擴展Java類與實現Java接口.
cglib比java.lang.reflect.Proxy更強的在于它不僅可以接管接口類的方法,還可以接管普通類的方法(cglib項目).從3.2開始, spring-core包中內置cglib類,因此可以不用添加額外依賴.
| 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?? 代理對象?????????* @param method??? 當期調用方法?????????* @param args? 方法參數?????????* @param proxy 被調用方法的代理對象(用于執行父類的方法)?????????* @return?????????* @throws Throwable?????????*/????????@Override????????public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {????????????// 前置增強????????????System.out.println("+ Before Advice ...");????????????// 執行目標方法????????????//Object result = method.invoke(target, args);????????????Object result = proxy.invoke(target, args);????????????// 后置增強????????????System.out.println("+ After Advice ...");????????????return result;????????}????};????public Object createProxy() {????????// 1. 創建Enhancer對象????????Enhancer enhancer = new Enhancer();????????// 2. cglib創建代理, 對目標對象創建子對象????????enhancer.setSuperclass(target.getClass());????????// 3. 傳入回調接口, 對目標增強????????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小結
- Spring AOP的底層通過JDK/cglib動態代理為目標對象進行橫向織入:
1) 若目標對象實現了接口,則Spring使用JDK的java.lang.reflect.Proxy代理.
2) 若目標對象沒有實現接口,則Spring使用cglib庫生成目標對象的子類. - Spring只支持方法連接點,不提供屬性連接.
- 標記為final的方法不能被代理,因為無法進行覆蓋.
- 程序應優先對針對接口代理,這樣便于程序解耦/維護.
Spring AOP
AOP聯盟為通知Advice定義了org.aopalliance.aop.Advice接口, Spring在Advice的基礎上,根據通知在目標方法的連接點位置,擴充為以下五類:
通知接口描述
| 前置通知 | MethodBeforeAdvice | 在目標方法執行前實施增強 |
| 后置通知 | AfterReturningAdvice | …執行后實施增強 |
| 環繞通知 | MethodInterceptor | ..執行前后實施增強 |
| 異常拋出通知 | ThrowsAdvice | …拋出異常后實施增強 |
| 引介通知 | IntroductionInterceptor | 在目標類中添加新的方法和屬性(少用) |
- 添加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 | /**?* 實現MethodInterceptor接口定義環繞通知?*?* @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手動代理
- 配置代理
Spring最原始的AOP支持, 手動指定目標對象與通知(沒有使用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????// 必須指定使用代理對象名稱, 否則不予代理????@Qualifier("serviceProxy")????private OrderService service;????@Test????public void client() {????????service.save();????????service.delete(88);????}} |
這種方式的缺陷在于每個Target都必須手動指定ProxyFactoryBean對其代理(不能批量指定),而且這種方式會在Spring容器中存在兩份Target對象(代理前/代理后),浪費資源,且容易出錯(比如沒有指定@Qualifier).
Spring自動代理 – 引入AspectJ
通過AspectJ引入Pointcut切點定義
| 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 : 切點定義????????????aop:advisor: 定義Spring傳統AOP的切面,只支持一個pointcut/一個advice????????????aop:aspect : 定義AspectJ切面的,可以包含多個pointcut/多個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是一個基于Java的AOP框架,提供了強大的AOP功能,其他很多AOP框架都借鑒或采納了AspectJ的一些思想,Spring2.0以后增加了對AspectJ切點表達式支持(如上),并在Spring3.0之后與AspectJ進行了很好的集成.
在Java領域,AspectJ中的很多語法結構基本上已成為AOP領域的標準, 他定義了如下幾類通知類型:
通知接口描述
| 前置通知 | @Before | 相當于BeforeAdvice |
| 后置通知 | @AfterReturning | 相當于AfterReturningAdvice |
| 環繞通知 | @Around | 相當于MethodInterceptor |
| 拋出通知 | @AfterThrowing | 相當于ThrowAdvice |
| 引介通知 | @DeclareParents | 相當于IntroductionInterceptor |
| 最終final通知 | @After | 不管是否異常,該通知都會執行 |
新版本Spring,建議使用AspectJ方式開發以簡化AOP配置.
AspectJ-XML-AOP
使用AspectJ編寫Advice無需實現任何接口,而且可以將多個通知寫入一個切面類.
前置通知
| 12345678910111213141516171819202122 | /**?* @author jifang?* @since 16/3/3 下午5:38.?*/public class Aspect {????/**?????* 無返回值?????*/????public void before1() {????????System.out.println("前置增強before1");????}????/**?????* 還可以傳入連接點參數 JoinPoint?????*?????* @param point?????*/????public void before2(JoinPoint point) {????????System.out.printf("前置增強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> |
- 前置通知小結
- 前置通知會保證在目標方法執行前執行;
- 前置通知默認不能阻止目標方法執行(但如果通知拋出異常,則目標方法無法執行);
- 可以通過JoinPoint參數獲得當前攔截對象和方法等信息.
后置通知
| 123 | public void afterReturning(JoinPoint point, Object result) {????System.out.printf("后置增強, 結果為 %s%n", result);} |
| 1 | <aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointcut"/> |
后置通知可以獲得方法返回值,但在配置文件定義返回值參數名必須與后置通知方法參數名一致(如result).
環繞通知
| 123456789 | public Object around(ProceedingJoinPoint point) throws Throwable {????System.out.printf("環繞前置增強 method: %s, args: %s%n", point.toShortString(), Arrays.toString(point.getArgs()));????Object result = point.proceed(point.getArgs());????System.out.printf("環繞后置增強 result: %s%n", result);????return result;} |
| 1 | <aop:around method="around" arg-names="point" pointcut-ref="pointcut"/> |
環繞通知可以實現任何通知的效果, 甚至可以阻止目標方法的執行.
拋出通知
| 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屬性指定異常對象名, 該名稱應和方法定義參數名一致.
最終通知
| 123 | public void after(JoinPoint point) {????System.out.println("最終通知, 釋放資源");} |
| 1 | <aop:after method="after" pointcut-ref="pointcut"/> |
無論目標方法是否出現異常,該通知都會執行(類似finally代碼塊, 應用場景為釋放資源).
AspectJ-Annotation-AOP
@AspectJ是AspectJ 1.5新增功能,可以通過JDK注解技術,直接在Bean類中定義切面.
AspectJ預定義的注解有:@Before/@AfterReturning/@Around/@AfterThrowing/@DeclareParents/@After.描述同前.
使用AspectJ注解AOP需要在applicationContext.xml文件中開啟注解自動代理功能:
| 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"/>????<!-- 啟用注解自動代理@Aspect-->????<aop:aspectj-autoproxy/></beans> |
@Before
| 12345678910111213 | /**?* @Aspect: 指定是一個切面?* @Component: 指定可以被Spring容器掃描到?*/@Aspect@Componentpublic class CustomAspect {????@Before("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")????public void before(JoinPoint point) {????????System.out.printf("前置增強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("后置增強, 結果為 %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;} |
如果不調用ProceedingJoinPoint的proceed方法,那么目標方法就不執行了.
@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定義切點
對于重復的切點,可以使用@Pointcut進行定義, 然后在通知注解內引用.
| 12345678910 | /**?* @author jifang?* @since 16/3/4 上午11:47.?*/public class OrderServicePointcut {????@Pointcut("execution(* com.fq.service.impl.OrderServiceImpl.*(..))")????public void pointcut() {????}} |
| 1234 | @After("OrderServicePointcut.pointcut()")public void after(JoinPoint point) {????System.out.println("最終通知, 釋放資源");} |
1) 如果切點與切面在同一個類內, 可省去類名前綴;?
2) 當需要通知多個切點時,可以使用||/&&進行連接.
小結
通知描述
| 前置通知 | 權限控制(少用) |
| 后置通知 | 少用 |
| 環繞通知 | 權限控制/性能監控/緩存實現/事務管理 |
| 異常通知 | 發生異常后,記錄錯誤日志 |
| 最終通知 | 釋放資源 |
from:?http://www.importnew.com/19041.html
總結
以上是生活随笔為你收集整理的Spring 实践:AOP的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。