深入 -- 代理模式与动态代理

代理模式与动态代理

本文只讲使用、设计逻辑,略微分析,不会深入源码。

先在实际场景中使用,再去怼源码,否则,早晚也是忘记…


代理模式

先简单叙述下代理模式。

很经典的例子就是,西门庆、王婆、潘金莲了吧…

西门庆并不能直接去找潘金莲,而是通过王婆找到了潘金莲,进而发生了后面的事情。

在这里,王婆就是代理类,而潘金莲则是被代理的类。


代码先行:

创建一个汽车接口CarInter,里面两个方法,开车、按喇叭。

package com.test.proxymode.proxymode03;

/**
 * 汽车接口
 */
public interface CarInter {

    /*开车*/
    void drive();

    /*按喇叭*/
    void horn();
}

创建BaomaImpl、VolvoImpl,这是carInter接口实现类,宝马汽车、沃尔沃汽车

这里具体实现汽车该用的动作

package com.test.proxymode.proxymode03;

/**
 * 宝马-实现CarInter接口
 *
 * @author 18041052
 * @create 2018-06-25-19:48
 **/
public class BaomaImpl implements CarInter {

    @Override
    public void drive() {
        System.out.println("bao ma 开车...");
    }

    @Override
    public void horn() {
        System.out.println("bao ma 按喇叭...");
    }
}


package com.test.proxymode.proxymode03;

/**
 * Volvo-实现CarInter接口
 *
 * @author 18041052
 * @create 2018-06-25-20:03
 **/
public class VolvoImpl implements CarInter {

    @Override
    public void drive() {
        System.out.println("Volvo 开车...");
    }

    @Override
    public void horn() {
        System.out.println("Volvo 按喇叭...");
    }
}

在创建Driver类,司机,司机也实现CarInter接口,汽车能执行的动作,司机也能执行(细节不必在意)

package com.test.proxymode.proxymode03;

/**
 * 司机-代理类
 *
 * @author 18041052
 * @create 2018-06-25-19:49
 **/
public class Driver implements CarInter {

    private CarInter carInter;

    /*默认开宝马*/
    public Driver() {
        carInter = new BaomaImpl();
    }

    /*传参开其他车*/
    public Driver(CarInter carInter) {
        this.carInter = carInter;
    }

    @Override
    public void drive() {
        carInter.drive();
    }

    @Override
    public void horn() {
        carInter.horn();
    }
}

创建主方法ProxyModeApp03测试,主人的角色

package com.test.proxymode.proxymode03;

/**
 * 代理模式
 *
 * @author 18041052
 * @create 2018-06-25-19:45
 **/
public class ProxyModeApp03 {

    public static void main(String[] args) {

        //找来司机-让司机去,具体什么车,看司机挑
        Driver driver = new Driver();
        driver.drive();
        driver.horn();

        //指定Volvo汽车-让司机去
        VolvoImpl volvo = new VolvoImpl();
        Driver driver1 = new Driver(volvo);
        driver1.drive();
        driver1.horn();
    }

}

运行结果:

简单梳理一下

首先创建了一个接口(CarInter),其中定义了接口规范(汽车能做的动作)。

创建两个实现类,实现carInter,是两个品牌的汽车。

接着创建一个代理类(Driver)司机,通过司机来开车,老板不用管。

main()中第一个测试方法,老板并不关心司机开什么车,做出相应动作

第二个测试方法,老板要求司机开沃尔沃,并作出相应动作


再举一个生活中的例子,去公司面试的时候,你首先找前台mm。

告诉 前台mm 是来面试的,随机找一个技术经理来给我们面试。

也可能,是你告诉前台mm,让做Java的技术经理(而不是C++技术经理)来面试我(不要在意细节)。


动态代理

代码先行,内部原理,暂时不看,先看怎么运用。

一个接口,来定义接口规范

package com.test.proxymode.proxymode01;

/**
 * 需要动态代理的接口
 *
 * @author 18041052
 * @create 2018-06-25-13:14
 **/
public interface Car {

    void driverCar();

    void stopCar();
}
接口实现类,宝马汽车,实际被代理的对象
package com.test.proxymode.proxymode01;

/**
 * 实际代理对象
 *
 * @author 18041052
 * @create 2018-06-25-13:15
 **/
public class BaomaCarImpl implements Car {

    @Override
    public void driverCar() {
        System.out.println("bao ma 开车...");
    }

    @Override
    public void stopCar() {
        System.out.println("bao ma 停车...");
    }
}

调用处理器实现类,在代理模式中,就是司机

package com.test.proxymode.proxymode01;

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

/**
 * 调用处理器实现类
 * 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
 *
 * @author 18041052
 * @create 2018-06-25-13:16
 **/
public class CarInvocationHandlerImpl implements InvocationHandler {

    /**
     * 实际代理的对象
     */
    private Object object;

    /**
     * 有参构造方法
     * 给要代理的真是对象赋值
     */
    public CarInvocationHandlerImpl(Object object) {
        this.object = object;
    }

