设计模式(2)-动态代理-大白话版

92 阅读4分钟

什么是代理?

以租房子举例

房东能用钥匙开门

中介负责开门前带领顾客过去,开门后帮顾客讲解。

房东只要开门就好,所有这个动作的前后都给中介去做。那么中介就是这个代理

用代码写就是

 interface 开门接口{
     void 开门();
 }

  public class 房东1 implement 开门接口{
          //中介要求能开租房门的才能做房东
          public void 开门(){
              打开防盗门();
          }
  }



 public class 中介 implement 开门接口{
     开门接口  具备开门能力的人1;
     
    
     public 中介(开门接口 具备开门能力的人){
          this.具备开门能力的人1 = 具备开门能力的人;
     }
     
     
     public void 开门(){
         //开门前中介带领顾客`
         具备开门能力的人1.开门();
         //开门后中介讲解`
     }
 }
 
 
 //最终代码  虽然调用的是中介.开门,但最终会到房东开门
 
 public static void main(String args[]){
     new 中介(new 房东1()).开门();
 }
 

静态代理

上述就是静态代理最终还是把有开门能力的房东传给了中介,中介调用房东开的门,他只做了之前和之后的服务。

缺点: 如果接口修改,那么对应的中介和房东都要修改。 代理只服务一种类型对象,多类型就要写很多代理类。

动态代理

这个代理类我就是不想写,能不能让机器给我写了?

核心动作是 房东.开门()

那么我可以如果用反射可以这么写 反射来的开门方法.invoke(房东,参数没有就null);

如果我自己生成一个中介,它的类构造函数中有个匿名内部类,然后匿名内部类的方法方法中调用 反射来的方法.invoke(房东,参数没有就null);,中介调用任何方法都调用这个 方法就好啦。

用房东时,房东开门,前后自己做就好了。

那么我怎么做呢?核心就是通过开门接口class生成代理对象中介对象。反正两者挺像的。

package com.kent.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(final String[] args) {
        final IOpenDoor maleWorker = new OpenDoorImpl();
        IOpenDoor iOpenDoor = (IOpenDoor) Proxy.newProxyInstance(
                 maleWorker.getClass().getClassLoader(),
                 maleWorker.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("begin-------");
                        Object result = method.invoke(maleWorker, args);
                        System.out.println("end-------");
                        return result;
                    }
                });

        iOpenDoor.dowork();


    }

    public interface IOpenDoor {
        void dowork();
    }
    public static class OpenDoorImpl implements IOpenDoor {
        @Override
        public void dowork() {
            System.out.println(this.getClass().getSimpleName()+": dowork");
        }
    }

}

怎么做呢?

1、创建房东开门接口

2、创建InvocationHandler接口的实现类,也就是匿名方法,在里边用invoke实现房东真正做的事情

3、通过Proxy的静态方法newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h) 创建一个代理对象中介

4、使用代理对象中介

总结起来就是Proxy通过类加载器和接口接口class 动态创建了一个指定接口的代理对象。

看一下这个代理对象的class

 

public class EmployeeProxy extends Proxy implements HumanAct{
    private static Method m1;
    
    static {
        try {
            m1 = Class.forName("com.example.lib.HumanAct").getMethod("eating");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

   
    protected EmployeeProxy(InvocationHandler h) {
        super(h);
    }
    @Override
    public void eating() {
        try {
            //就是传进来了InvocationHandler匿名方法
            super.h.invoke(this,m1,null);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

可以看到 静态代码块中 反射生成了接口的方法的method,并在内部也实现了接口,等这个类调用该实现接口的方法,便会调用外部的匿名内部类InvocationHandler的invoke方法,那么相当于把中介之前的要做的事情都在invoker中做了。这样动态生成的代理对象,就不用写代理类,接口修改也只用改房东,目标类。

能否不写代理类,而直接得到代理Class对象,然后根据它创建代理实例(反射)。 Class包含了一个类的所有信息,比如构造器 方法字段。

代理类和目标类实现相同接口,是为了保证代理对象和目标对象 内部结构一致,这个对代理对象的操作可以到目标对象上来。

Proxy的静态方法getProxyClass(ClassLoader, interfaces),通过类加载器和一组接口可以得到代理Class对象。

getProxyClass(),会从接口Class中,拷贝类结构信息到一个新的Class中,但新Class带有构造器,可以创建对象。

核心就是通过接口Class,创建一个代理Class,通过代理Class对象创建代理对象。

image.png

image.png

image.png

image.png

再来个不那么恰当的例子。

九千岁是接口,有童子功大法。但是不能够实例化,因为没有小丁丁(构造器)。那么他不想一身武艺,后继无人。于是想到了两种方法。

静态代理 认干儿子,让代理类实现他,干儿子有构造器,可以new实例

动态代理 找神医,Proxy用克隆大法Proxy.getProxy(),克隆一个干儿子 代理Class,因为有小dd所以克隆人Class可以创建实例,也就是代理对象