创建AOP静态代理(上篇)
前言
?AOP的靜態(tài)代理主要是在虛擬機(jī)啟動(dòng)時(shí)通過(guò)改變目標(biāo)對(duì)象字節(jié)碼的方式來(lái)完成對(duì)目標(biāo)對(duì)象的增強(qiáng),它與動(dòng)態(tài)代理相比具有更高的效率,因?yàn)樵趧?dòng)態(tài)代理調(diào)用的過(guò)程中,還需要一個(gè)動(dòng)態(tài)創(chuàng)建代理類(lèi)并代理目標(biāo)對(duì)象的步驟,而靜態(tài)代理則是在啟動(dòng)時(shí)便完成了字節(jié)碼增強(qiáng),當(dāng)系統(tǒng)再次調(diào)用目標(biāo)類(lèi)時(shí)與調(diào)用正常的類(lèi)并無(wú)差別,所以在效率上會(huì)相對(duì)高些。
Instrumentation使用
Java在1.5引入java.lang.instrument,你可以由此實(shí)現(xiàn)一個(gè)Java?agent,通過(guò)此agent來(lái)修改類(lèi)的字節(jié)碼即改變一個(gè)類(lèi)。本節(jié)會(huì)通過(guò)Java?Instrument實(shí)現(xiàn)一個(gè)簡(jiǎn)單的profiler。當(dāng)然instrument并不限于profiler,instrument它可以做很多事情,它類(lèi)似一種更低級(jí),更松耦合的AOP,可以從底層來(lái)改變一個(gè)類(lèi)的行為。你可以由此產(chǎn)生無(wú)限的遐想。接下來(lái)要做的事情,就是計(jì)算一個(gè)方法所花的時(shí)間,通常我們會(huì)在代碼中按以下方式編寫(xiě)。
在方法開(kāi)頭加入?long?stime? = System.nanoTime();在方法結(jié)尾通過(guò)System.nanoTime()-stime得出所花的時(shí)間。你不得不在想監(jiān)控的每個(gè)方法中寫(xiě)入重復(fù)的代碼,好一點(diǎn)的情況,你可以用AOP來(lái)干這事,但總是感覺(jué)有點(diǎn)別扭,這種profiler的代碼還是要打包在你的項(xiàng)目中,Java?Instrument使得這一切更干凈。
(1)寫(xiě)ClassFileTransformer類(lèi);(PS:需要導(dǎo)入javassist-3.9.0.GA.jar)
public class PerfMonXformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){byte[] transformed = null;System.out.println("Transforming " + className);ClassPool pool = ClassPool.getDefault();CtClass cl = null;try{cl = pool.makeClass(new java.io.ByteArrayInputStream(classfileBuffer));if (cl.isInterface() == false){CtBehavior[] methods = cl.getDeclaredBehaviors();for (int i = 0;i < methods.length;i++){if (methods[i].isEmpty() == false){//修改method字節(jié)碼 doMethod(methods[i]);}}transformed = cl.toBytecode();}} catch (Exception e) {e.printStackTrace();}finally {if (cl != null){cl.detach();}}return transformed;}private void doMethod(CtBehavior method) throws CannotCompileException {method.insertBefore("long stime = System.nanoTime();");method.insertAfter("System.out.println(/leave " + method.getName() + " and time:/ + (System.nanoTime() - stime));");} }(2)編寫(xiě)agent類(lèi);
public class PerfMonAgent {static private Instrumentation ins = null;public static void premain(String agentArgs,Instrumentation inst ){System.out.println("PerfMonAgent.premain() was called.");inst = ins;ClassFileTransformer trans = new PerfMonXformer();System.out.println("Adding a PerMonXformer instance to the JVM.");inst.addTransformer(trans);} }上面兩個(gè)類(lèi)就是agent的核心了,JVM啟動(dòng)時(shí)在應(yīng)用加載前會(huì)調(diào)用PerfMonAgent.premain,然后PerfMonAgent.premain中實(shí)例化了一個(gè)定制的ClassFileTransforme,即PerfMonXformer通過(guò)inst.addTransformer(trans)把PerfMonXformer的實(shí)例加入Instrumentation實(shí)例(由JVM傳入),這就使得應(yīng)用中的類(lèi)加載時(shí),PerfMonXformer.transform都會(huì)被調(diào)用,你在此方法中可以改變加載的類(lèi)。為了改變類(lèi)的字節(jié)碼,我們使用了Jboss的Javassist,雖然不一定要這么用,但Jboss的Javassist真的很強(qiáng)大,能讓你很容易的改變類(lèi)的字節(jié)碼。在上面的方法中我通過(guò)改變類(lèi)的字節(jié)碼,在每個(gè)類(lèi)的方法入口中加入了?long?stime =?System.nanoTime(),在方法的出口加入了:
System.out.println("methodClassName.methodName:" + (System.nanoTime() - stime));(3)打包agent;
對(duì)于agent的打包,有點(diǎn)講究。
??JAR的META-INF/MANIFEST.MF加入Premain-Class:xx,xx在此語(yǔ)境中就是我們的agent類(lèi),即org.toy.PerfMonAgent。
??如果你的agent類(lèi)引入別的包,需要使用Boot-Class-Path:xx,xx在此語(yǔ)境中就是上面提到的JBoss?javassist,即/home/pwlazy/.m2/repository/javassist/javassist/3.8.0 .GA/javassist-3.8.0.GA.jar。
(4)打包應(yīng)用。
Java選項(xiàng)中有-javaagent:xx,xx就是你的agent.jar,Java通過(guò)此選項(xiàng)加載agent,由agent來(lái)監(jiān)控classpath下的應(yīng)用。
總結(jié):Spring中的靜態(tài)AOP直接使用了AspectJ提供的方法,而AspectJ又是在Instrument基礎(chǔ)上進(jìn)行的封裝。在AspectJ中會(huì)有如下的功能:
1.讀取META-INF/aop.xml。
2.將aop.xml中定義的增強(qiáng)器通過(guò)自定義的ClassFileTransformer織入對(duì)應(yīng)的類(lèi)中。
當(dāng)然上述是AspectJ所做的事情,并不在我們討論的范疇,Spring是直接使用AspectJ,也就是將動(dòng)態(tài)代理直接委托給了AspectJ,那么,Spring怎么嵌入AspectJ的呢?同樣我們還是從配置文件入手。
自定義標(biāo)簽
在Spring中如果需要使用AspectJ的功能,首先要做的第一步就是在配置文件中加入配置:<context:load-time-weaver/>。我們根據(jù)之前的介紹的自定義命名空間的知識(shí)便可以推斷,引用AspectJ的入口便是這里,可以通過(guò)查找load-time-weaver來(lái)找到對(duì)應(yīng)的自定義命名處理類(lèi)。
在ContextNamespaceHandler類(lèi)中:
public class ContextNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());} }繼續(xù)跟進(jìn)LoadTimeWeaverBeanDefinitionParser,作為BeanDefinitionParser接口的實(shí)現(xiàn)類(lèi),他的核心邏輯是從parse函數(shù)開(kāi)始的,而經(jīng)過(guò)父類(lèi)的封裝,LoadTimeWeaverBeanDefinitionParser類(lèi)的核心實(shí)現(xiàn)被轉(zhuǎn)移到了doParse函數(shù)中,如下:
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);if (isAspectJWeavingEnabled(element.getAttribute(ASPECTJ_WEAVING_ATTRIBUTE), parserContext)) {if (!parserContext.getRegistry().containsBeanDefinition(ASPECTJ_WEAVING_ENABLER_BEAN_NAME)) {RootBeanDefinition def = new RootBeanDefinition(ASPECTJ_WEAVING_ENABLER_CLASS_NAME);parserContext.registerBeanComponent(new BeanComponentDefinition(def, ASPECTJ_WEAVING_ENABLER_BEAN_NAME));}if (isBeanConfigurerAspectEnabled(parserContext.getReaderContext().getBeanClassLoader())) {new SpringConfiguredBeanDefinitionParser().parse(element, parserContext);}}}其實(shí)之前在分析動(dòng)態(tài)AOP也就是在分析配置<aop:aspectj-autoproxy/>中已經(jīng)提到了自定義配置的解析流程,對(duì)于<aop:aspectj-autoproxy/>的解析無(wú)非是以標(biāo)簽作為標(biāo)志,進(jìn)而使得相關(guān)處理類(lèi)的注冊(cè),那么對(duì)于自定義標(biāo)簽<context:load-time-weaver/>其實(shí)是起到了同樣的作用。
上述函數(shù)的核心其實(shí)就是注冊(cè)一個(gè)對(duì)于AspectJ處理的類(lèi) org.Springframework.context.weaving.AspectJWeavingEnable,它的注冊(cè)流程總結(jié)如下:
(1)是否開(kāi)啟AspectJ。
之前雖然反復(fù)的提到了在配置文件中加入<context:load-time-weaver/>便相當(dāng)于加入了AspectJ開(kāi)關(guān)。但是,并不是配置了這個(gè)標(biāo)簽就意味著開(kāi)啟了AspectJ功能,這個(gè)標(biāo)簽中還有一個(gè)屬性aspectj-weaving,這個(gè)屬性有3個(gè)備選值,on、off和autodetect,默認(rèn)為autodetect,也就是說(shuō),如果我們只是用了<context:load-time-weaver/>,那么Spring會(huì)幫助我們檢測(cè)是否可以使用AspectJ功能,而檢測(cè)的依據(jù)便是文件 META-INF/aop.xml是否存在,看看在Spring中的實(shí)現(xiàn)方式:
protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {if ("on".equals(value)) {return true;}else if ("off".equals(value)) {return false;}else {// 自動(dòng)檢測(cè)ClassLoader cl = parserContext.getReaderContext().getBeanClassLoader();return (cl != null && cl.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) != null);}}(2)將org.Springframework.context.weaving.AspectJWeavingEnable封裝在BeanDefinition中注冊(cè);
當(dāng)通過(guò)AspectJ功能驗(yàn)證后便可以進(jìn)行AspectJWeavingEnable的注冊(cè)了,注冊(cè)的方式很簡(jiǎn)單,無(wú)非是將類(lèi)路徑注冊(cè)在新初始化的RootBeanDefinition中,在RootBeanDefinition的獲取時(shí)會(huì)轉(zhuǎn)換成對(duì)應(yīng)的class。
盡管在init方法中注冊(cè)了AspectJWeavingEnable,但是對(duì)于標(biāo)簽本身Spring也會(huì)以bean的形式保存,也就是當(dāng)Spring解析到<context:load-time-weaver/>標(biāo)簽的時(shí)候也會(huì)產(chǎn)生一個(gè)bean,而這個(gè)bean的信息是什么呢?
在LoadTimeWeaverBeanDefinitionParser類(lèi)中有這樣的方法:
protected String getBeanClassName(Element element) {if (element.hasAttribute(WEAVER_CLASS_ATTRIBUTE)) {return element.getAttribute(WEAVER_CLASS_ATTRIBUTE);}return DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME;} protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {return ConfigurableApplicationContext.LOAD_TIME_WEAVER_BEAN_NAME;}其中的常量:
private static final String DEFAULT_LOAD_TIME_WEAVER_CLASS_NAME ="org.springframework.context.weaving.DefaultContextLoadTimeWeaver";private static final String WEAVER_CLASS_ATTRIBUTE = "weaver-class"; String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";單憑以上的信息我們可以推斷出,當(dāng)Spring在讀取到自定義標(biāo)簽<context:load-time-weaver/>后胡產(chǎn)生一個(gè)bean,而這個(gè)bean的id為loadTimeWeaver,class為org.springframework.context.weaving.DefaultContextLoadTimeWeaver,也就是完成了DefaultContextLoadTimeWeaver類(lèi)的注冊(cè)。
完成了以上的注冊(cè)功能后,并不意味著這在Spring中就可以使用AspectJ了,因?yàn)槲覀冞€有一個(gè)很重要的步驟忽略了,就是LoadTimeAwareProcessor的注冊(cè)。在AbstractApplicationContext中的prepareBeanFactory函數(shù)中有這樣一段代碼:
if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));// Set a temporary ClassLoader for type matching.beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));}在AbstractApplicationContext中的prepareBeanFactory函數(shù)是在容器初始化時(shí)候調(diào)用的,也就是說(shuō)只有注冊(cè)了LoadTimeAwareProcessor才會(huì)激活整個(gè)AspectJ的功能。
至此,所有的AspectJ的準(zhǔn)備工作已經(jīng)全部完成了,下篇文章將會(huì)繼續(xù)講述AOP的靜態(tài)代理的步驟------織入。
參考:《Spring源碼深度解析》 郝佳 編著:
轉(zhuǎn)載于:https://www.cnblogs.com/Joe-Go/p/10241624.html
與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的创建AOP静态代理(上篇)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Jan.09
- 下一篇: react实现svg实线、虚线、方形进度