轻松理解Java动态代理(JDK)

作者: admin 分类: Java深入理解 发布时间: 2017-05-08 23:13

动态代理作用:主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法)

我在网上看到很多讨论Java动态代理的文章,都写得挺有水平,但是一般都用了一些高深的词汇,我个人觉得那些词汇对初学者来说是有点难理解的,所以,本文章要用很土的语言描述Java动态代理,争取让中学生都能听懂。
Java是JDK5中新加的机制,大家都知道Spring是用Java的动态代理实现的,那这个动态代理是什么东东呢,首先他肯定是个代理,我们先讲代理,把代理弄明白了,动态代理就好说了。代理都知道吧,你去买东西就有很多的代理商,他们就是卖原厂的东西。比如,你天天要买肉,猪是农民伯伯养的,但你是从屠夫手上买到肉的,这个屠夫就可以当成是代理。那为什么要代理呢,代理有什么用呢,当然是有事给他做了,对于屠夫这个代理就好理解了,因为你自己不可能去宰猪吧,所以代理就是去买活猪,然后宰掉再卖给你,当然屠夫有可能给肉注点水,关键看他坏不坏
我们应该要用三个类You、Butcher、Farmer分别指你、屠夫、农民伯伯。其中农民伯伯又提供一个买肉的方法给屠夫调用,这个方法输入是钱的数量,返回是肉的数量,都用int型,代码如下:
  1. class Farmer {
  2.     public int buyMeat(int money) {
  3.         int meat = 0;
  4.         // … meat = ***;
  5.         return meat;
  6.     }
  7. }
而屠夫则提供一个买肉的方法给你调用,同样是输入钱,返回肉,但是会把肉加工一下(杀猪和刮猪毛在代码中就省了,要不然还得为猪写个类),代码如下:
  1. class Butcher {
  2.     public int buyMeat(int money) {
  3.         Farmer farmer = new Farmer();            // 1.find a farmer.
  4.         int meat = farmer.buyMeat(money);        // 2.buy meat from the farmer.
  5.         meat += 5;                               // 3.inject 5 pound water into the meat, so weight will increase.
  6.         return meat;                             // 4.return to you.
  7.     }
  8. }
然你从屠夫手上买肉的代码就变成这样:
  1. class You {
  2.     public void work() {
  3.         int youMoney = 10;
  4.         Butcher butcher = new Butcher();                    // find a butcher.
  5.         int meat = butcher.buyMeat(youMoney);
  6.         System.out.println(“Cook the meat, weight: ” + meat);       // you cooked it.
  7.     }
  8. }
这个程序我们还可以优化一下,我们发现屠夫有农民有一个相同的买肉方法,我们可以提取一个接口,叫为商贩(pedlar)吧,以后你买肉就不用管他是屠夫还是农民伯伯了,只要他有肉卖就可以了,我们提取一个接口后,代码就变成这样:
  1. class You {
  2.     public void work() {
  3.         int youMoney = 10;
  4.         Peldar peldar= new Butcher();                               // find a peldar.
  5.         int meat = peldar.buyMeat(youMoney);
  6.         System.out.println(“Cook the meat, weight: ” + meat);        // you cooked it.
  7.     }
  8. }
  9. interface Peldar {
  10. int buyMeat(int money);
  11. }
  12. class Butcher implements Peldar {
  13.     @Override
  14.     public int buyMeat(int money) {
  15.         Farmer farmer = new Farmer();            // 1.find a farmer.
  16.         int meat = farmer.buyMeat(money);        // 2.buy meat from the farmer.
  17.         meat += 5;                               // 3.inject 5 pound water into the meat, so weight will increase.
  18.         return meat;                             // 4.return to you.
  19.     }
  20. }
  21. class Farmer implements Peldar {
  22.     @Override
  23.     public int buyMeat(int money) {
  24.         int meat = 0;
  25.         // … meat = ***;
  26.         return meat;
  27.     }
  28. }
这就是代理,值得注意的是一般代理类和最终类会实现同一接口,这样的好处是,调用者就不用关心当前引用的到底是代理还是最终类。
不过这叫静态代理,因为代理类(屠夫类)是你亲手写,动态代理就是Java在运行的时候,动态生成一个等价的代理类。虽然类是动态生成的,但是杀猪和注水的代码还是要写的,只是不要写一个类了。写到哪里呢,写到下面这个接口里面:
  1. public interface InvocationHandler {
  2.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  3. }
参数是什么意思呢,我写成这样,可能你就明白了:
  1. public interface InvocationHandler {
  2.     public Object invoke(Object butcher, Method buyMeat, Object[] money) throws Throwable;
  3. }
