创建AOP静态代理(上篇)
前言
?AOP的靜態代理主要是在虛擬機啟動時通過改變目標對象字節碼的方式來完成對目標對象的增強,它與動態代理相比具有更高的效率,因為在動態代理調用的過程中,還需要一個動態創建代理類并代理目標對象的步驟,而靜態代理則是在啟動時便完成了字節碼增強,當系統再次調用目標類時與調用正常的類并無差別,所以在效率上會相對高些。
Instrumentation使用
Java在1.5引入java.lang.instrument,你可以由此實現一個Java?agent,通過此agent來修改類的字節碼即改變一個類。本節會通過Java?Instrument實現一個簡單的profiler。當然instrument并不限于profiler,instrument它可以做很多事情,它類似一種更低級,更松耦合的AOP,可以從底層來改變一個類的行為。你可以由此產生無限的遐想。接下來要做的事情,就是計算一個方法所花的時間,通常我們會在代碼中按以下方式編寫。
在方法開頭加入?long?stime? = System.nanoTime();在方法結尾通過System.nanoTime()-stime得出所花的時間。你不得不在想監控的每個方法中寫入重復的代碼,好一點的情況,你可以用AOP來干這事,但總是感覺有點別扭,這種profiler的代碼還是要打包在你的項目中,Java?Instrument使得這一切更干凈。
(1)寫ClassFileTransformer類;(PS:需要導入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字節碼 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)編寫agent類;
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);} }上面兩個類就是agent的核心了,JVM啟動時在應用加載前會調用PerfMonAgent.premain,然后PerfMonAgent.premain中實例化了一個定制的ClassFileTransforme,即PerfMonXformer通過inst.addTransformer(trans)把PerfMonXformer的實例加入Instrumentation實例(由JVM傳入),這就使得應用中的類加載時,PerfMonXformer.transform都會被調用,你在此方法中可以改變加載的類。為了改變類的字節碼,我們使用了Jboss的Javassist,雖然不一定要這么用,但Jboss的Javassist真的很強大,能讓你很容易的改變類的字節碼。在上面的方法中我通過改變類的字節碼,在每個類的方法入口中加入了?long?stime =?System.nanoTime(),在方法的出口加入了:
System.out.println("methodClassName.methodName:" + (System.nanoTime() - stime));(3)打包agent;
對于agent的打包,有點講究。
??JAR的META-INF/MANIFEST.MF加入Premain-Class:xx,xx在此語境中就是我們的agent類,即org.toy.PerfMonAgent。
??如果你的agent類引入別的包,需要使用Boot-Class-Path:xx,xx在此語境中就是上面提到的JBoss?javassist,即/home/pwlazy/.m2/repository/javassist/javassist/3.8.0 .GA/javassist-3.8.0.GA.jar。
(4)打包應用。
Java選項中有-javaagent:xx,xx就是你的agent.jar,Java通過此選項加載agent,由agent來監控classpath下的應用。
總結:Spring中的靜態AOP直接使用了AspectJ提供的方法,而AspectJ又是在Instrument基礎上進行的封裝。在AspectJ中會有如下的功能:
1.讀取META-INF/aop.xml。
2.將aop.xml中定義的增強器通過自定義的ClassFileTransformer織入對應的類中。
當然上述是AspectJ所做的事情,并不在我們討論的范疇,Spring是直接使用AspectJ,也就是將動態代理直接委托給了AspectJ,那么,Spring怎么嵌入AspectJ的呢?同樣我們還是從配置文件入手。
自定義標簽
在Spring中如果需要使用AspectJ的功能,首先要做的第一步就是在配置文件中加入配置:<context:load-time-weaver/>。我們根據之前的介紹的自定義命名空間的知識便可以推斷,引用AspectJ的入口便是這里,可以通過查找load-time-weaver來找到對應的自定義命名處理類。
在ContextNamespaceHandler類中:
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());} }繼續跟進LoadTimeWeaverBeanDefinitionParser,作為BeanDefinitionParser接口的實現類,他的核心邏輯是從parse函數開始的,而經過父類的封裝,LoadTimeWeaverBeanDefinitionParser類的核心實現被轉移到了doParse函數中,如下:
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);}}}其實之前在分析動態AOP也就是在分析配置<aop:aspectj-autoproxy/>中已經提到了自定義配置的解析流程,對于<aop:aspectj-autoproxy/>的解析無非是以標簽作為標志,進而使得相關處理類的注冊,那么對于自定義標簽<context:load-time-weaver/>其實是起到了同樣的作用。
上述函數的核心其實就是注冊一個對于AspectJ處理的類 org.Springframework.context.weaving.AspectJWeavingEnable,它的注冊流程總結如下:
(1)是否開啟AspectJ。
之前雖然反復的提到了在配置文件中加入<context:load-time-weaver/>便相當于加入了AspectJ開關。但是,并不是配置了這個標簽就意味著開啟了AspectJ功能,這個標簽中還有一個屬性aspectj-weaving,這個屬性有3個備選值,on、off和autodetect,默認為autodetect,也就是說,如果我們只是用了<context:load-time-weaver/>,那么Spring會幫助我們檢測是否可以使用AspectJ功能,而檢測的依據便是文件 META-INF/aop.xml是否存在,看看在Spring中的實現方式:
protected boolean isAspectJWeavingEnabled(String value, ParserContext parserContext) {if ("on".equals(value)) {return true;}else if ("off".equals(value)) {return false;}else {// 自動檢測ClassLoader cl = parserContext.getReaderContext().getBeanClassLoader();return (cl != null && cl.getResource(AspectJWeavingEnabler.ASPECTJ_AOP_XML_RESOURCE) != null);}}(2)將org.Springframework.context.weaving.AspectJWeavingEnable封裝在BeanDefinition中注冊;
當通過AspectJ功能驗證后便可以進行AspectJWeavingEnable的注冊了,注冊的方式很簡單,無非是將類路徑注冊在新初始化的RootBeanDefinition中,在RootBeanDefinition的獲取時會轉換成對應的class。
盡管在init方法中注冊了AspectJWeavingEnable,但是對于標簽本身Spring也會以bean的形式保存,也就是當Spring解析到<context:load-time-weaver/>標簽的時候也會產生一個bean,而這個bean的信息是什么呢?
在LoadTimeWeaverBeanDefinitionParser類中有這樣的方法:
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";單憑以上的信息我們可以推斷出,當Spring在讀取到自定義標簽<context:load-time-weaver/>后胡產生一個bean,而這個bean的id為loadTimeWeaver,class為org.springframework.context.weaving.DefaultContextLoadTimeWeaver,也就是完成了DefaultContextLoadTimeWeaver類的注冊。
完成了以上的注冊功能后,并不意味著這在Spring中就可以使用AspectJ了,因為我們還有一個很重要的步驟忽略了,就是LoadTimeAwareProcessor的注冊。在AbstractApplicationContext中的prepareBeanFactory函數中有這樣一段代碼:
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函數是在容器初始化時候調用的,也就是說只有注冊了LoadTimeAwareProcessor才會激活整個AspectJ的功能。
至此,所有的AspectJ的準備工作已經全部完成了,下篇文章將會繼續講述AOP的靜態代理的步驟------織入。
參考:《Spring源碼深度解析》 郝佳 編著:
轉載于:https://www.cnblogs.com/Joe-Go/p/10241624.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的创建AOP静态代理(上篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jan.09
- 下一篇: react实现svg实线、虚线、方形进度