Java代理模式的实现方式

Wesley13
• 阅读 500

代理模式是23种设计模式之一,定义为:为其他对象提供一种代理以控制对这个对象的访问。简单来说就是在调用方无感知的情况下,修改原有的行为。

[TOC]

静态代理

即通过手动编码方式实现

public interface IFooService {
    void foo();
}
public class FooService implements IFooService {
    public void foo() {
        System.out.println("foo");
    }
}
public class FooServiceProxy implements IFooService {
    private IFooService fooService;

    public FooServiceProxy() {
        this.fooService = new FooService();
    }

    @Override
    public void foo() {
        System.out.println("before foo");
        fooService.foo();
    }

    public static void main(String[] args) {
        new FooServiceProxy().foo();
    }
}

当然实践中为了扩展性考虑,可能会把被代理对象作为参数传入代理类,这实际上就变成了装饰者模式

public class FooServiceDecorator implements IFooService {
    private IFooService fooService;

    public FooServiceDecorator(IFooService fooService) {
        this.fooService = fooService;
    }

    @Override
    public void foo() {
        System.out.println("before foo");
        fooService.foo();
    }

    public static void main(String[] args) {
        new FooServiceDecorator(new FooService()).foo();
    }
}

可见硬编码实现代理操作简单,但是需要为每个代理对象单独编码,工作量大且维护困难。

JDK动态代理

public class JdkProxyMain {
    public static void main(String[] args) {
        FooService target = new FooService();
        InvocationHandler handler = (o, method, objects) -> {
            System.out.println("before:" + method.getName());
            return method.invoke(target, objects);
        };
         IFooService proxy = (IFooService) Proxy.newProxyInstance(JdkProxyMain.class.getClassLoader(), new Class[]{IFooService.class}, handler);
        proxy.foo();
    }
}

这种做法实际上读取指定接口的方法,通过拼装字节码创建一个类并实现相关方法,在方法里加入执行Handler的代码,然后加载这个类,最后创建代理类对象。

通过增加jvm属性,可以让jvm保存代理生成的代理类class文件。

public class JdkProxyMain {
    public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        FooService target = new FooService();
        InvocationHandler handler = (o, method, objects) -> {
            System.out.println("before:" + method.getName());
            return method.invoke(target, objects);
        };
        IFooService proxy = (IFooService) Proxy.newProxyInstance(JdkProxyMain.class.getClassLoader(), new Class[]{IFooService.class}, handler);
        proxy.foo();
    }
}

执行完成后,项目根目录会生成com.sun.proxy.$Proxy0,用工具反编译查看源码

public final class $Proxy0 extends Proxy implements IFooService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final void bar() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void foo() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("org.dfg.demo.proxy.IFooService").getMethod("bar");
            m4 = Class.forName("org.dfg.demo.proxy.IFooService").getMethod("foo");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可见代理类继承自java.lang.reflect.Proxy,实现了指定接口和接口方法,方法内容为执行super.h(也就是创建代理是传入的InvocationHandler对象)的invoke方法。此处需要注意的是invoke方法的第一个参数为this,也就是代理对象本身,如果直接用这个对象作为参数用反射执行方法,会导致无限循环。这就是为何说呢么在InvocationHandler里通常使用外部传入的被代理对象执行反射调用。

Cglib

Cglib是一个用于生成和修改Java字节码的工具。为AOP、测试、数据访问框架等提供动态代理对象和字段访问拦截。

public class CglibTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FooService.class);
        enhancer.setCallback(new Interceptor(target));
        FooService service = (FooService) enhancer.create();
        service.foo();
    }
}
class Interceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before:" + method.getName());
        return proxy.invokeSuper(obj, args);
    }
}

Cglib创建代理不用基于接口,可以直接继承类,创建一个子类,重写他的方法。由于Cglib不能直接保存生成的代理类,需要用其他办法查看生成的代理类。首先,在上面代码中加一个阻塞方法,并启动执行:

public class CglibTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FooService.class);
        enhancer.setCallback(new Interceptor(target));
        FooService service = (FooService) enhancer.create();
        service.foo();
        for (;;){
        }
    }
}
class Interceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before:" + method.getName());
        return proxy.invokeSuper(obj, args);
    }
}

Hotspot提供了一个运行时调试工具,可以查看jvm数据。执行

# java1.8
java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB
# java9+
%JAVA_HOME%/bin/jhsdb hsdb

启动HSDB图形界面,点击File->Attach to HotSpot process,输入进程id(可执行jps获得),点击OK

Java代理模式的实现方式

点击Tools->Class Browser,输入FooService查找

Java代理模式的实现方式

点击下方窗口中,“public class org.dfg.demo.proxy.FooService$$EnhancerByCGLIB$$17719a51”

Java代理模式的实现方式

打开class详情点击“Create .class File”,保存class文件,生成的文件就在执行HSDB命令所在的目录。反编译class:

public class FooService$$EnhancerByCGLIB$$17719a51 extends FooService implements Factory {
    private MethodInterceptor CGLIB$CALLBACK_0;
    
    public Object newInstance(Callback var1) {
        CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
        FooService$$EnhancerByCGLIB$$17719a51 var10000 = new FooService$$EnhancerByCGLIB$$17719a51();
        CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
        return var10000;
    }

    public void setCallbacks(Callback[] var1) {
        this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
    }

    public final void foo() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$foo$0$Method, CGLIB$emptyArgs, CGLIB$foo$0$Proxy);
        } else {
            super.foo();
        }
    }
    ...
}