第一个参数是自动生成的代理类的一个对象(自动生成的屠夫类的对象),第二个参数是正前正在被调用的方法的对象(方法怎么还有对象呢,参见Java反射机制),我们这里只有一个方法叫buyMeat,所以这个参数代表的肯定就是它了,第三个参数是传给前面那个方法的参数数组,buyMeat只有一个参数,所以这个数组只会有一个元素。于是杀猪注水的代码写进来就变成这样了:
  1. InvocationHandler mInvocationHandler = new InvocationHandler() {
  2.     @Override
  3.     public Object invoke(Object butcher, Method buyMeat, Object[] args) throws Throwable {
  4.         Farmer farmer = new Farmer();                   // 1.find a farmer.
  5.         int meat = (Integer) buyMeat.invoke(farmer, args);      // 2.buy meat from the farmer.
  6.         meat += 5;                                      // 3.inject 5 pound water into the meat, so weight will increase.
  7.         return meat;                                    // 4.return to you.
  8.     }
  9. };
这个里调用农民伯伯的买肉方法有点不符常规,这里是反射机制调用法,意思是这样的,以farmer对象为接受者来调用buyMeat方法,跟直接调用farmer的方法是一样的,你可能会问那为什么不直接调用呢,你可能没注意,invoke的第一个参数类型是Object,所以你可以向任何对象发布调用命令(但不一定会成功,什么时候会成功等下说),如果你有很多farmer对象,甚至不是farmer对象,只要某接口的实例就可以(哪个接口等下说明,我们先命名为A接口),就可以当成参数传进来,然后对其进行方法调用。现在我们来看看如何生成代理类吧,很简单,可以调用Proxy的工厂方法,如下:
  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  2.     throws IllegalArgumentException
解释参数,第一个ClassLoader是用来加载代理类的(关于ClassLoader,本文暂不讲解),你暂不了解也没关系,第二个是一个数组,每个元数都是一个接口,新生成的代理都会实现所有这些接口,传给InvocationHandler.invoke第二个参数的方法,必定属于所有这些接口中的方法,上一段落说的那个A接口必须是数组中的一个元素,上一段落说的那个调用成失败问题也明了了。第三个参数InvocationHandler更好理解了,就是只要代理类中的任何方法被调用,就会通知这个InvocationHandler。下面写出完整代码:
/**
 * @author itmrchen
 * @date 2017/5/9
 * @time 11:01
 */
public class You {
    public void work() {
        int youMoney = 10;
        Peldar peldarProxy = (Peldar) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Peldar.class}, mInvocationHandler);
        int meat = peldarProxy.buyMeat(youMoney);
        System.out.println("Cook the meat, weight: " + meat);
    }

    InvocationHandler mInvocationHandler = new InvocationHandler() {
        /**
         * @param butcher 指代我们所代理的那个真实对象
         * @param buyMeat 指代的是我们所要调用真实对象的某个方法的Method对象
         * @param args 指代的是调用真实对象某个方法时接受的参数
         */
        public Object invoke(Object butcher, Method buyMeat, Object[] args)
                throws Throwable {
            Farmer farmer = new Farmer();                           // 1.find a farmer.
            int meat = (Integer) buyMeat.invoke(farmer, args);      // 2.buy meat from the farmer.
            meat += 5;                                              // 3.inject 5 pound water into the meat, so weight will increase.
            return meat;                                            // 4.return to you.
        }
    };
}
public interface Peldar {
    int buyMeat(int money);
}

public class Farmer implements Peldar {
    public int buyMeat(int money) {
        int meat = 0;
        return meat;
    }
}
这里You类里生成一个代理类,在代理类的buyMeat被调用时,代码就跟之前的静态代理一样的了。
下面再提供一种相关好理解的写法:
public interface Peldar {
    int buyMeat(int money);
}

public class Farmer implements Peldar {
    public int buyMeat(int money) {
        int meat = 0;
        return meat;
    }
}

public class DynamicProxy implements InvocationHandler {

    private Object butcher;//我们要代理的真实对象

    public DynamicProxy(Object butcher)
    {
        this.butcher = butcher;
    }
    /**
     * @param object 指代我们所代理的那个真实对象
     * @param buyMeat 指代的是我们所要调用真实对象的某个方法的Method对象
     * @param args 指代的是调用真实对象某个方法时接受的参数
     */
    public Object invoke(Object object, Method buyMeat, Object[] args)
            throws Throwable {
        int meat = (Integer) buyMeat.invoke(butcher, args);      // 2.buy meat from the farmer.
        meat += 5;                                              // 3.inject 5 pound water into the meat, so weight will increase.
        return meat;                                            // 4.return to you.
    }

}

public class Client {
    public static void main(String[] args) {
        //我们要代理的真实对象
        Farmer farmer = new Farmer();
        //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(farmer);
        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数farmer.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        Peldar peldar = (Peldar) Proxy.newProxyInstance(handler.getClass().getClassLoader(), farmer.getClass().getInterfaces(), handler);
        System.out.println(peldar.buyMeat(10));
    }
}

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注