AOP是Spring提供的關(guān)鍵特性之一。AOP即面向切面編程,是OOP編程的有效補(bǔ)充。使用AOP技術(shù),可以將一些系統(tǒng)性相關(guān)的編程工作,獨(dú)立提取出來,獨(dú)立實(shí)現(xiàn),然后通過切面切入進(jìn)系統(tǒng)。從而避免了在業(yè)務(wù)邏輯的代碼中混入很多的系統(tǒng)相關(guān)的邏輯——比如權(quán)限管理,事物管理,日志記錄等等。這些系統(tǒng)性的編程工作都可以獨(dú)立編碼實(shí)現(xiàn),然后通過AOP技術(shù)切入進(jìn)系統(tǒng)即可。從而達(dá)到了將不同的關(guān)注點(diǎn)分離出來的效果。本文深入剖析Spring的AOP的原理。
1. AOP相關(guān)的概念
1)aspect:切面,切入系統(tǒng)的一個(gè)切面。比如事務(wù)管理是一個(gè)切面,權(quán)限管理也是一個(gè)切面;
2)Join point:連接點(diǎn),也就是可以進(jìn)行橫向切入的位置;
3)Advice:通知,切面在某個(gè)連接點(diǎn)執(zhí)行的操作(分為:Before advice,After returning advice,After throwing advice,After (finally) advice,Around advice);
4)Pointcut:切點(diǎn),符合切點(diǎn)表達(dá)式的連接點(diǎn),也就是真正被切入的地方;
2. AOP 的實(shí)現(xiàn)原理
AOP分為靜態(tài)AOP和動(dòng)態(tài)AOP。靜態(tài)AOP是指AspectJ實(shí)現(xiàn)的AOP,他是將切面代碼直接編譯到java類文件中。動(dòng)態(tài)AOP是指將切面代碼進(jìn)行動(dòng)態(tài)織入實(shí)現(xiàn)的AOP。Spring的AOP為動(dòng)態(tài)AOP,實(shí)現(xiàn)的技術(shù)為:JDK提供的動(dòng)態(tài)代理技術(shù) 和 CGLIB(動(dòng)態(tài)字節(jié)碼增強(qiáng)技術(shù))。盡管實(shí)現(xiàn)技術(shù)不一樣,但都是基于代理模式,都是生成一個(gè)代理對象。
1) JDK動(dòng)態(tài)代理
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。JDK動(dòng)態(tài)代理要求被代理實(shí)現(xiàn)一個(gè)接口,只有接口中的方法才能夠被代理。其方法是將被代理對象注入到一個(gè)中間對象,而中間對象實(shí)現(xiàn)InvocationHandler接口,在實(shí)現(xiàn)該接口時(shí),可以在 被代理對象調(diào)用它的方法時(shí),在調(diào)用的前后插入一些代碼。而 Proxy.newProxyInstance() 能夠利用中間對象來生產(chǎn)代理對象。插入的代碼就是切面代碼。所以使用JDK動(dòng)態(tài)代理可以實(shí)現(xiàn)AOP。我們看個(gè)例子:
被代理對象實(shí)現(xiàn)的接口,只有接口中的方法才能夠被代理:
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); // 生成代理對象,對被代理對象的這些接口進(jìn)行代理:UserServiceImpl.class.getInterfaces() UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.getUser(1); proxyObject.addUser(new User()); }}執(zhí)行結(jié)果:
do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....
我們看到在 UserService接口中的方法addUser 和 getUser方法的前面插入了我們自己的代碼。這就是JDK動(dòng)態(tài)代理實(shí)現(xiàn)AOP的原理。
我們看到該方式有一個(gè)要求,被代理的對象必須實(shí)現(xiàn)接口,而且只有接口中的方法才能被代理。
2)CGLIB(code generate libary)
字節(jié)碼生成技術(shù)實(shí)現(xiàn)AOP,其實(shí)就是繼承被代理對象,然后Override需要被代理的方法,在覆蓋該方法時(shí),自然是可以插入我們自己的代碼的。因?yàn)樾枰狾verride被代理對象的方法,所以自然CGLIB技術(shù)實(shí)現(xiàn)AOP時(shí),就必須要求需要被代理的方法不能是final方法,因?yàn)閒inal方法不能被子類覆蓋。我們使用CGLIB實(shí)現(xiàn)上面的例子:
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()); // 設(shè)置父類 // 設(shè)置回調(diào) enhancer.setCallback(this); // 在調(diào)用父類方法時(shí),回調(diào) this.intercept() // 創(chuàng)建代理對象 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()); }}輸出結(jié)果:
do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....
我們看到達(dá)到了同樣的效果。它的原理是生成一個(gè)父類enhancer.setSuperclass(this.target.getClass())的子類enhancer.create(),然后對父類的方法進(jìn)行攔截enhancer.setCallback(this). 對父類的方法進(jìn)行覆蓋,所以父類方法不能是final的。
3)接下來我們看下spring實(shí)現(xiàn)AOP的相關(guān) 從上面的源碼我們可以看到: 如果被代理對象實(shí)現(xiàn)了接口,那么就使用JDK的動(dòng)態(tài)代理技術(shù),反之則使用CGLIB來實(shí)現(xiàn)AOP,所以Spring默認(rèn)是使用JDK的動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)AOP的。 JdkDynamicAopProxy的實(shí)現(xiàn)其實(shí)很簡單: 3. Spring AOP的配置 Spring中AOP的配置一般有兩種方法,一種是使用 <aop:config> 標(biāo)簽在xml中進(jìn)行配置,一種是使用注解以及@Aspect風(fēng)格的配置。 1)基于<aop:config>的AOP配置 下面是一個(gè)典型的事務(wù)AOP的配置: 再看一個(gè)例子: <aop:aspect> 配置一個(gè)切面;<aop:pointcut>配置一個(gè)切點(diǎn),基于切點(diǎn)表達(dá)式;<aop:before>,<aop:after>,<aop:around>是定義不同類型的advise. aspectBean 是切面的處理bean: 2) 基于注解和@Aspect風(fēng)格的AOP配置 我們以事務(wù)配置為例:首先我們啟用基于注解的事務(wù)配置 然后掃描Service包: 最后在service上進(jìn)行注解: 搞定。這種事務(wù)配置方式,不需要我們書寫pointcut表達(dá)式,而是我們在需要事務(wù)的類上進(jìn)行注解。但是如果我們自己來寫切面的代碼時(shí),還是要寫pointcut表達(dá)式。下面看一個(gè)例子(自己寫切面邏輯): 首先去掃描 @Aspect 注解定義的 切面: 啟用@AspectJ風(fēng)格的注解: 這里有兩個(gè)屬性,<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>, proxy-target-class="true" 這個(gè)最好不要隨便使用,它是指定只能使用CGLIB代理,那么對于final方法時(shí)會(huì)拋出錯(cuò)誤,所以還是讓spring自己選擇是使用JDK動(dòng)態(tài)代理,還是CGLIB. expose-proxy="true"的作用后面會(huì)講到。 切面代碼: 我們使用到了 @Aspect 來定義一個(gè)切面;@Component是配合<context:component-scan/>,不然掃描不到;@Order定義了該切面切入的順序,因?yàn)樵谕粋€(gè)切點(diǎn),可能同時(shí)存在多個(gè)切面,那么在這多個(gè)切面之間就存在一個(gè)執(zhí)行順序的問題。該例子是一個(gè)切換數(shù)據(jù)源的切面,那么他應(yīng)該在 事務(wù)處理 切面之前執(zhí)行,所以我們使用 @Order(0) 來確保先切換數(shù)據(jù)源,然后加入事務(wù)處理。@Order的參數(shù)越小,優(yōu)先級越高,默認(rèn)的優(yōu)先級最低: 關(guān)于數(shù)據(jù)源的切換可以參加專門的博文:http://m.survivalescaperooms.com/digdeep/p/4512368.html 3)切點(diǎn)表達(dá)式(pointcut) 上面我們看到,無論是 <aop:config> 風(fēng)格的配置,還是 @Aspect 風(fēng)格的配置,切點(diǎn)表達(dá)式都是重點(diǎn)。都是我們必須掌握的。 1> pointcut語法形式(execution): 帶有 ? 號的部分是可選的,所以可以簡化成:ret-type-pattern name-pattern(param_pattern) 返回類型,方法名稱,參數(shù)三部分來匹配。 配置起來其實(shí)也很簡單: * 表示任意返回類型,任意方法名,任意一個(gè)參數(shù)類型; .. 連續(xù)兩個(gè)點(diǎn)表示0個(gè)或多個(gè)包路徑,還有0個(gè)或多個(gè)參數(shù)。就是這么簡單??聪吕樱?/p> execution(* net.aazj.service..*.get*(..)) :表示net.aazj.service包或者子包下的以get開頭的方法,參數(shù)可以是0個(gè)或者多個(gè)(參數(shù)不限); execution(* net.aazj.service.AccountService.*(..)): 表示AccountService接口下的任何方法,參數(shù)不限; 注意這里,將類名和包路徑是一起來處理的,并沒有進(jìn)行區(qū)分,因?yàn)轭惷彩前窂降囊徊糠帧?/p> 參數(shù)param-pattern部分比較復(fù)雜: () 表示沒有參數(shù),(..)參數(shù)不限,(*,String) 第一個(gè)參數(shù)不限類型,第二參數(shù)為String. 2> within() 語法: within()只能指定(限定)包路徑(類名也可以看做是包路徑),表示某個(gè)包下或者子報(bào)下的所有方法: within(net.aazj.service.*), within(net.aazj.service..*),within(net.aazj.service.UserServiceImpl.*) 3> this() 與 target(): this是指代理對象,target是指被代理對象(目標(biāo)對象)。所以 this() 和 target() 分別限定 代理對象的類型和被代理對象的類型: this(net.aazj.service.UserService): 實(shí)現(xiàn)了UserService的代理對象(中的所有方法); target(net.aazj.service.UserService): 被代理對象實(shí)現(xiàn)了UserService(中的所有方法); 4> args(): 限定方法的參數(shù)的類型: args(net.aazj.pojo.User): 參數(shù)為User類型的方法。 5> @target(), @within(), @annotation(), @args(): 這些語法形式都是針對注解的,比如帶有某個(gè)注解的類,帶有某個(gè)注解的方法,參數(shù)的類型帶有某個(gè)注解: @within(org.springframework.transaction.annotation.Transactional)@target(org.springframework.transaction.annotation.Transactional) 兩者都是指被代理對象類上有 @Transactional 注解的(類的所有方法),(兩者似乎沒有區(qū)別???) @annotation(org.springframework.transaction.annotation.Transactional):方法 帶有@Transactional 注解的所有方法 @args(org.springframework.transaction.annotation.Transactional):參數(shù)的類型 帶有@Transactional 注解的所有方法 6> bean(): 指定某個(gè)bean的名稱 bean(userService): bean的id為 "userService" 的所有方法; bean(*Service): bean的id為 "Service"字符串結(jié)尾的所有方法; 另外注意上面這些表達(dá)式是可以利用 ||, &&, ! 進(jìn)行自由組合的。比如:execution(public * net.aazj.service..*.getUser(..)) && args(Integer,..) 4. 向注解處理方法傳遞參數(shù) 有時(shí)我們在寫注解處理方法時(shí),需要訪問被攔截的方法的參數(shù)。此時(shí)我們可以使用 args() 來傳遞參數(shù),下面看一個(gè)例子: 方法: 它會(huì)攔截 net.aazj.service 包下或者子包下的getUser方法,并且該方法的第一個(gè)參數(shù)必須是int型的,那么使用切點(diǎn)表達(dá)式args(userId,..)就可以使我們在切面中的處理方法before3中可以訪問這個(gè)參數(shù)。 before2方法也讓我們知道也可以通過 JoinPoint 參數(shù)來獲得被攔截方法的參數(shù)數(shù)組。JoinPoint 是每一個(gè)切面處理方法都具有的參數(shù),@Around類型的具有的參數(shù)類型為ProceedingJoinPoint。通過JoinPoint或者ProceedingJoinPoint參數(shù)可以訪問到被攔截對象的一些信息(參見上面的before2方法)。 5. Spring AOP的缺陷 因?yàn)镾pring AOP是基于動(dòng)態(tài)代理對象的,那么如果target中的方法不是被代理對象調(diào)用的,那么就不會(huì)織入切面代碼,看個(gè)例子: 看到上面的 addUser() 方法中,我們調(diào)用了 getUser() 方法,而getUser() 方法是誰調(diào)用的呢?是UserServiceImpl的實(shí)例,不是代理對象,那么getUser()方法就不會(huì)被織入切面代碼。 切面代碼如下: 執(zhí)行如下代碼: 輸出結(jié)果如下: 雖然 getUser()方法 被調(diào)用了,但是因?yàn)椴皇谴韺ο笳{(diào)用的,所以 AOPTest.m1() 方法并沒有執(zhí)行。這就是Spring aop的缺陷。解決方法如下: 首先: 將 <aop:aspectj-autoproxy /> 改為: 然后,修改UserServiceImpl中的 addUser() 方法: if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config);final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @Override public 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); } <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>
public class DataSourceInterceptor { public void before(JoinPoint jp) { DataSourceTypeManager.set(DataSources.SLAVE); }} <!-- 使用annotation定義事務(wù) --> <tx:annotation-driven transaction-manager="transactionManager" />
<context:component-scan base-package="net.aazj.service,net.aazj.aop" />
@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); }}<context:component-scan base-package="net.aazj.aop" />
<aop:aspectj-autoproxy />
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); }}/** * 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;}execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
@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"); } } @Before("execution(public * net.aazj.service..*.getUser(..)) && args(userId,..)") public void before3(int userId) { System.out.println("userId-----" + userId); }@Service("userService")@Transactionalpublic class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); } public void addUser(String username){ getUser(2); userMapper.addUser(username); }@Aspect@Componentpublic class AOPTest { @Before("execution(public * net.aazj.service..*.getUser(..))") public void m1(){ System.out.println("in m1..."); } @Before("execution(public * net.aazj.service..*.addUser(..))") public void m2(){ System.out.println("in m2..."); }}public class Test { public static void main(String[] args){ applicationContext context = new ClassPathXmlApplicationContext( new String[]{"config/spring-mvc.xml","config/applicationContext2.xml"}); UserService us = context.getBean("userService", UserService.class); if(us != null){ us.addUser("aaa");in m2...
<aop:aspectj-autoproxy expose-proxy="true"/>
@Service("userService")@Transactionalpublic class UserServiceImpl implements UserService{ @Autowired private UserMapper userMapper; @Transactional (readOnly=true) public User getUser(int userId) { return userMapper.getUser(userId); } public void addUser(String username){ ((UserService)AopContext.currentProxy()).getUser(2); userMapper.addUser(username); }((UserService)AopContext.currentProxy()).getUser(2); 先獲得當(dāng)前的代理對象,然后在調(diào)用 getUser() 方法,就行了。expose-proxy="true" 表示將當(dāng)前代理對象暴露出去,不然 AopContext.currentProxy() 或得的是 null .修改之后的運(yùn)行結(jié)果:
in m2...in m1...
新聞熱點(diǎn)
疑難解答
圖片精選