代理模式

静态代理

要求:真实角色,代理角色;真实角色和代理角色要实现同一个接口,代理角色要持有真实角色的引用。

案例

有个抽象的方法,租房,由房东实现,中介来代理

uml类图

核心代码

/**
 * 中介
 * 静态代理模式
 */
public class RentProxy implements Rent {

    private Rent rent;

    public RentProxy(Rent rent) {
        this.rent = rent;
    }

    @Override
    public void rent() {
        System.out.println("中介开始推销");
        this.rent.rent();
        System.out.println("中介收钱");
    }
}

静态代理模式的优势

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共的业务交给代理类,实现了业务的分工
  • 公共业务发生扩展时,集中管理

静态代理缺点

  • 一个真实的角色就会产生一个代理类,代码量会翻倍,开发效率降低(动态代理可解决)

动态代理

  • 动态代理和静态代理实现的功能是一样的
  • 动态代理是自动生成的,不是我们预先写好的
  • 动态代理分为两个大类
    • 基于接口的动态代理:JDK动态代理
    • 基于类的动态代理:cglib

jdk动态代理

两个关键的类

  • (Interface)InvocationHandler
  • Proxy

InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口
每个代理的实例都有一个关联的调用处理程序,当在代理实例上调用方法时,方法调用将被编码并分配到其调用处理程序的invoke方法上。

public Object invoke(Object proxy, Method method, Object[] args)

参数

  • proxy
    调用该方法的代理实例
  • method
    所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口
  • args
    包含的方法调用传递代理实例的参数值的对象的阵列

Proxy

proxy提供了创建动态代理类和实例的静态方法。

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

核心代码

public class ProxyHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    //需要传人一个实现类
    public void setRent(Rent rent) {
        this.rent = rent;
    }

    /**
     * 生成代理实例
     * 一种比较快捷的方式
     *
     * @return
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    //重写invoke,处理代理实例,并返回
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("动态代理");
        //代理rent接口
        Object invoke = method.invoke(rent, args);
        return invoke;
    }
}

创建代理类

public class Consumer {

    /**
     * 获取动态代理对象的两种方式
     *
     * @param args
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws InstantiationException
     */
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        /**
         * 第一种方式,采用newProxyInstance的快捷方式
         * 其实质也是第二种方式
         */
        ProxyHandler proxyHandler = new ProxyHandler();
        proxyHandler.setRent(new Host());
        Rent proxy = (Rent) proxyHandler.getProxy();
        proxy.rent();

        /**
         * 第二种方式
         * getProxyClass,通过构造器逐步实现
         */
        Rent rent = new Host();
        //1.0 获取代理类的类对象,主要设置相同的ClassLoader去加载目标类实现的接口Rent类
        Class<?> proxyClass = Proxy.getProxyClass(Consumer.class.getClassLoader(), Rent.class);
        //2.0 得到代理类后,就可以通过代理类的处理器句柄来得到构造器
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        //3.0 获取具体执行方法的句柄处理器,目的通过构造器传入被代理目标类对象,注入到代理类处理器句柄中进行代理调用
        ProxyHandler handler = new ProxyHandler();
        handler.setRent(rent);
        //4.0 通过构造器创建代理类对象
        Rent o = (Rent) constructor.newInstance(handler);
        o.rent();
    }
}

思考

为什么JDK动态代理为什么必须针对接口

通过Proxy生成的代理类,反编译后

public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}

由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

为何调用代理类的方法就会自动进入InvocationHandler 的 invoke()方法

因为在动态代理类的定义中,构造函数是含参的构造,参数就是我们invocationHandler 实例,而每一个被代理接口的方法都会在代理类中生成一个对应的实现方法,并在实现方法中最终调用invocationHandler 的invoke方法,这就解释了为何执行代理类的方法会自动进入到我们自定义的invocationHandler的invoke方法中,然后在我们的invoke方法中再利用jdk反射的方式去调用真正的被代理类的业务方法,而且还可以在方法的前后去加一些我们自定义的逻辑。比如切面编程AOP等。

posted @ 2020-09-18 09:48  刃牙  阅读(139)  评论(0编辑  收藏  举报