国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Spring AOP 深入剖析

2019-11-08 03:22:22
字體:
來源:轉載
供稿:網友

[toc] AOP是SPRing提供的關鍵特性之一。AOP即面向切面編程,是OOP編程的有效補充。使用AOP技術,可以將一些系統性相關的編程工作,獨立提取出來,獨立實現,然后通過切面切入進系統。從而避免了在業務邏輯的代碼中混入很多的系統相關的邏輯——比如權限管理,事物管理,日志記錄等等。這些系統性的編程工作都可以獨立編碼實現,然后通過AOP技術切入進系統即可。從而達到了 將不同的關注點分離出來 的效果。本文深入剖析Spring的AOP的原理。

1. AOP相關的概念

1) aspect :切面,切入系統的一個切面。比如事務管理是一個切面,權限管理也是一個切面;

2) Join point :連接點,也就是可以進行橫向切入的位置;

3) Advice :通知,切面在某個連接點執行的操作(分為: Before advice , After returning advice , After throwing advice , After (finally) advice , Around advice );

4) Pointcut :切點,符合切點表達式的連接點,也就是真正被切入的地方;

2. AOP 的實現原理

AOP分為靜態AOP和動態AOP。靜態AOP是指AspectJ實現的AOP,他是將切面代碼直接編譯到java類文件中。動態AOP是指將切面代碼進行動態織入實現的AOP。Spring的AOP為動態AOP,實現的技術為: JDK提供的動態代理技術 和 CGLIB(動態字節碼增強技術) 。盡管實現技術不一樣,但 都是基于代理模式 , 都是生成一個代理對象 。

1) JDK動態代理

主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。 JDK動態代理要求被代理實現一個接口,只有接口中的方法才能夠被代理 。其方法是將被代理對象注入到一個中間對象,而中間對象實現InvocationHandler接口,在實現該接口時,可以在 被代理對象調用它的方法時,在調用的前后插入一些代碼。而 Proxy.newProxyInstance() 能夠利用中間對象來生產代理對象。插入的代碼就是切面代碼。所以使用JDK動態代理可以實現AOP。我們看個例子:被代理對象實現的接口,只有接口中的方法才能夠被代理:public interface UserService { public void addUser(User user); public User getUser(int id);}

被代理對象:

public class UserServiceImpl implements UserService { public void addUser(User user) { System.out.println("add user into database."); } public User getUser(int id) { User user = new User(); user.setId(id); System.out.println("getUser from database."); return user; }}

代理中間類:

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class ProxyUtil implements InvocationHandler { private Object target; // 被代理的對象 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do sth before...."); Object result = method.invoke(target, args); System.out.println("do sth after...."); return result; } ProxyUtil(Object target){ this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; }}

測試:

import java.lang.reflect.Proxy;import net.aazj.pojo.User;public class ProxyTest { public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的對象 ProxyUtil proxyUtils = new ProxyUtil(proxyedObject); // 生成代理對象,對被代理對象的這些接口進行代理:UserServiceImpl.class.getInterfaces() UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.getUser(1); proxyObject.addUser(new User()); }}

執行結果:

do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....我們看到在 UserService接口中的方法 addUser 和 getUser方法的前面插入了我們自己的代碼。這就是JDK動態代理實現AOP的原理。我們看到該方式有一個要求, 被代理的對象必須實現接口,而且只有接口中的方法才能被代理 。

2)CGLIB (code generate libary)

字節碼生成技術實現AOP,其實就是繼承被代理對象,然后Override需要被代理的方法,在覆蓋該方法時,自然是可以插入我們自己的代碼的。因為需要Override被代理對象的方法,所以自然CGLIB技術實現AOP時,就 必須要求需要被代理的方法不能是final方法,因為final方法不能被子類覆蓋 。我們使用CGLIB實現上面的例子:package net.aazj.aop;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class CGProxy implements MethodInterceptor{ private Object target; // 被代理對象 public CGProxy(Object target){ this.target = target; } public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable { System.out.println("do sth before...."); Object result = proxy.invokeSuper(arg0, arg2); System.out.println("do sth after...."); return result; } public Object getProxyObject() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 設置父類 // 設置回調 enhancer.setCallback(this); // 在調用父類方法時,回調 this.intercept() // 創建代理對象 return enhancer.create(); }}public class CGProxyTest { public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的對象 CGProxy cgProxy = new CGProxy(proxyedObject); UserService proxyObject = (UserService) cgProxy.getProxyObject(); proxyObject.getUser(1); proxyObject.addUser(new User()); }}

輸出結果:

do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....我們看到達到了同樣的效果。它的原理是生成一個父類 enhancer.setSuperclass( this.target.getClass()) 的子類 enhancer.create() ,然后對父類的方法進行攔截enhancer.setCallback( this) . 對父類的方法進行覆蓋,所以父類方法不能是final的。

3) 接下來我們看下spring實現AOP的相關從上面的源碼我們可以看到:

if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config);

如果被代理對象實現了接口,那么就使用JDK的動態代理技術,反之則使用CGLIB來實現AOP,所以 Spring默認是使用JDK的動態代理技術實現AOP的 。

