创建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 {
    @Override
    public 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 {

    @Override
    public 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源码深度解析》 郝佳 编著:

posted on 2019-01-09 10:10  AoTuDeMan  阅读(569)  评论(0编辑  收藏  举报

导航