可以看到代理类继承自目标类,并覆盖了目标类的方法,方法里调用了var10000,也就是创建代理时传入的MethodInterceptor的invoke方法。MethodInterceptor.invoke方法相比Jdk的InvocationHandler.invoke方法,多了一个MethodProxy,这个对象会在代理类初始化时创建,每个方法对应一个,保存了方法的返回值类型、目标类、方法在目标类中的名字、代理类、方法在代理类中的名字。MethodProxy第一次执行时会执行初始化,会分别为目标类和代理类创建一个FastClass用于执行方法。在HSDB中搜索FooService时搜到的FooService$$FastClassByCGLIB$$4d44bc7.class和FooService$$EnhancerByCGLIB$$17719a51$$FastClassByCGLIB$$d3a59d03.class就是这时候创建的。反编译:

public class FooService$$FastClassByCGLIB$$4d44bc7 extends FastClass {
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        FooService var10000 = (FooService)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.foo();
                return null;
            case 1:
                return new Boolean(var10000.equals(var3[0]));
            case 2:
                return var10000.toString();
            case 3:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
    ...
}


public class FooService$$EnhancerByCGLIB$$17719a51$$FastClassByCGLIB$$d3a59d03 extends FastClass {
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        17719a51 var10000 = (17719a51)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            ...
            case 11:
                var10000.foo();
                return null;
               ...
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }

        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
}

这两个类的invoke方法分别被MethodProxy的invoke和invokeSuper调用,都是执行foo方法,区别是一个把第二个参数强转成FooService,一个把第二个参数强转成17719a51也就是代理类。而第二个参数就是invoke和invokeSuper的第一个参数。所以这里有一点要注意,如果调用MethodProxy.invoke方法传入了代理对象,会导致无限循环抛出StackOverflowError异常。如果MethodProxy.invokeSuper方法传入了目标对象,会强转失败抛出ClassCastException异常。MethodProxy的注释里也有提到

    /**
     * Invoke the original method, on a different object of the same type.
     * @param obj the compatible object; recursion will result if you use the object passed as the first
     * argument to the MethodInterceptor (usually not what you want)
     * @param args the arguments passed to the intercepted method; you may substitute a different
     * argument array as long as the types are compatible
     * @see MethodInterceptor#intercept
     * @throws Throwable the bare exceptions thrown by the called method are passed through
     * without wrapping in an <code>InvocationTargetException</code>
     */
    public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (IllegalArgumentException e) {
            if (fastClassInfo.i1 < 0)
                throw new IllegalArgumentException("Protected method: " + sig1);
            throw e;
        }
    }

    /**
     * Invoke the original (super) method on the specified object.
     * @param obj the enhanced object, must be the object passed as the first
     * argument to the MethodInterceptor
     * @param args the arguments passed to the intercepted method; you may substitute a different
     * argument array as long as the types are compatible
     * @see MethodInterceptor#intercept
     * @throws Throwable the bare exceptions thrown by the called method are passed through
     * without wrapping in an <code>InvocationTargetException</code>
     */
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

有一点要说的是MethodProxy.invokeSuper在代理类上调用方法,为什么不会导致无限循环?因为在创建代理类是还创建了方法“CGLIB$STATICHOOK1”。并把方法名传入MethodProxy。

public class FooService$$EnhancerByCGLIB$$17719a51 extends FooService implements Factory {
    final void CGLIB$foo$0() {
        super.foo();
    }
    static void CGLIB$STATICHOOK1() {
        Class var0 = Class.forName("org.dfg.demo.proxy.FooService$$EnhancerByCGLIB$$17719a51");
        Class var1;
        CGLIB$foo$0$Method = ReflectUtils.findMethods(new String[]{"foo", "()V"}, (var1 = Class.forName("org.dfg.demo.proxy.FooService")).getDeclaredMethods())[0];
        CGLIB$foo$0$Proxy = MethodProxy.create(var1, var0, "()V", "foo", "CGLIB$foo$0");
    }
}

MethodProxy初始化时从生成的代理类FastClass即FooService$$EnhancerByCGLIB$$17719a51$$FastClassByCGLIB$$d3a59d03获取到对应代理方法的index,并在执行invokeSuper时传入。

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private CreateInfo createInfo;
    
    private final Object initLock = new Object();
    private volatile FastClassInfo fastClassInfo;

    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
        MethodProxy proxy = new MethodProxy();
        proxy.sig1 = new Signature(name1, desc); //foo
        proxy.sig2 = new Signature(name2, desc); //CGLIB$foo$0
        proxy.createInfo = new CreateInfo(c1, c2);
        return proxy;
    }

    private void init() {
        if (fastClassInfo == null) {
            synchronized (initLock) {
                if (fastClassInfo == null) {
                    CreateInfo ci = createInfo;

                    FastClassInfo fci = new FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(sig1);
                    fci.i2 = fci.f2.getIndex(sig2); //18
                    fastClassInfo = fci;
                    createInfo = null;
                }
            }
        }
    }
}

代理方法id为18,调用了代理类的CGLIB$foo$0,并最终调用了代理类的super.foo方法。所以实际上调用的不是代理方法,也就不会导致循环调用。

public class FooService$$EnhancerByCGLIB$$17719a51$$FastClassByCGLIB$$d3a59d03 extends FastClass {
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        17719a51 var10000 = (17719a51)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 18:
                var10000.CGLIB$foo$0();
                return null;
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