JdkDynamicAopProxy的實現其實很簡單:final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @Overridepublic Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);}

3. Spring AOP的配置

Spring中AOP的配置一般有兩種方法,一種是使用 <aop:config> 標簽在
xml中進行配置,一種是使用注解以及@Aspect風格的配置。

1) 基于的AOP配置

下面是一個典型的事務AOP的配置:<tx:advice id="transactionAdvice" transaction-manager="transactionManager"?> <tx:attributes > <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="append*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="load*" propagation="SUPPORTS" /> <tx:method name="search*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes></tx:advice><aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* net.aazj.service..*Impl.*(..))" /> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /></aop:config>

再看一個例子:

<bean id="aspectBean" class="net.aazj.aop.DataSourceInterceptor"/><aop:config> <aop:aspect id="dataSourceAspect" ref="aspectBean"> <aop:pointcut id="dataSourcePoint" expression="execution(public * net.aazj.service..*.getUser(..))" /> <aop:pointcut expression="" id=""/> <aop:before method="before" pointcut-ref="dataSourcePoint"/> <aop:after method=""/> <aop:around method=""/> </aop:aspect> <aop:aspect></aop:aspect></aop:config><aop:aspect> 配置一個切面;<aop:pointcut>配置一個切點,基于切點表達式;<aop:before>,<aop:after>,<aop:around>是定義不同類型的advise. aspectBean 是切面的處理bean:public class DataSourceInterceptor { public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }}

2) 基于注解和@Aspect風格的AOP配置

我們以事務配置為例:首先我們啟用基于注解的事務配置```python

然后掃描Service包:```python<context:component-scan base-package="net.aazj.service,net.aazj.aop" /><div class="se-preview-section-delimiter"></div>

最后在service上進行注解:

@Service("userService")@Transactionalpublic class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { System.out.println("in UserServiceImpl getUser"); System.out.println(DataSourceTypeManager.get()); return userMapper.getUser(userId); } public void addUser(String username){ userMapper.addUser(username);// int i = 1/0; // 測試事物的回滾 } public void deleteUser(int id){ userMapper.deleteByPrimaryKey(id);// int i = 1/0; // 測試事物的回滾 } @Transactional (rollbackFor = BaseBusinessException.class) public void addAndDeleteUser(String username, int id) throws BaseBusinessException{ userMapper.addUser(username); this.m1(); userMapper.deleteByPrimaryKey(id); } private void m1() throws BaseBusinessException { throw new BaseBusinessException("xxx"); } public int insertUser(User user) { return this.userMapper.insert(user); }}<div class="se-preview-section-delimiter"></div>搞定。這種事務配置方式,不需要我們書寫pointcut表達式,而是我們在需要事務的類上進行注解。但是如果我們自己來寫切面的代碼時,還是要寫pointcut表達式。下面看一個例子(自己寫切面邏輯):首先去掃描 @Aspect 注解定義的 切面:<context:component-scan base-package="net.aazj.aop" /><div class="se-preview-section-delimiter"></div>

切面代碼:

import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;@Aspect // for aop@Component // for auto scan@Order(0) // execute before @Transactionalpublic class DataSourceInterceptor { @Pointcut("execution(public * net.aazj.service..*.get*(..))") public void dataSourceSlave(){}; @Before("dataSourceSlave()") public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }}<div class="se-preview-section-delimiter"></div>我們使用到了 @Aspect 來定義一個切面;@Component是配合<context:component-scan/>,不然掃描不到;@Order定義了該切面切入的順序 ,因為在同一個切點,可能同時存在多個切面,那么在這多個切面之間就存在一個執行順序的問題。該例子是一個切換數據源的切面,那么他應該在 事務處理 切面之前執行,所以我們使用 @Order(0) 來確保先切換數據源,然后加入事務處理。@Order的參數越小,優先級越高,默認的優先級最低:/** * Annotation that defines ordering. The value is optional, and represents order value * as defined in the {@link Ordered} interface. Lower values have higher priority. * The default value is {@code Ordered.LOWEST_PRECEDENCE}, indicating * lowest priority (losing to any other specified order value). */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})public @interface Order { /** * The order value. Default is {@link Ordered#LOWEST_PRECEDENCE}. * @see Ordered#getOrder() */ int value() default Ordered.LOWEST_PRECEDENCE;}<div class="se-preview-section-delimiter"></div>關于數據源的切換可以參加專門的博文:

http://www.cnblogs.com/digdeep/p/4512368.html

3) 切點表達式(pointcut)

