動態AOP使用示例
面向對象編程有一些弊端,當需要爲多個不具有繼承關係的對象引入同一個公共行爲時,例如日誌,安全檢測等,我們只有在每個對象裏引入公共行爲,這樣程序中就產生了大量的重複代碼,所以有了面向對象編程的補充,面向切面編程(AOP'),AOP所關注的方向是橫向的,不同於OOP的縱向。下面是一個AOP的示例
(1)創建用於攔截的bean。
在實際工作中,此bean可能是滿足業務需要的核心邏輯。例如test方法中可能會封裝着某個核心業務,但是,如果我們想在test前後加入日誌來跟蹤測試,如果直接修改源碼並不符合面向對象的設計方法,而且隨意改動原有代碼也會造成一定的風險,還好接下來的Spring幫我們做到了這一點。
public class TestBean {
private String testStr= "testStr";
public String getTestStr(){
return testStr;
}
public void setTestStr(String testStr){
this.testStr = testStr;
}
public void test(){
System.out.println("test");
}
}
(2)創建Advisor
Spring中摒棄了最原始的繁雜配置方式而採用@AspectJ註解對POJO進行標註,使AOP的工作大大簡化,例如,在AspectJTest類中,我們要做的就是在所有類的test方法執行前在控制檯beforeTest。而在所有類的test方法執行後打印afterTest,同時又使用環繞的方式在所有類的方法執行前後在此分別打印before1和after1.
@Aspect
public class AspectJTest {
@Pointcut("execution(* *.test(..))")
public void test(){
}
@Before("test()")
public void beforeTest(){
System.out.println("beforeTest");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p){
System.out.println("before1");
Object o = null;
try{
o = p.proceed();
}catch(Throwable e){
e.printStackTrace();
}
System.out.println("after1");
return o;
}
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
TestBean bean = (TestBean)bf.getBean("test");
bean.test();
}
}
(3)創建配置文件。
XML是Spring的基礎。儘管Spring一再簡化配置,並且大有使用註解取代XML配置之勢,但是無論如何,至少現在XML還是Spring的基礎。要在Spring中開啓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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<aop:aspectj-autoproxy/>
<bean id="test" class="org.tarena.note.test.TestBean"/>
<bean class="org.tarena.note.test.AspectJTest"/>
</beans>
(4)測試。
經過以上步驟後,便可以驗證Spring的AOP爲我們提供的神奇效果了
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
TestBean bean = (TestBean)bf.getBean("test");
bean.test();
}
Spring實現了對所有類的test方法進行增強,使輔助功能可以獨立於核心業務之外,方便與程序的擴展和解耦。
那麼,Spring是如何實現AOP的呢?首先我們知道,SPring是否支持註解的AOP是由一個配置文件控制的,也就是
<aop:aspectj-autoproxy/>
,當在配置文件中聲明瞭這句配置的時候,Spring就會支持註解的AOP,那麼我們的分析就從這句註解開始。
動態AOP自定義標籤
之前講過Spring中的自定義註解,如果聲明瞭自定義的註解,那麼就一定會在程序中的某個地方註冊了對應的解析器。我們搜索這個代碼,嘗試找到註冊的地方,全局搜索後我們發現了在AopNamespaceHandler中對應着這樣一段函數:
public void init()
{
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
解析是使用的spirng解析自定義註解的方式,由配置文件得知,在遇到aspectj-autoproxy註解時就會使用解析器AspectJAutoProxyBeanDefinitionParser進行解析,那麼我麼看看AspectJAutoProxyBeanDefinitionParse的內部實現。
2.1註冊AnnotationAwareAspectJAutoProxyCreator
所有解析器,因爲是對BeanDefinitionParser接口的統一實現,入口都是從parse函數開始的,AspectJAutoProxyBeanDefinitionParser的parse函數如下:
public BeanDefinition parse(Element element, ParserContext parserContext)
{
//註冊AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
//對註解中子類的處理
extendBeanDefinition(element, parserContext);
return null;
}
其中registerAspectJAnnotationAutoProxyCreatorIfNecessary函數是我們比較關心的,也是關鍵邏輯的實現 public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement)
{
//註冊或升級AutoProxyCreator定義beanName爲org.Springframework.aop.config.internalAutoProxyCreator的BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
//對於proxy-target-class以及expose-proxy屬性的處理
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
//註冊組件並通知,便於監聽器進一步處理
//其中beanDefinition的className爲AnnotationAwareAspectJAutoProxyCreator
registerComponentIfNecessary(beanDefinition, parserContext);
}
在registerAspectJAnnotationAutoProxyCreatorIfNeccessary方法中主要完成了3件事情,基本上每行代碼都是一個完整的邏輯。
1.註冊或升級AnnotationAwareAspectJAutoProxyCreator
對於AOP的實現,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根據@Point註解定義的切點來自動代理相匹配的bean。但是爲了配置簡便,Spring使用了自定義配置來幫助我們自動註冊AnnotationAwareAspectJAutoProxyCreator,其註冊過程就是在這裏實現的。
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source)
{
return registerOrEscalateApcAsRequired(org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source)
{
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//如果已經存在了自動代理創建器且存在的自動代理創建器與現在的不一致那麼需要根據優先級來判斷到底需要使用哪個
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
if(!cls.getName().equals(apcDefinition.getBeanClassName()))
{
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if(currentPriority < requiredPriority)
//改變bean最重要的就是改變bean所對應的className屬性
apcDefinition.setBeanClassName(cls.getName());
}
return null;
} else
{
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Integer.valueOf(-2147483648));
beanDefinition.setRole(2);
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
return beanDefinition;
}
}
以上代碼實現了自動註冊AnnotationAwareAspectJAutoProxyCreator類的功能,同時這裏還涉及了一個優先級的問題,如果已經存在了自動代理創建器,而且存在的自動代理創建器與現在的不一致,那麼需要根據優先級來判斷到底需要使用哪個。
2.處理proxy-target-class以及expose-proxy屬性
useClassProxyingIfNecessary實現了proxy-target-class屬性以及expose-proxy屬性的處理
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement)
{
if(sourceElement != null)
{
//對於proxy-target-class屬性的處理
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute("proxy-target-class")).booleanValue();
if(proxyTargetClass)
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
//對於expose-proxy屬性的處理
boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute("expose-proxy")).booleanValue();
if(exposeProxy)
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
//強制使用的過程其實也是一個屬性設置的過程 public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry)
{
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry)
{
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
- proxy-target-class:SpringAOP部分使用JDK動態代理或者CGLIB來爲目標對象創建代理(建議儘量使用JDK的動態代理),如果被代理的目標對象實現了至少一個接口,則會使用JDK動態代理,所有該目標類型實現的接口都將代理,若該目標對象沒有實現任何接口,則創建一個CGLIB代理,如果你希望強制使用CGLIB代理,(例如看望代理目標對象的所有方法,而不只是實現自接口的方法)那也可以。但是需要考慮以下兩個問題。
無法通知(advise)Final方法,因爲它們不能被重寫。
你需要將CGLIB二進制發行包放在classpath下面。
- JDK動態代理:其代理對象必須是某個接口的實現,它是通過在運行期間創建一個接口的實現類來完成對目標對象的代理。
- CGLIB:實現原理類似於JDK動態代理,只是它在運行期間生成的代理對象時針對目標擴展的子類。CGLIB是高效的代碼生成包,底層是依靠ASM(開通的Java字節碼編輯類庫)操作字節碼實現的,性能比JDK強。