    public int getIndex(Signature var1) {
        String var10000 = var1.toString();
        switch(var10000.hashCode()) {
        case -387144666:
            if (var10000.equals("CGLIB$foo$0()V")) {
                return 18;
            }
            break;
        return -1;
    }
}

由于虚拟机为了实现多态性,在调用虚方法时执行invokevirtual指令的分派逻辑是按照方法接收者的实际类型进行分派。也就是说子类在通过super调用父类对象时,父类中this指向的是子类。这就解决了一个问题,就是要求目标对象内部方法互相调用时,也能被代理到。

修改代码:

public interface IFooService {
    void foo();
    void bar();
}
public class FooService implements IFooService {
    public void foo() {
        System.out.println("foo");
        bar();
    }

    @Override
    public void bar() {
        System.out.println("bar");
    }
}
public class CglibInnerCallTest {
    public static void main(String[] args) {
        FooService target = new FooService();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FooService.class);
        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("before:" + method.getName());
            return proxy.invoke(target, args1);
        });
        FooService service = (FooService) enhancer.create();
        service.foo();

        System.out.println("~~~~~~~");

        enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> {
            System.out.println("before:" + method.getName());
            return proxy.invokeSuper(obj, args1);
        });
        service = (FooService) enhancer.create();
        service.foo();
    }
}

执行后打印:

before:foo
foo
bar
~~~~~~~
before:foo
foo
before:bar
bar

所以,Cglib相比Jdk代理,不需要被代理的目标类方法继承自接口,解决了类内部方法互相调用不能被代理的问题,同时提供一些其他功能:

如拦截器过滤:

public class CglibFilterTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(FooService.class);
        enhancer.setCallbacks(new MethodInterceptor[]{fooInterceptor, defaultInterceptor});
        enhancer.setCallbackFilter(method -> {
            if ("foo".equals(method.getName())) {
                return 0;
            } else {
                return 1;
            }
        });
        FooService service = (FooService) enhancer.create();
        service.foo();
        service.bar();
    }

    private static MethodInterceptor fooInterceptor = (obj, method, args, proxy) -> {
        System.out.println("invoke foo");
        return proxy.invokeSuper(obj, args);
    };

    private static MethodInterceptor defaultInterceptor = (obj, method, args, proxy) -> {
        System.out.println("invoke other");
        return proxy.invokeSuper(obj, args);
    };
}

执行后打印:

invoke foo
foo
invoke other
bar
invoke other
bar

动态代理虽然不用硬编码,实现了一定的代码复用,但是在使用的时候需要创建代理工厂,创建代理对象,仍然会侵入原有代码(虽然大部分开发场景中,Spring都把这个工作做了,这也是IOC的一大优势吧)。

AspectJ

AspectJ官网(https://www.eclipse.org/aspectj/)的描述是

AspectJ is

  • a seamless aspect-oriented extension to the Java™ programming language
  • Java platform compatible
  • easy to learn and use

AspectJ enables

  • clean modularization of crosscutting concerns, such as error checking and handling, synchronization, context-sensitive behavior, performance optimizations, monitoring and logging, debugging support, and multi-object protocols

简单来说Aspectj不是一种代理实现,而是直接修改字节码,将改动直接插入原有代码中,这个过程称为编织(weaving)。相比动态代理不需要创建代理类,没有多余的方法调用,所以效率更高,开销更少。

这里有几个概念:

  • Join Point(连接点)

    AspectJ通过将连接点的概念叠加到现有的Java语义上,并向Java中添加一些新的程序元素来扩展Java。连接点是程序执行过程中定义良好的点。这些包括方法和构造函数调用、字段访问和下面描述的其他。切入点选择连接点,并在这些连接点的执行上下文中公开一些值。有几个基本的切入点指示器,其他的可以通过切入点声明来命名和定义。

  • Pointcut (切入点)

    在AspectJ中,切入点选择程序流中的连接点并从这些连接点的执行上下文中公开数据的程序元素。主要通过Advice来使用。

  • Advice (通知)

    通知定义横切行为,一条通知的代码在它的切入点选择的每个连接点上运行。简单来说,切入点选择连接点,然后交给通知处理。

    AspectJ支持三种建议,通知的类型决定了它如何与定义它的连接点交互。因此,AspectJ将通知划分为在其连接点之前运行的通知(Before)、在其连接点之后运行的通知(After)和代替或“围绕”(Around)连接点运行的通知。

  • Inter-type declaration(类型间声明)

    官方描述为:AspectJ中的类型间声明是跨类及其层次结构的声明。它们可以声明跨多个类的成员,或者更改类之间的继承关系。简单来说就是修改类的接口和添加属性。

  • Aspect(切面)

    方面是一种横切类型,它封装了切入点、通知和静态横切特性。简单来说Aspect=Pointcut +Advice 。

https://www.eclipse.org/aspectj/doc/released/progguide/semantics.htm

AspectJ提供了三种编织方式:编译时(Compile-time weaving)、编译后(Post-compile weaving)、加载时(Load-time weaving)。官网还提到了运行时编织,并表示“AspectJ 5不提供对运行时编织的显式支持”。

https://www.eclipse.org/aspectj/doc/released/devguide/ltw.html#weaving-class-files-more-than-once

编译时编织(CTW)

通过工具直接在编译的时候织入,生成可以直接运行的字节码。

AspectJ定义了一种语言用于描述切面,语法类似java,扩展名为aj。

public aspect AspectJAdvisor {
    pointcut pc(): (execution(* *..FooService.*(..)));

    before (): pc() {
        System.out.println("aspect before:" + thisJoinPoint.getSignature().getName());
    }
}

编译时织入需要AspectJ提供的编译器编译,有几种工具:

以上几种方法其实最终都是调用aspectjtools-xxx.jar中的org.aspectj.tools.ajc.Main类实现的。这里以SDK命令为例:

ajc -cp E:\\aspectjrt-1.9.2.jar -d target\ajc src\main\java\org\dfg\demo\proxy\FooService.java src\main\java\org\dfg\demo\proxy\aspectj\AspectAdvice.aj src\main\java\org\dfg\demo\proxy\aspectj\AspectJTest.java

由于AspectJ在编译时已经将Advice织入字节码中,所以直接执行即可:

public class AspectJTest {
    public static void main(String[] args) {
        new FooService().foo();
    }
}

执行输出:

aspect before:foo
foo
aspect before:bar
bar

反编译class文件:

public class FooService {
    private static final JoinPoint$StaticPart ajc$tjp_0;
    private static final JoinPoint$StaticPart ajc$tjp_1;
    public void foo() {
        AspectJAdvisor.aspectOf().ajc$before$org_dfg_demo_proxy_aspectj_AspectJAdvisor$1$346234(FooService.ajc$tjp_0);
        System.out.println("foo");
        this.bar();
    }
    public void bar() {
        AspectJAdvisor.aspectOf().ajc$before$org_dfg_demo_proxy_aspectj_AspectJAdvisor$1$346234(FooService.ajc$tjp_1);
        System.out.println("bar");
    }
    static {
        ajc$preClinit();
    }
    private static void ajc$preClinit() {
        final Factory factory = new Factory("FooService.java", (Class)FooService.class);
        ajc$tjp_0 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "foo", "org.dfg.demo.proxy.FooService", "", "", "", "void"), 8);
        ajc$tjp_1 = factory.makeSJP("method-execution", (Signature)factory.makeMethodSig("1", "bar", "org.dfg.demo.proxy.FooService", "", "", "", "void"), 13);
    }
}
public class AspectJAdvisor {
    @Before(value = "pc()",argNames = "")
    public void ajc$before$org_dfg_demo_proxy_aspectj_AspectJAdvisor$1$346234(StaticPart thisJoinPointStaticPart) {
        System.out.println("aspect before:" + thisJoinPointStaticPart.getSignature().getName());
    }