上面我們看到,無論是 <aop:config> 風格的配置,還是 @Aspect 風格的配置,切點表達式都是重點。都是我們必須掌握的。
1>pointcut語法形式(execution):
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)帶有 ? 號的部分是可選的,所以可以簡化成: ret-type-pattern name-pattern(param_pattern) 返回類型,方法名稱,參數三部分來匹配 。配置起來其實也很簡單: * 表示任意返回類型,任意方法名,任意一個參數類型; .. 連續兩個點表示0個或多個包路徑,還有0個或多個參數 。就是這么簡單。看下例子:execution(* net.aazj.service..*.get*(..)) :表示net.aazj.service包或者子包下的以get開頭的方法,參數可以是0個或者多個(參數不限);execution(* net.aazj.service.AccountService.*(..)): 表示AccountService接口下的任何方法,參數不限;注意這里,將類名和包路徑是一起來處理的,并沒有進行區分,因為類名也是包路徑的一部分。參數param- pattern 部分比較復雜: () 表示沒有參數,(..)參數不限,(*,String) 第一個參數不限類型,第二參數為String .
2>within() 語法:
within()只能指定(限定)包路徑(類名也可以看做是包路徑),表示某個包下或者子報下的所有方法:within(net.aazj.service.*), within(net.aazj.service..*),within(net.aazj.service.UserServiceImpl.*)
3>this() 與 target():
this是指代理對象,target是指被代理對象(目標對象)。所以 this() 和 target() 分別限定 代理對象的類型和被代理對象的類型:this(net.aazj.service.UserService): 實現了UserService的代理對象(中的所有方法);target (net.aazj.service.UserService): 被代理對象 實現了UserService(中的所有方法);
4> args():
限定方法的參數的類型:args(net.aazj.pojo.User): 參數為User類型的方法。
5>@target(), @within(), @annotation(), @args():
這些語法形式都是針對注解的 ,比如 帶有某個注解的 類 , 帶有某個注解的 方法 , 參數的類型 帶有某個注解 :@within(org.springframework.transaction.annotation.Transactional)@target(org.springframework.transaction.annotation.Transactional)兩者都是指被代理對象 類 上有 @Transactional 注解的(類的所有方法),(兩者似乎沒有區別???)@annotation(org.springframework.transaction.annotation.Transactional): 方法 帶有 @Transactional 注解的所有方法 @args(org.springframework.transaction.annotation.Transactional): 參數的類型 帶有 @Transactional 注解 的所有方法
6>bean(): 指定某個bean的名稱
bean(userService): bean的id為 "userService" 的所有方法;bean(*Service): bean的id為 "Service"字符串結尾的所有方法;另外注意上面這些表達式是可以利用 ||, &&, ! 進行自由組合的。比如:execution(public * net.aazj.service..*.getUser(..)) && args(Integer,..)

4. 向注解處理方法傳遞參數

有時我們在寫注解處理方法時,需要訪問被攔截的方法的參數。此時我們可以使用 args() 來傳遞參數,下面看一個例子:@Aspect@Component // for auto scan//@Order(2)public class LogInterceptor { @Pointcut("execution(public * net.aazj.service..*.getUser(..))") public void myMethod(){}; @Before("myMethod()") public void before() { System.out.println("method start"); } @After("myMethod()") public void after() { System.out.println("method after"); } @AfterReturning("execution(public * net.aazj.mapper..*.*(..))") public void AfterReturning() { System.out.println("method AfterReturning"); } @AfterThrowing("execution(public * net.aazj.mapper..*.*(..))")// @Around("execution(public * net.aazj.mapper..*.*(..))") public void AfterThrowing() { System.out.println("method AfterThrowing"); } @Around("execution(public * net.aazj.mapper..*.*(..))") public Object Around(ProceedingJoinPoint jp) throws Throwable { System.out.println("method Around"); SourceLocation sl = jp.getSourceLocation(); Object ret = jp.proceed(); System.out.println(jp.getTarget()); return ret; } @Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)") public void before3(int userId) { System.out.println("userId-----" + userId); } @Before("myMethod()") public void before2(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println("userId11111: " + (Integer)args[0]); System.out.println(jp.getTarget()); System.out.println(jp.getThis()); System.out.println(jp.getSignature()); System.out.println("method start"); } }<div class="se-preview-section-delimiter"></div>

方法:

@Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)") public void before3(int userId) { System.out.println("userId-----" + userId); }它會攔截 net.aazj.service 包下或者子包下的getUser方法,并且該方法的第一個參數必須是int型的, 那么使用切點表達式args(userId,..) 就可以使我們在切面中的處理方法before3中可以訪問這個參數。before2方法也讓我們知道也可以通過 JoinPoint 參數來獲得被攔截方法的參數數組。 JoinPoint 是每一個切面處理方法都具有的參數, @Around 類型的具有的參數類型為ProceedingJoinPoint。通過 JoinPoint或者 ProceedingJoinPoint 參數可以訪問到被攔截對象的一些信息(參見上面的 before2 方法)。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 宁晋县| 宣恩县| 芦山县| 布拖县| 滕州市| 富平县| 安丘市| 南丰县| 城步| 盐亭县| 波密县| 红河县| 万州区| 文化| 和林格尔县| 扎赉特旗| 伊通| 鄢陵县| 广西| 浦城县| 东丽区| 徐州市| 宁南县| 福海县| 界首市| 义马市| 灵寿县| 天津市| 社旗县| 东海县| 沁水县| 娱乐| 靖边县| 浠水县| 扎赉特旗| 钦州市| 大姚县| 甘洛县| 汉川市| 即墨市| 桐城市|