• jdk动态代理源码分析


    代理的名词:

    代理对象:增强后的对象
    目标对象:被增强的对象
    他们不是绝对的,会根据情况发生变化

    java实现的代理的两种办法:

    静态代理:

    • 继承:代理对象继承目标对象,重写需要增强的方法;缺点:会代理类过多,非常复杂
    • 聚合:目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。缺点:也会产生类爆炸,只不过比继承少一点点

    总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。

    动态代理:
    自己模拟的动态代理
    不需要手动创建类文件(因为一旦手动创建类文件,就会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。缺点:首先要生成文件 动态编译文件 class 需要一个URLclassloader

    软件性能的最终体现在IO操作
    JDK动态代理 通过接口反射得到字节码,然后把字节码转成class native openJDK c++
    cglib

    先写一个demo 实现对目标类的一个代理:
    代理接口:

    public interface PlayGame {
    
        public void killBoss();
    }
    

    目标类:

    public class PlayGameGril implements PlayGame {
        @Override
        public void killBoss() {
            System.out.println("if you help me killBoss,I will date with you !!!");
        }
    }
    

    代理逻辑(处理器):

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class PlayGameHandler implements InvocationHandler {
    
        private Object target;
    
        public PlayGameHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("I'm Proxy ,I will play assassin, you just to lie");
            return method.invoke(target);
        }
    }
    

    测试类:

    @Test
    public void testPlayGame(){
        PlayGameGril playGameGril = new PlayGameGril();
        PlayGameHandler playGameHandler = new PlayGameHandler(playGameGril);
        PlayGame playGameProxy = (PlayGame)Proxy.newProxyInstance(playGameGril.getClass().getClassLoader(), playGameGril.getClass().getInterfaces(), playGameHandler);
        playGameProxy.killBoss();
    }
    

    输出结果:

    I'm Proxy ,I will play assassin, you just to lie
    if you help me killBoss,I will date with you !!!
    
    Process finished with exit code 0
    

    开始分析源码:我们进入(PlayGame)Proxy.newProxyInstance 这个里面看看:

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
    
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();//Check permissions required to create a Proxy class.
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
    
        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);
    

    接着我们看下 checkProxyAccess(Reflection.getCallerClass(), loader, intfs);这个方法,根据注释代理类是在这里面生成的

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
    
        // If the proxy class defined by the given loader implementing
        return proxyClassCache.get(loader, interfaces);
    }
    

    接着我们进入proxyClassCache.get(loader, interfaces);看看:

    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
    ......
        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
    

    在我们看源码时有时候会遇到源码比较多而我们不想每一个都进去看时或者我们不知道那个是关键代码时,我们可以关键变量处断点观察变量值变化的点,或者看调用链等都是看源码的一个技巧。像上面的我们根据return value 断点到这value已经有值了。于是往上查找value值变化点 V value = supplier.get(); 再次进去看看 有多个实现类,但是根据上面值会先从缓存中查找,所以我们看有缓存关键字的这个实现方法

    @Override
    public synchronized V get() { // serialize access
        // re-check
        Supplier<V> supplier = valuesMap.get(subKey);
        if (supplier != this) {
       ......
        try {
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) { // remove us on failure
                valuesMap.remove(subKey, this);
            }
        }
    

    同理查看valueFactory.apply(key, parameter)

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);//针对这里已经有接口类还重新生成接口类是为了验证是否是同一个加载器(涉及到jvm双亲委派机制)
            } catch (ClassNotFoundException e) {
            }
            ......
        /*
         * Generate the specified proxy class.
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);//生成代理类字节码
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);//字节码信息转化为代理类 private static native Class<?> defineClass0 本地方法涉及到c语言的交互
        } catch (ClassFormatError e) {
    

    我们看下ProxyGenerator.generateProxyClass 这个方法:public static byte[] generateProxyClass 是个静态方法我们直接调用它查看下生成的代理类信息

    @Test
    public void testPlayGame(){
    //查看生成的文件
    byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    "playGameProxy", new Class[]{PlayGame.class}, Modifier.PUBLIC);
    try {
    FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\aa.class"));
    fileOutputStream.write(proxyClassFile);
    fileOutputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    } 
    

    查看对应路径下的文件:

    public class playGameProxy extends Proxy implements PlayGame {
        private static Method m1;
        private static Method m3;
    ......
        public final void killBoss() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null); //这里的h 就是我们的代理逻辑处理类playGameHandler this即代理的对象proxy m3 即接口方法Method 代理类通过接口同名方法实现了对原目标方法的传递
            } catch (RuntimeException | Error var2) {
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
    ...
                m3 = Class.forName("PlayGame").getMethod("killBoss");
    ...
        }
    }
    

    我们再次对应下PlayGameHandler 相关代码:

    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("I'm Proxy ,I will play assassin, you just to lie");//相关的代理逻辑
            return method.invoke(target);//原目标方法
        }
    

    源对象通过PlayGameHandler 对象构造方法传入,自己也可以模拟jdk代理,大概思路可以通过代码拼接代理类java代码,生成文件后,手动加载编译用一个类似Url..loader加载(此时项目自动编译已完成所以需要手动编译字节码),再从文件中加载字节码信息,生成代理对象

  • 相关阅读:
    linux 查看系统版本
    SSL协议详解
    DIV+CSS圆角边框
    初识CoreData与详解
    initWithCoder: 与initWithFrame:的区别
    RAC初体验
    NSNotificationKVOlockdelegate的区别和用法
    Objective-O Runtime 运行时初体验
    UUID
    Swift微博编写感
  • 原文地址:https://www.cnblogs.com/leifonlyone/p/12707120.html
Copyright © 2020-2023  润新知