    public static AspectJAdvisor aspectOf() {
        if (ajc$perSingletonInstance == null) {
            throw new NoAspectBoundException("org_dfg_demo_proxy_aspectj_AspectJAdvisor", ajc$initFailureCause);
        } else {
            return ajc$perSingletonInstance;
        }
    }
}

可见AspectJ直接将调用Advice方法的代码放到了目标类中,所以内部互相调用也能触发Advice。

编译后编织(PCW)

编译后编织(也称为二进制编织)用于编织现有的类文件和JAR文件。先将代码用javac编译,然后执行命令:

ajc -cp E:\aspectjrt-1.9.2.jar -inpath target\classes\ -d target\ajc src\main\java\org\dfg\demo\proxy\aspectj\AspectAdvice.aj
或
ajc -cp E:\aspectjrt-1.9.2.jar -inpath target\proxy-aspectj-1.0-SNAPSHOT.jar -outjar target\weaver.jar src\main\java\org\dfg\demo\proxy\aspectj\AspectAdvice.aj

加载时编织(LTW)

加载时编织机制通过增加JVM启动参数启用,通过配置文件确定切面,并提供一些选项允许用户调试配置和编织过程。

在这之前要先介绍JVMTI,JVM工具接口(JVM Tool Interface)是开发和监视工具使用的编程接口。它提供了一些检查JVM状态和控制中运行的应用程序的接口,可以用于分析,调试,监控,线程分析和覆盖率分析等。

https://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html

其中有个Bytecode Instrumentation接口,该接口支持字节码检测,即改变构成目标程序的Java虚拟机字节码指令的能力。Instrumentation 支持三种插入方法:

  • 静态检测:在将类文件加载到VM之前对其进行检测。

    简单来说就是创建一个全名相同的class文件,放到环境变量前面,根据jvm不会加载重复类的特性“覆盖”原始类字节码,从而修改类的行为。

  • 加载时间检测:当VM加载类文件时,将发送类文件的原始字节以便将检测发送给代理。

    Java1.5增加的功能,在启动参数中加入-javaagent:xxx.jar,jvm加载类之前,会调用指定agent的premain方法,并修改字节码。

  • 动态检测:已经加载(甚至可能正在运行)的类被修改。

    Java1.6增加的功能,可以在jvm启动后,甚至类加载后,对类进行修改并重新加载,但是有诸多限制。

javaagent的用法,网络上已经有很多文章,就不做过多介绍了。

AspectJ提供了三种实现LTW方式

  • Agent方式

    基于JVMTI提供的javaagent参数:-javaagent:pathto/aspectjweaver.jar。

  • 命令脚本aj

    使用AspectJ提供的SDK里的脚本aj(指定了AspectJ的类加载器)或aj5(指定javaagent)命令启动程序。

  • 自定义类加载器

    AspectJ提供了一个接口,允许用户自定义类加载器在加载之前对类进行编织。

一般情况下使用javaagent方式,只需要在环境变量加入配置文件,启动参数中加入javaagent即可,对现有流程的影响是最小的。

由于javac无法编译aj文件,LTW似乎也无法识别aj文件,所以Advisor需要改成java类文件:

@Aspect
public class AspectJAdvisorClass {
    @Pointcut("(execution(* *..FooService.*(..)))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void pointcut(JoinPoint joinPoint) {
        System.out.println("aspect before:" + joinPoint.getSignature().getName());
    }
}

