掌握java动态代理及原理有多难?

前言:使用的jdk是1.7,需要了解反射机制 泛型 字节码登概念!

一、代理模式

代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

file

二、编写一个java动态代理

1. 准备两个接口

分别是ProductService(生产接口)和 FactoryService(工厂接口)

代码如下:

package test.myproxy;

/**
 * <ul>
 * <li>Title: FactoryService</li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/13 0013 上午 10:05
 */
public interface FactoryService {

    void addProduce(int num);

}

package test.myproxy;

/**
 * <ul>
 * <li>Title: ProductService</li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/12 0012 下午 17:46
 */
public interface ProductService {

    /**
     * 添加产品
     * @param productName
     */
    void addProduct(String productName);

}

2. MyServiceImpl类实现上述两个接口

package test.myproxy;

/**
 * <ul>
 * <li>Title: MyServiceImpl</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/12 0012 下午 17:47
 */
public class MyServiceImpl implements ProductService,FactoryService {

    @Override
    public void addProduct(String productName) {
        System.out.println("正在添加"+productName);
    }


    @Override
    public void addProduce(int num) {
        System.out.println("准备生成"+num+"件商品");
    }
}


3.实现动态代理的功能

3.1 jdk中需要实现InvocationHandler这个接口,重写invoke方法
3.2 很多文章中为了便于理解,长把下面代码中的target 和getInstance的参数写成实际的接口,如上面的ProductService,这边不想这么写,是为了让大家能理解泛型,getInstance的接口里面我也明确指出参数是与代理对象是继承关系/实现接口的关系。

代码如下:


package test.myproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <ul>
 * <li>Title: JdkInvocationHandler</li>
 * <li>Description: TODO </li>
 * </ul>
 *
 * @author ken
 * @date 2021/4/12 0012 下午 17:45
 */
public class JdkInvocationHandler<T> implements InvocationHandler {
    //代理对象
    private T target;
        
    public <B extends T> T getInstance(B target){
        this.target = target;
        Class clazz = this.target.getClass();
        // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(),
                clazz.getInterfaces(),
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String currentDate  = simpleDateFormat.format(new Date());
        System.out.println("日期【"+currentDate + "】添加了一款产品");
        return method.invoke(target,args);

    }
}

3.3 测试

这里的示例,用了泛型和强转两种方式:

 public static void main(String[] args) throws Exception {
        ProductService proxy =  new JdkInvocationHandler<ProductService>().getInstance(new MyServiceImpl());
        proxy.addProduct("iphone");
                
        FactoryService proxyFactory = (FactoryService) new JdkInvocationHandler().getInstance(new MyServiceImpl());
        proxyFactory.addProduce(500);

        // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用
       /* byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});
        FileOutputStream os = new FileOutputStream("Proxy0.class");
        os.write(bytes);
        os.close();*/
    }

file

4.分析如何生成动态代理

在上面的 JdkInvocationHandler类 实现了 InvocationHandler接口,在getInstance方法中最关键的一行代码是Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(), this);

下面跟随这个代码进一步剖析内容:

下面给大家介绍是
位置: java.lang.reflect
类: Proxy

在这个类里面有一些成员属性,先了解一下后面有些会用到。

file
4.1 newProxyInstance方法的内容

