Java 静态代理、Java动态代理、Cglib动态代理

前言

代理模式(Proxy Pattern),23种java常用设计模式之一。代理模式的定义:代理类对被代理对象提供一种代理以控制对这个对象的访问。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。个人理解:在生活中我们常常把不必要的事情丢给别人去完成,而这些不必要的部分相当于他们代替我们完成的,这就相当于是代理模式。

例如:

1、明星与经纪人:就假设在和甲方谈商演的时候,影视明星只负责决定是否去演出,而经纪人就需要先去联系甲方并和甲方商定角色和报酬,然后将结果告诉影视明星,影视明星决定是否去演出,经纪人在得到明星的答复后将结果通知甲方,走后面的流程。

2、中介与房东:在房屋租(售)的时候,租(购)房人会先找中介咨询房屋信息,如果中介有钥匙还可以带租(购)房人去看房,如果租(购)房人对房屋无购买意愿,则本次流程结束;如果租(购)房人对房屋有兴趣,则先与中介协商价格;如果中介觉得房东不会接受这个价格,则直接拒绝;如果中介觉得价格合理,则与房东沟通,中介将房东的决定告知租(购)房人,然后走接下来的流程。

应用场景

1、当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理模式。

2、当客户端对象需要访问远程主机中的对象时可以使用远程代理模式,如RPC。

3、当需要为一个对象的访问(引用)提供一些额外的操作时可以使用代理模式。

4、当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理,例如一个对象需要很长时间才能完成加载时。(待论证)

5、当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。(待论证)

Java 的代理就是客户类不再直接和委托类打交道, 而是通过一个中间层来访问, 这个中间层就是代理。为啥要这样呢, 是因为使用代理有 2 个优势:

  • 可以隐藏委托类的实现
  • 可以实现客户与委托类之间的解耦, 在不修改委托类代码的情况下能够做一些额外的处理

我们举个很常见的例子: 工厂会生产很多的玩具, 但是我们买玩具都是到商店买的, 而不是到工厂去买的, 工厂怎么生产我们并不关心, 我们只知道到商店可以买到自己想要的玩具,并且,如果我们需要送人的话商店可以把这些玩具使用礼品盒包装。这个工厂就是委托类, 商店就是代理类, 我们就是客户类。

在 Java 中我们有很多场景需要使用代理类, 比如远程 RPC 调用的时候我们就是通过代理类去实现的, 还有 Spring 的 AOP 切面中我们也是为切面生成了一个代理类等等。 代理类主要分为静态代理、JDK 动态代理和 CGLIB 动态代理,它们各有优缺点,没有最好的, 存在就是有意义的,在不同的场景下它们会有不同的用武之地。

一、静态代理

Java中的静态代理要求代理类(ProxyHello)和委托类(HelloServiceImpl)都实现同一个接口(HelloService)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一单需要修改接口,代理类和委托类都需要修改。

/**
 * <p>
 * <p>Title:HelloService.java</p >
 * <p>Description: 静态代理的委托接口</p >
 * <p>Date:2020/9/25 9:30</p >
 *
 * @author wsh
 * @version 1.0
 */
public interface HelloService {

    /**
     * 定义接口方法
     * @param userName
     */
    public void sayHello(String userName);
}

/**
 * <p>
 * <p>Title:HelloServiceImpl.java</p >
 * <p>Description: 委托类的实现</p >
 * <p>Date:2020/9/25 9:35</p >
 *
 * @author wsh
 * @version 1.0
 */
public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello(String userName) {
        System.out.println("HelloService ^_^"+userName);
    }
}

/**
 * <p>
 * <p>Title:ProxyHello.java</p >
 * <p>Description: 代理类</p >
 * <p>Date:2020/9/25 9:38</p >
 *
 * @author wsh
 * @version 1.0
 */
public class ProxyHello implements HelloService{
    private HelloService helloService = new HelloServiceImpl();

    @Override
    public void sayHello(String userName) {
        System.out.println("代理对象包装礼盒----------");
        helloService.sayHello(userName);
    }
}

/**
 * <p>
 * <p>Title:TestProxy.java</p >
 * <p>Description: 测试代理类</p >
 * <p>Date:2020/9/25 9:42</p >
 *
 * @author wsh
 * @version 1.0
 */
public class TestProxy {

    public static void main(String[] args) {
        ProxyHello proxyHello = new ProxyHello();
        proxyHello.sayHello("后端小王");
    }
}

测试结果:

二、JDK动态代理

Java中的动态代理依靠反射来实现,代理类和委托类不需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理。代理类在JVM运行时动态生成,而不是编译期就能确定。
Java动态代理主要涉及到两个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler。代理类需要实现InvocationHandler接口或者创建匿名内部类,而Proxy用于创建动态实例。

newProxyInstance方法的三个参数是ClassLoader loader、Class<?>[] interfaces和InvocationHandler h。
ClassLoader loader:类加载器,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。
Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用。
Class<?>[] interfaces:指明被代理类实现的接口,之后我们通过拼接字节码生成的类才能知道调用哪些方法。
InvocationHandler h:这是一个方法委托接口,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,
如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,
由它的invoke方法决定处理。InvocationHandler 接口内部只是一个 invoke() 方法。

invoke方法的三个参数是Object proxy、 Method method和 Object[] args。
Object proxy:代理类实例。注意:这个代理类实例不是代理接口的实现类对象,
而是JDK根据传入的接口生成一个extends Proxy implements Interface的代理类的对象。
这个对象其实就是Proxy.newProxyInstance方法生成的对象(结尾处做验证)。
Method method:被调用的方法对象。
Object[] args:传入的方法参数。