在环境变量里加入配置文件META-INF/aop.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD 1.5.0//EN" "http://www.eclipse.org/aspectj/dtd/aspectj_1_5_0.dtd">
<aspectj>
    <weaver options="-verbose -debug">
    </weaver>
    <aspects>
        <aspect name="org.dfg.demo.proxy.aspectj.AspectJAdvisorClass"/>
    </aspects>
</aspectj>

调用方式仍然不需要做修改:

public class AspectJTest {
    public static void main(String[] args) {
        new FooService().foo();
    }
}

在启动命令中加入,执行

java -javaagent:E:\aspectjweaver-1.9.2.jar -classpath target\classes;D:\.m2\repository\org\aspectj\aspectjrt\1.9.2\aspectjrt-1.9.2.jar org.dfg.demo.proxy.aspectj.AspectJTest

输出:

aspect before:foo
foo
aspect before:bar
bar

查看aspectjweaver-1.9.2.jar,发现它的META-INF/MANIFEST.MF文件中,同时指定了Premain-Class和Agent-Class

Premain-Class: org.aspectj.weaver.loadtime.Agent
Agent-Class: org.aspectj.weaver.loadtime.Agent
Can-Redefine-Classes: true

而org.aspectj.weaver.loadtime.Agent类中也实现了agentmain方法,难道AspectJ支持加载后编织?于是做了一个测试:

public class AgentmainTest {
    public static void main(String[] args) throws Exception {
        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        final VirtualMachine machine = VirtualMachine.attach(pid);
        machine.loadAgent("E:\\aspectjweaver-1.9.2.jar");
        machine.detach();
        new FooService().foo();
    }
}

执行输出:

aspect before:foo
foo
aspect before:bar
bar

看起来似乎是实现了jvm启动后编织,但是这个测试里FooService实际上没有加载(搜索类被JVM加载的几种触发条件)。如果在最前面触发类加载,就不行了。

public class AspectJAgentmainTest {
    public static void main(String[] args) throws Exception {
        FooService.class.getName(); //触发类加载
        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        final VirtualMachine machine = VirtualMachine.attach(pid);
        machine.loadAgent("E:\\aspectjweaver-1.9.2.jar");
        machine.detach();
        new FooService().foo();
    }
}

执行输出,发现织入失效了:

foo
bar

查看java.lang.instrument.Instrumentation.addTransformer()注释:

   /**
     * Registers the supplied transformer. 
     * All future class definitions will be seen by the transformer, 
     * except definitions of classes upon which any registered transformer is dependent.
     * The transformer is called when classes are loaded, when they are {@linkplain #redefineClasses redefined}.
     * and if <code>canRetransform</code> is true,
     * when they are {@linkplain #retransformClasses retransformed}.
     * See {@link java.lang.instrument.ClassFileTransformer#transform
     * ClassFileTransformer.transform} for the order of transform calls.
     * ......
     */
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

注释里说类被加载或者调用redefined,才会执行这个方法。由于同一个类只会被JVM加载一次,所以上面例子中没有织入成功。这里还提到了两个方法redefined和retransformed。由于发现org.aspectj.weaver.loadtime.Agent中保存了Instrumentation对象,所以尝试调用这个两个方法。

retransformed方法可以对已加载的类的字节码做转换:

public class AspectJRetransformTest {
    public static void main(String[] args) throws Exception {
        FooService.class.getName();

        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        final VirtualMachine machine = VirtualMachine.attach(pid);
        machine.loadAgent("E:\\aspectjweaver-1.9.2.jar");
        machine.detach();

        Agent.getInstrumentation().retransformClasses(FooService.class);

        new FooService().foo();
    }
}

执行会报错:

Exception in thread "main" java.lang.UnsupportedOperationException: retransformClasses is not supported in this environment
    at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:141)
    at org.dfg.demo.proxy.aspectj.agentmain.AspectJRetransformTest.main(AspectJRetransformTest.java:21)

因为aspectjweaver-1.9.2.jar的META-INF/MANIFEST.MF文件里没有指定Can-Retransform-Classes为true,同时org.aspectj.weaver.loadtime.Agent类的agentmain方法中调用 没有指定第二个参数为true:

   public static void premain(String options, Instrumentation instrumentation) {
        if (s_instrumentation != null) {
            return;
        }
        s_instrumentation = instrumentation;
        s_instrumentation.addTransformer(s_transformer);
    }
    public static void agentmain(String options, Instrumentation instrumentation) {
        premain(options, instrumentation);
    }

即使Can-Retransform-Classes设置为true也不会生效。那么,aspectjweaver-1.9.2.jar的META-INF/MANIFEST.MF文件里指定了Can-Redefine-Classes为true,是否能通过redefine织入呢:

public class AspectJRedefineTest {
    public static void main(String[] args) throws Exception {
        FooService.class.getName();

        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        final VirtualMachine machine = VirtualMachine.attach(pid);
        machine.loadAgent("E:\\aspectjweaver-1.9.2.jar");
        machine.detach();

        final InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("org/dfg/demo/proxy/FooService.class");
        Agent.getInstrumentation().redefineClasses(new ClassDefinition(FooService.class, IOUtils.toByteArray(is)));

        new FooService().foo();
    }
}

执行后仍然报错,即不能添加或删除字段:

Exception in thread "main" java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
    at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
    at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
    at org.dfg.demo.proxy.aspectj.agentmain.AspectJRedefineTest.main(AspectJRedefineTest.java:26)