(为了看起来简洁 我把一些注释删掉了)


 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        /*
         * 查找或生成指定的代理类。
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }

这段代码是通过 c1这个Class类信息 ,根据构造函数参数,动态生成反射类。这里很重要的在getProxyClass0这行代码,注释上的英文的意思是 运行在堆上,且这部分代码不要重构。由此也可知道这部分很重要。下面我把里面的代码展示出来:

4.2 getProxyClass0方法的内容
file

(这个是getProxyClass0方法上的注释)


private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller
            final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME);
            final ClassLoader ccl = caller.getClassLoader();
            checkProxyLoader(ccl, loader);
            ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
        }

        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        Class<?> proxyClass = null;
        String[] interfaceNames = new String[interfaces.length];

        // for detecting duplicates
        Set<Class<?>> interfaceSet = new HashSet<>();

        for (int i = 0; i < interfaces.length; i++) {
            String interfaceName = interfaces[i].getName();
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(interfaceName, false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != interfaces[i]) {
                throw new IllegalArgumentException(
                    interfaces[i] + " is not visible from class loader");
            }

            if (!interfaceClass.isInterface()) {
                throw new IllegalArgumentException(
                    interfaceClass.getName() + " is not an interface");
            }

            if (interfaceSet.contains(interfaceClass)) {
                throw new IllegalArgumentException(
                    "repeated interface: " + interfaceClass.getName());
            }
            interfaceSet.add(interfaceClass);

            interfaceNames[i] = interfaceName;
        }

        List<String> key = Arrays.asList(interfaceNames);

        /*
         * Find or create the proxy class cache for the class loader.
         */
        Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
        }

        synchronized (cache) {
            do {
                Object value = cache.get(key);
                if (value instanceof Reference) {
                    proxyClass = (Class<?>) ((Reference) value).get();
                }
                if (proxyClass != null) {
                    // proxy class already generated: return it
                    return proxyClass;
                } else if (value == pendingGenerationMarker) {
                    // proxy class being generated: wait for it
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                    }
                    continue;
                } else {

                    cache.put(key, pendingGenerationMarker);
                    break;
                }
            } while (true);
        }

        try {
            String proxyPkg = null;     // package to define proxy class in
            for (int i = 0; i < interfaces.length; i++) {
                int flags = interfaces[i].getModifiers();
                if (!Modifier.isPublic(flags)) {
                    String name = interfaces[i].getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            {

                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces);
                try {
                    proxyClass = defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
            // add to set of all generated proxy classes, for isProxyClass
            proxyClasses.put(proxyClass, null);

        } finally {

            synchronized (cache) {
                if (proxyClass != null) {
                    cache.put(key, new WeakReference<Class<?>>(proxyClass));
                } else {
                    cache.remove(key);
                }
                cache.notifyAll();
            }
        }
        return proxyClass;
    }


下面分析这段代码:
这部代码的意义

4.2.1. 如果类实现的接口超过65535,直接抛出 “interface limit exceeded”的异常,这部分应该是为了性能,一般情况下 我们的实现的接口 大多在200个以下,这部分可以忽略
4.2.2 类的所有接口实例化,并放入集合中 ,然后查找或创建类加载器的代理类缓存(loaderToCache 就是存放类加载器的缓存,key为类加载器,value为 Map<List<String>,Object>,该map的key则为 类实现的接口集合),后面是设置代理类的包名 如果有non-public的接口[即接口包名使用这个类的包名,否则默认包名是com.sun.proxy,类名通常是类名前缀+num,类名前缀为$Proxy,这部分后面会提到],最终返回的是生成代理类[注意自动生成代理类名],后面凭借“这个类名”可以调用方法
4.2.3 类的所有接口实例化
  Class <?> interfaceClass = Class.forName(interfaceName, false, loader);
4.2.4 类所有实现的接口 放入集合中,这个部分后面
        List<String> key = Arrays.asList(interfaceNames);
4.2.5 查找或创建类加载器的代理类缓存。
 Map<List<String>, Object> cache;
        synchronized (loaderToCache) {
            cache = loaderToCache.get(loader);
            if (cache == null) {
                cache = new HashMap<>();
                loaderToCache.put(loader, cache);
            }
            /*
             * This mapping will remain valid for the duration of this
             * method, without further synchronization, because the mapping
             * will only be removed if the class loader becomes unreachable.
             */
        }
4.2.6 这部分是记录 当前创建代理的个数,防止生成的代理类的”类名”重复
                long num;
                synchronized (nextUniqueNumberLock) {
                    num = nextUniqueNumber++;
                }
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
4.2.7 其中这一块就是创建 代理类的 字节码 ,defineClass0是通过类加载器、代理类名、字节码生成代理类。
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
        proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

【这块调用的c语言的本地方法】

file

ps:在很多文章里面都用了 ProxyGenerator.generateProxyClass的方法,然后用文件输出流把字节码内容写入文件里面,看到这里估计你应该知道为什么会如此写了吧。

file

下面简单看下 输出的内容

该类所有的方法都被“反射”创建了,实际调用时就是调用这个针对的方法。

file

file

file

看到最后,我们可以知道一个反射 无非把 自身的“所有方法” 赋予了另一个“凭空产生的”对象,让他使用自己的“权利”而已。

本文来源于:程序员ken,专属平台有csdn、思否(SegmentFault)、 简书、 开源中国(oschina)、掘金,转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,564评论 0 11
  • 彩排完,天已黑
    刘凯书法阅读 4,217评论 1 3
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 125,013评论 2 7