    /**
     * 该方法负责集中处理动态代理类上的所有方法调用。
     * 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
     *
     * @param proxy  代理类实例
     * @param method 被调用的方法对象
     * @param args   调用参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("Method : " + method);

        System.out.println("在开车/停车之前,要做点什么...");
        //当代理对象调用对象真正方法时,其会自动跳转到代理对象关联的handler对象的invoke方法来进行调用
        Object returnValue = method.invoke(object, args);
        System.out.println("在开车/停车之后,要做点什么...");

        return returnValue;
    }
}

主测试方法,相当于老板、西门庆

package com.test.proxymode.proxymode01;

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

/**
 * 动态代理
 *
 * @author 18041052
 * @create 2018-06-25-10:28
 **/
public class ProxyMode01 {

    public static void main(String[] args) {

        //代理的真实对象
        Car car = new BaomaCarImpl();

        /**
         * 相当于司机、王婆。
         *
         * InvocationHandlerImpl 实现了 InvocationHandler 接口
         * 能实现方法调用从代理类到委托类的分派转发
         * 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
         * 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
         */
        InvocationHandler handler = new CarInvocationHandlerImpl(car);

        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class[] interfaces = car.getClass().getInterfaces();
        /**
         * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
         */
        Car subject = (Car) Proxy.newProxyInstance(loader, interfaces, handler);

        subject.driverCar();

        System.out.println();

        subject.stopCar();

    }

}

输出结果:

分析下代码:

在代理模式的代码中,有一个司机的角色(Driver),它实现了CarInter接口,具有了一些能执行汽车动作的方法。

在动态代理中,变成了CarInvocationHandlerImpl类,它实现了InvocationHandler类。

它能实现方法调用从代理类(王婆)委托类(潘金莲)的分派转发。

方法调用会被转发到CarInvocationHandlerImpl类中的invoke()


上面动态代理的代码中,关键的是Proxy.newProxyInstance(loader, interfaces, handler);

该方法会根据指定的参数动态创建代理对象。

三个参数的意义:

loader : 指定代理对象的类加载器

interfaces : 代理对象需要实现的接口,可以同时指定多个接口

handler : 方法调用的实际处理者,代理对象的方法调用都会转发到这里


newProxyInstance()会返回一个实现了指定接口的代理对象。

对该对象的所有方法调用都会转发给CarInvocationHandlerImpl.invoke()方法

动态代理神奇的地方:

1. 代理对象是在程序运行时产生的,而不是编译期。

2. 对代理对象的所有接口方法都会转发到InvocationHandler.invoke()方法。

在invoke()方法中,可以加入任何逻辑,比如修改方法入参、日志功能、鉴权校验等…


CGLIB动态代理

Java动态代理是基于接口的,继续看下CGLIB动态代理

代码先行:

创建一个类,该类并没有实现任何接口

package com.test.proxymode.proxymode04;

/**
 * 该类没有实现任何接口,该类没有实现任何接口,so,无法使用Java原生动态代理
 * <p>
 * 使用CGLIB动态代理
 *
 * @author 18041052
 * @create 2018-06-25-23:21
 **/
public class HelloConcrete {

    public String sayHello(String name) {

        //假装做了一点什么...
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");

        return "hello : " + name;
    }

}

创建一个操作类,该类实现MethodInterceptor类,是CGLIB中的。

实现interecpt()方法, 所有的方法调用会被转发到这个方法的interecpt()方法中。

package com.test.proxymode.proxymode04;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 首先实现一个 MethodInterceptor接口 ,方法调用会被转发到该类的intercept()方法。
 *
 * @author 18041052
 * @create 2018-06-25-23:24
 **/
public class MethodInterceptorHandler implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("调用之前,我应该做点什么呢?");

        Object result = methodProxy.invokeSuper(obj, objects);

        System.out.println("调用之后,我应该做点什么呢?");

        return result;

    }
}

测试方法

package com.test.proxymode.proxymode04;

import net.sf.cglib.proxy.Enhancer;

/**
 * CGLIB动态代理
 *
 * @author 18041052
 * @create 2018-06-25-23:21
 **/
public class ProxyModeApp04 {

    public static void main(String[] args) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(HelloConcrete.class);
        enhancer.setCallback(new MethodInterceptorHandler());

        HelloConcrete hello = (HelloConcrete) enhancer.create();
        System.out.println("返回数据 - " + hello.sayHello("I love you!"));

    }

}
输出结果:


看到了嘛,CGLIB委托代理类并不要求实现某个接口,很简单方便。


上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,

最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,

在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;

通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。

CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

注意:CGLIB不会调用final()方法。


使用场景:

一个程序,对外提供好多接口服务分为service层、dao层。

有一天,来了一个需求,要求我们增加权限控制。

怎么做呢?

我们可以再请求进入dao层的时候,让它进入代理对象,进行鉴权校验。

(当然,你说在最前面进行鉴权也可以,不要在意细节,举个例子而已…)


JDK原生动态代理是Java原生支持的,不需要任何外部依赖,但是它只能基于接口进行代理;

CGLIB通过继承的方式进行代理,无论目标对象有没有实现接口都可以代理,但是无法处理final的情况。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值