关于这一点,java.lang.instrument.Instrumentation的redefineClasses方法和retransformClasses方法都有说明:

    /**
     * ......
     * The redefinition may change method bodies, the constant pool and attributes.
     * The redefinition must not add, remove or rename fields or methods, change the
     * signatures of methods, or change inheritance.  These restrictions maybe be
     * lifted in future versions.  The class file bytes are not checked, verified and installed
     * until after the transformations have been applied, if the resultant bytes are in
     * error this method will throw an exception.
     * ......
     */
    void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;

就是说重定义不得添加、移除、重命名字段或方法,不得更改方法签名、继承关系。可能在以后的版本中会取消这些限制。联想在之前反编译的AspectJ编织后的class里,可以看到增加了一个属性用于保存JointPoint,这是不被允许的。AspectJ官网上说:“AspectJ 5不提供运行时编织的显式支持,尽管简单的编码模式可以支持在方面动态启用和禁用通知。”,大概就是因为这个原因。同时aspectjweaver.jar的MANIFEST.MF文件中指定Can-Retransform-Classes为true,Agent类保留了Instrumentation对象,而源码中也没有找到使用的地方,所以这似乎是开放给特定需求的用户使用。

这里提供一个简单的加载后修改字节码的例子,新建一个项目:

package org.dfg.demo.agentmain;
public class SimpleAgent {
    public static Instrumentation instrumentation;

    public static void agentmain(final String options, final Instrumentation instrumentation) {
        System.out.println("agentmain! arg:" + options);
        instrumentation.addTransformer(new TestTransformer());//第二个参数只影响retransform

        Agent.instrumentation = instrumentation;
    }
}
public class SimpleTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> type, ProtectionDomain domain, byte[] bytes) {
        if (!"org/dfg/demo/agentmain/FooService".equals(className)) {
            return null;
        }
        System.out.println("transform!");

        try {
            ClassPool pool = new ClassPool();
            pool.appendSystemPath();
            CtClass cc = pool.makeClass(new ByteArrayInputStream(bytes));
            final CtMethod method = cc.getMethod("foo", "()V");
            method.insertBefore("System.out.println(\"before foo\");");
            return cc.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

添加META-INF/MANIFEST.MF文件:

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Agent-Class>org.dfg.demo.agentmain.SimpleAgent</Agent-Class>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

在另一个模块引入依赖:

    <dependencies>
        <dependency>
            <groupId>org.dfg.demo</groupId>
            <artifactId>agentmain</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
    </dependencies>
    <!-- JVMTI是只有HotSpot支持,所以需要单独引入依赖 -->
    <profiles>
        <profile>
            <id>default-tools</id>
            <activation>
                <jdk>[,1.8]</jdk>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>${java.version}</version>
                    <scope>system</scope>
                    <optional>true</optional>
                    <systemPath>${java.home}/../lib/tools.jar</systemPath>
                </dependency>
            </dependencies>
        </profile>
        <profile>
            <id>java9-tools</id>
            <activation>
                <jdk>[1.9,]</jdk>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>com.sun</groupId>
                    <artifactId>tools</artifactId>
                    <version>${java.version}</version>
                    <scope>system</scope>
                    <optional>true</optional>
                    <systemPath>${java.home}/lib/jrt-fs.jar</systemPath>
                </dependency>
            </dependencies>
        </profile>
    </profiles>

执行测试:

public class RedefineTest {
    public static void main(String[] args) throws Exception {
        FooService.class.getName(); //触发类加载

        final File file = new File("./agentmain/target/agentmain-1.0-SNAPSHOT.jar");

        String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
        final VirtualMachine machine = VirtualMachine.attach(pid);
        machine.loadAgent(file.getCanonicalPath());
        machine.detach();

        //重新加载
        SimpleAgent.instrumentation.redefineClasses(new ClassDefinition(FooService.class, getByteArray()));

        new FooService().foo();
    }

    public static byte[] getByteArray() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get(FooService.class.getName());
        return cc.toBytecode();
    }
}

输出:

agentmain! arg:null
transform!
before foo
foo

AspectJ支持多种表达式,网络上已有很多介绍,有一点需要说的是call和execution的区别。官方的说法是execution是在被调用方法内部织入,而call是在调用目标方法的地方织入,call无法捕获非静态方法的super调用。经验法则是,如果要捕获一个实际代码运行时的连接点(通常是跟踪的情况),使用execution,如果要捕获特定签名运行时的连接点调用(通常是生产方面的情况),使用call。

通常使用起来区别不大,有一种特殊情况,比如Spring中的一种常见场景,在Service实现类上启动事务,在调用的地方使用接口注入,如果在使用call配置service实现类的切面,就不会生效。

public interface IUserService {
    void f1();
    void f2();
}
public class UserService implements IUserService {
    @Override
    public void f1() {
        System.out.println("f1");
    }
    @Override
    public void f2() {
        System.out.println("f2");
        f1();
    }
}
public aspect ImplAdvisor {
    pointcut pc(): call(* *..UserService.*(..)); //多态性取不到实际类型,所以f2方法拦截不到
    before (): pc() {
        System.out.println("aspect before:" + thisJoinPoint.getSignature().getName());
    }
}
public class UserServiceTest {
    private IUserService service;

    public UserServiceTest(UserService userService) {
        this.service = userService;
    }

    public static void main(String[] args) {
        new UserServiceTest(new UserService()).run();
    }