/**
 * <p>
 * <p>Title:HelloService.java</p >
 * <p>Description: 代理的委托接口</p >
 * <p>Date:2020/9/25 9:30</p >
 *
 * @author wsh
 * @version 1.0
 */
public interface HelloService {

    /**
     * 定义接口方法
     * @param userName
     */
    public void sayHello(String userName);
}

/**
 * <p>
 * <p>Title:HelloServiceImpl.java</p >
 * <p>Description: 委托类的实现</p >
 * <p>Date:2020/9/25 9:35</p >
 *
 * @author wsh
 * @version 1.0
 */
public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello(String userName) {
        System.out.println("HelloService ^_^"+userName);
    }
}

/**
 * <p>
 * <p>Title:HelloServiceDynamicProxy.java</p >
 * <p>Description: 动态代理类</p >
 * <p>Date:2020/9/25 10:12</p >
 *
 * @author wsh
 * @version 1.0
 */
public class HelloServiceDynamicProxy {
    private HelloService helloService;

    public HelloServiceDynamicProxy(HelloService helloService){
        this.helloService = helloService;
    }

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(helloService.getClass().getClassLoader(),helloService.getClass().getInterfaces(),new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("Before say Hello");
                Object ret = method.invoke(helloService,args);
                System.out.println("After say Hello");
                return ret;
            }
        });
    }
}

/**
 * <p>
 * <p>Title:HelloServieDynamicProxyTest.java</p >
 * <p>Description: 动态代理测试类</p >
 * <p>Date:2020/9/25 10:22</p >
 *
 * @author wsh
 * @version 1.0
 */
public class HelloServieDynamicProxyTest {

    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        HelloService dynamicProxy = (HelloService) new HelloServiceDynamicProxy(helloService).getProxyInstance();
        dynamicProxy.sayHello("后端小王");
    }
}

测试结果:

三、Cglib动态代理

Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作。GLib动态代理不要求被代理类实现接口,任何一个类都可以被轻松的代理(final类不能,final方法也不能)。

Cglib动态代理需要自定义实现MethodInterceptor接口,在代理对象调用方法时,会被拦截到intercept方法中。
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
Object obj:cglib生成的代理对象,对应JDK动态代理中invoke方法的Object proxy。
Method method:被调用的代理对象方法,对应JDK动态代理中invoke方法的Method method。
Object[] args:方法入参,对应JDK动态代理中invoke方法的Object[] args。
MethodProxy proxy:被调用的方法的代理方法,CGLib动态代理通过该参数的invokeSuper方法来调用被代理对象的方法。

代码实现

maven依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
/**
 * <p>
 * <p>Title:CglibHelloClass.java</p >
 * <p>Description: Cglib代理委托类</p >
 * <p>Date:2020/9/25 15:23</p >
 *
 * @author wsh
 * @version 1.0
 */
public class CglibHelloClass {

    public String sayHello(String userName){
        System.out.println("目标对象的方法已经执行了");
        return userName + "sayHello";
    }
}

/**
 * <p>
 * <p>Title:CglibInterceptor.java</p >
 * <p>Description: 用于对方法调用拦截以及回调</p >
 * <p>Date:2020/9/25 15:27</p >
 *
 * @author wsh
 * @version 1.0
 */
public class CglibInterceptor implements MethodInterceptor {
    /**
     * CGLIB 增强类对象,代理类对象是由 Enhancer 类创建的,
     * Enhancer 是 CGLIB 的字节码增强器,可以很方便的对类进行拓展
     */
    private Enhancer enhancer = new Enhancer();

    /**
     *
     * @param obj  被代理的对象
     * @param method 代理的方法
     * @param args 方法的参数
     * @param proxy CGLIB方法代理对象
     * @return  cglib生成用来代替Method对象的一个对象,使用MethodProxy比调用JDK自身的Method直接执行方法效率会有提升
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("方法调用前");
        Object o = proxy.invokeSuper(obj,args);
        return o;
    }

    /**
     * 使用动态代理创建一个代理对象
     * @param c
     * @return
     */
    public Object newProxyInstance(Class<?> c){
        /**
         * 设置产生的代理对象的父类,增强类型
         */
        enhancer.setSuperclass(c);
        /**
         * 定义代理逻辑对象为当前对象,要求当前对象实现 MethodInterceptor 接口
         */
        enhancer.setCallback(this);
        /**
         * 使用默认无参数的构造函数创建目标对象,这是一个前提,被代理的类要提供无参构造方法
         */
        return enhancer.create();
    }
}

/**
 * <p>
 * <p>Title:MainCglibProxyTest.java</p >
 * <p>Description: Cglib动态代理的测试类</p >
 * <p>Date:2020/9/25 15:36</p >
 *
 * @author wsh
 * @version 1.0
 */
public class MainCglibProxyTest {

    public static void main(String[] args) {
        CglibInterceptor interceptor = new CglibInterceptor();
        CglibHelloClass cglibHelloClass = (CglibHelloClass) interceptor.newProxyInstance(CglibHelloClass.class);
        String str = cglibHelloClass.sayHello("后端小王");
        System.out.println(str);
    }
}

测试结果:

代理总结

静态代理:是通过静态代理类实现了被代理类的接口的方式来实现的。

JDK动态代理:是通过JVM根据反射等机制动态的生成一个被代理类的接口的实现类实现的,该实现类的继承方法会调用代理类中的invoke方法。

CGLib动态代理:是使用ASM来生成被代理类的一个子类的字节码,该子类会重写父类的方法,在方法中调用代理类对象中的intercept()方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值