关于spring jdk动态代理的闲扯

这些天看了一下spring的动态代理,写点笔记记录一下,帮助自己理清思路,同时也希望能帮助到大家。理解不是很清楚,有什么错误,欢迎指正。

 

spring 动态代理用到的地方很多,常见的AOP等。spring的动态代理实现由两种方式,JDK和CGLIB。JDK方式需要被代理的类是某个接口的实现,且只能代理该接口中的方法。CGLIB方式没有这个限制但是CGLIB是通过集成的方式的来实现方法的增强的,所以需要被代理的类与方法不是final修饰的。

先写一个JDK动态代理的例子

定义一个A接口

public interface A { public void test(); }

A接口的实现

public class AImpl implements A { @Override public void test() { System.out.println("A impl"); } }

后切接口

public interface AfterAdvice {
    public void after();
}

前切接口
public interface BeforeAdvice {
    public void before();
}

代理类

@Data
public class ProxyFactory {
    private Object targetObject;
    private BeforeAdvice beforeAdvice;
    private AfterAdvice afterAdvice;

    public Object createProxy(){
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = targetObject.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(beforeAdvice !=null){
                    beforeAdvice.before();
                }
                Object invoke = method.invoke(targetObject, args);
                if(afterAdvice !=null){
                    afterAdvice.after();
                }
                return invoke;
            }
        };
        return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
    }
}

测试代码

public static void main(String[] args) throws Exception {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setTargetObject(new AImpl());
    proxyFactory.setBeforeAdvice(new BeforeAdvice() {
        @Override
        public void before() {
            System.out.println("before....");
        }
    });
    proxyFactory.setAfterAdvice(new AfterAdvice() {
        @Override
        public void after() {
            System.out.println("after...");
        }
    });
    A proxy = (A)proxyFactory.createProxy();
    proxy.test();
}

运行输出

before....
A impl
after...

Process finished with exit code 0

从上面可以看到一个简单的基于JDK动态代理的代码增强功能就是实现了。

感觉上面JDK动态代理实现很简单。下面类似梳理一下原代码。可以看到整个程序的核心代码就一句Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); 当我们调用proxy.test()的时候实际上执行的是invocationHandler的invoke()方法的。所以主要目的就是找到我们的test()是如何被invoke()代理的。

下面走一走源码。

在Proxy.newProxyInstance()方法中重要的方法入如图一标注的两个方法。

getProxyClass0()获取代理classd对象是重中之重。

 proxyClassCache的初始化代码如下,

/**
 * a cache of proxy classes
 */
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
在getProxyClass0方法中只有一句代码,proxyClassCache.get(loader, interfaces);

再看一下proxyClassCache.get()方法,key是我们调用Proxy.newProxyInstance()时传入的classLoader,paramter是interfaces。在构造Factory时也传入到了Factory实例中。subkeyFactory是proxyClassCache初始化时传入的第一个参数即KeyFactory。

 

然后Factory的get方法。Factory是WeakCache的内部类。在get方法中重要的方法就是valueFactory.apply()。valueFactory是proxyClassCache初始化的时候传入的第二个参数即ProxyClassFactory。所以接下来就要看ProxyClassFactory的apply方法了。

 

在apply方法中关键代码,如下,generateProxyClasss生成class字节码,defineClass0是一个本地方法,将字节码生成对应的class对象。这两个方法是重头戏。但是可能需要对Class文件结构相当了解,有点懵,没有理解得很透彻。由于defineClass0是一个本地方法没有什么好说的。再捋一捋ProxyGenerator.generateProxyClass()方法。proxyName是一个jdk动态生成的代理类名看一下源码就知道了,interfaces当然是我们传入的接口。

 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
try {
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
    /*
     * A ClassFormatError here means that (barring bugs in the
     * proxy class generation code) there was some other
     * invalid aspect of the arguments supplied to the proxy
     * class creation (such as virtual machine limitations
     * exceeded).
     */
    throw new IllegalArgumentException(e.toString());
}

看一下generateProxyClass的实现,generateClass就生成了class字节码了。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    if (saveGeneratedFiles) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                try {
                    int var1 = var0.lastIndexOf(46);
                    Path var2;
                    if (var1 > 0) {
                        Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                        Files.createDirectories(var3);
                        var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                    } else {
                        var2 = Paths.get(var0 + ".class");
                    }

                    Files.write(var2, var4, new OpenOption[0]);
                    return null;
                } catch (IOException var4x) {
                    throw new InternalError("I/O exception saving generated file: " + var4x);
                }
            }
        });
    }

    return var4;
}

generateClassFile方法实现如下图。

再看一下generateConstructor方法,在这个方法中java/lang/reflect/Proxy,Ljava/lang/reflect/InvocationHandler;)V好像很眼熟,我们在调用Proxy.newProxyInstance的时候的Proxy就是java/lang/reflect/Proxy,同时此Proxy也有一个构造方法是传入InvocationHandler的。再看一下我们的图一,在获取了Class对象后newInstance()方法也传入了InvocationHandler参数。

private ProxyGenerator.MethodInfo generateConstructor() throws IOException {
    ProxyGenerator.MethodInfo var1 = new ProxyGenerator.MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
    DataOutputStream var2 = new DataOutputStream(var1.code);
    this.code_aload(0, var2);
    this.code_aload(1, var2);
    var2.writeByte(183);
    var2.writeShort(this.cp.getMethodRef("java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V"));
    var2.writeByte(177);
    var1.maxStack = 10;
    var1.maxLocals = 2;
    var1.declaredExceptions = new short[0];
    return var1;
}

generateClassFile可能是生成一个实现接口,继承了Proxy的class。接口的方法实现就就是调用InvocationHandler的invoke方法。

到底是不是这么回事呢?在idea VM options中加入-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true 再跑一次代码。

会生成一个com.sun.proxy.$Proxy0.class文件,用Idea打开看一下。如下图

是不是跟猜测的一模一样。h 当然是InvocationHandler了。

 

到此我们基本上把jdk代理流程捋了一次。cglib的实现方式再看看吧,感觉比jdk方式更难。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值