    public void run() {
        service.f2();
    }
}

执行输出:

f2
aspect before:f1
f1

这是因为call是在调用端切入,而由于java的多态性,在编译时无法确定IUserService的实际类型是UserService,也就没法在调用端织入。

Spring AOP

多数人接触AOP就是从Spring开始的,AOP作为Spring除了IOC(叫DI更合适)之外的另一核心技术,在各种场景都有应用。SpringAOP有两种实现方式:JDK Proxy和Cglib,取决于启用配置时的参数。

通常情况配置AOP有两种方式:

xml

在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

Spring启动时会解析xml,会根据Namespace找到spring-aop.jar中的META-INF\spring.handlers,取得对应的handler:AopNamespaceHandler。可查看源码:

org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver org.springframework.beans.factory.xml.BeanDefinitionParserDelegate org.springframework.aop.config.AopNamespaceHandler

AopNamespaceHandler中注册了一个AspectJAutoProxyBeanDefinitionParser,在解析到"aspectj-autoproxy"标签时调用AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(),这个方法调用了AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary()创建AnnotationAwareAspectJAutoProxyCreator的BeanDefinition,然后判断获取的配置“proxy-target-class”为true时调用AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(),为添加AnnotationAwareAspectJAutoProxyCreator的BeanDefinition的proxyTargetClass属性为true。

public abstract class AopNamespaceUtils {
    public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    }

    private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
        if (sourceElement != null) {
            boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
            if (proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
        ......
        }
    }
    ......
}
public abstract class AopConfigUtils {
    ......
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

    public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
        }
    }
    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
        ......
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }
}

注解

在Configuration类上加入@EnableAspectJAutoProxy(proxyTargetClass = true)

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringAnnotationTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringAnnotationTest.class);
    }
}

ConfigurationClassPostProcessor继承了BeanDefinitionRegistryPostProcessor,会在BeanFactory初始化完成之前调用。ConfigurationClassPostProcessor会找到添加了@Configuration注解的类,创建并加载该类BeanDefinition,同时会查找这个类(EnableAspectJAutoProxy)上声明的@Import(AspectJAutoProxyRegistrar.class)。

org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions org.springframework.context.annotation.ConfigurationClassParser org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader

最后调用AspectJAutoProxyRegistrar的registerBeanDefinitionsf方法(继承自ImportBeanDefinitionRegistrar)。

public class ConfigurationClassPostProcessor {
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();

        for (String beanName : candidateNames) {
            ......
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            parser.parse(candidates);
            parser.validate();

            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            ......
        }
    }
}
class ConfigurationClassParser {
    public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
                ......
                if (bd instanceof AnnotatedBeanDefinition) {
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                ......
        }
    }
    private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
            throws IOException {

        if (visited.add(sourceClass)) {
            for (SourceClass annotation : sourceClass.getAnnotations()) {
                String annName = annotation.getMetadata().getClassName();
                if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
                    collectImports(annotation, imports, visited);
                }
            }
            imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
        }
    }
}
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

跟xml配置方式一样,最终仍然调用了AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary()方法,并注册了AspectJAwareAdvisorAutoProxyCreator的bean。查看AspectJAwareAdvisorAutoProxyCreator的UML:

Java代理模式的实现方式

AspectJAwareAdvisorAutoProxyCreator继承了ProxyConfig,AopConfigUtils.forceAutoProxyCreatorToUseClassProxying()就是设置ProxyConfig的proxyTargetClass属性为true。还实现了InstantiationAwareBeanPostProcessor接口,InstantiationAwareBeanPostProcessor继承自BeanPostProcessor,当spring实例化bean之前,会调用所有的InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(),实例化bean之后,会调用所有BeanPostProcessor.postProcessAfterInitialization()。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
        ......
        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }
        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        }catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {
        Object result = existingBean;
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }
}

AbstractAutoProxyCreator实现了postProcessAfterInitialization方法,先判断是否有符合当前bean的advise。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,    @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);

        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);

        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        return proxyFactory.getProxy(getProxyClassLoader());
    }
}

判断bean是否需要代理,使用了AspectJ的Pointcut API,查找符合条件的方法。

public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
    @Override
    public boolean matches(Class<?> targetClass) {
        PointcutExpression pointcutExpression = obtainPointcutExpression();
        try {
            try {
                return pointcutExpression.couldMatchJoinPointsInType(targetClass);
            } catch (ReflectionWorldException ex) {
                logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
                // Actually this is still a "maybe" - treat the pointcut as dynamic if we don't know enough yet
                PointcutExpression fallbackExpression = getFallbackPointcutExpression(targetClass);
                if (fallbackExpression != null) {
                    return fallbackExpression.couldMatchJoinPointsInType(targetClass);
                }
            }
        } catch (Throwable ex) {
            logger.debug("PointcutExpression matching rejected target class", ex);
        }
        return false;
    }
}

最后创建代理

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        //启用optimize || 启用proxyTargetClass || 是否仅指定了SpringProxy接口或者根本没有指定代理接口
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            }
            //目标类是接口 || 目标类是JDK Proxy创建的代理类
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
}

可见Spring AOP虽然使用了AspectJ的表达式和部分API,但实际上还是使用JDK Proxy和Cglib两种代理实现。

所以Spring不支持AspectJ的call表达式,因为动态代理无法做到在调用方拦截。

Spring的AOP两种方式都是用被代理对象执行调用(虽然有个例外ConfigurationClassEnhancer,仅用于@Configuration指定的类),所以仍然不能实现做到类内部调用。Spring提供了一种解决办法,AOP配置参数还有一个名为exposeProxy,注释说这个参数:“设置代理是否应该由AOP框架通过ThreadLocal暴露出来,以便通过AopContext类进行检索。 如果建议的对象需要自己调用另一个建议的方法,这将非常有用”。

public class ProxyConfig implements Serializable {
    /**
     * Set whether the proxy should be exposed by the AOP framework as a
     * ThreadLocal for retrieval via the AopContext class. This is useful
     * if an advised object needs to call another advised method on itself.
     * (If it uses {@code this}, the invocation will not be advised).
     * <p>Default is "false", in order to avoid unnecessary extra interception.
     * This means that no guarantees are provided that AopContext access will
     * work consistently within any method of the advised object.
     */
    public void setExposeProxy(boolean exposeProxy) {
        this.exposeProxy = exposeProxy;
    }
}
class CglibAopProxy implements AopProxy, Serializable {
    private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
        // Parameters used for optimization choices...
        boolean exposeProxy = this.advised.isExposeProxy();

        // Choose a "straight to target" interceptor. (used for calls that are
        // unadvised but can return this). May be required to expose the proxy.
        Callback targetInterceptor;
        if (exposeProxy) {
            targetInterceptor = (isStatic ?
                    new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                    new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
        } else {
            targetInterceptor = (isStatic ?
                    new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                    new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
        }
        ......
    }
    private static class StaticUnadvisedExposedInterceptor implements MethodInterceptor, Serializable {
        @Nullable
        private final Object target;

        public StaticUnadvisedExposedInterceptor(@Nullable Object target) {
            this.target = target;
        }
        @Override
        @Nullable
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            try {
                oldProxy = AopContext.setCurrentProxy(proxy);
                Object retVal = methodProxy.invoke(this.target, args); //基于target执行调用
                return processReturnType(proxy, this.target, method, retVal);
            }
            finally {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }
}
public final class AopContext {
    private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal<>("Current AOP proxy");

    private AopContext() {
    }
    public static Object currentProxy() throws IllegalStateException {
        Object proxy = currentProxy.get();
        if (proxy == null) {
            throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
        }
        return proxy;
    }
    static Object setCurrentProxy(@Nullable Object proxy) {
        Object old = currentProxy.get();
        if (proxy != null) {
            currentProxy.set(proxy);
        } else {
            currentProxy.remove();
        }
        return old;
    }
}

如果exposeProxy=true,会使用StaticUnadvisedExposedInterceptor,在执行目标方法之前,会把当前代理对象放入AopContext封装的ThreadLocal中。在目标方法中就可以取得代理对象,从而调用代理的方法,如果目标对象为singleton,每次都从Spring上下文中获取,也可能达到同样效果

public class FooService implements IFooService {

    @Autowired
    ApplicationContext ctx;

    public void foo() {
        System.out.println("foo");
        bar();

        System.out.println("~~~~~~~~~");
        Object proxy = AopContext.currentProxy();
        if (proxy instanceof FooService) {
            ((FooService) proxy).bar();
        }

        System.out.println("~~~~~~~~~");
        ctx.getBean(FooService.class).bar();
    }
    public void bar() {
        System.out.println("bar");
    }
}

Javassist

Javassist是一个用于在Java中编辑字节码的类库,能够在运行时定义新类,或在JVM加载时修改类。 Javassist不仅支持字节码级支持,还允许以源码方式进行修改。方便不熟悉Java字节码规范的用户对程序进行编辑。

使用Javassist可以方便的通过修改代码实现代理的效果

public class JavassistTest {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();
        //必须写字符串,不能写FooService.class.getName(),因为会触发类初始化,导致class被加载,执行toClass会报错
        CtClass cc = pool.get("org.dfg.demo.proxy.FooService");

        final CtMethod method = cc.getDeclaredMethod("foo");
        method.insertBefore("System.out.println(\"before foo\");");
        method.setBody(method, null);

        cc.writeFile(Thread.currentThread().getContextClassLoader().getResource(".").getFile());//保存修改后代字节码
        cc.toClass();
        cc.freeze();

        FooService service = new FooService();
        service.foo();
    }
}

总结

Java中常见代理实现方法有三类:

  • 硬编码实现静态代理
  • JDK Proxy、Cglib等工具实现动态代理
  • AspectJ、Javassist、ASM等工具修改字节码,不算代理,但是效果类似

相关代码:https://github.com/dingfugui/proxy-demo

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java模式之一
代理模式的概念:对其他对象提供一种代理以控制对这个对象的访问代理模式的三种实现(1)静态代理静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.eg publicinterfacePammerDao{   voiddomain();}publicTargetimpl
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Java中jdk代理和cglib代理
代理模式给某一个对象提供一个代理,并由代理对象控制对原对象的引用。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。在Java中代理模式从实现方式上可以分为两个类别:静态代理和动态代理静态代理:也就是我们学习设计模式之代理模式时常见的事例,具体不在赘述,参见:
Wesley13 Wesley13
3年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Wesley13 Wesley13
3年前
Java设计模式_(结构型)_代理模式
引用百科即ProxyPattern,23种常用的面向对象软件的设计模式之一。代理模式的定义:为其他对象提供一种代理(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fbaike.baidu.com%2Fitem%2F%25E4%25BB%25A3%25E7%2590%25
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
待兔 待兔
5个月前
Java设计模式之 - 代理模式
代理模式的介绍:代理模式也称为委托模式,在开发中经常用到,是编程的好帮手,在日常生活中也比较常见。比如公司中午让同事帮忙带一份饭,比如我们请一个律师打官司,比如我们用代理服务器上网等等。代理模式真是无处不在。代理模式的定义:为其它对象提供一种代理以控制对这