代理模式-静态代理和动态代理的那些事

代理模式

代理模式用于为其他对象提供一种代理以控制对这个对象的访问,代理模式中分为代理对象和委托对象(被代理对象),就如同经纪人和艺人的关系,广告商想要某位艺人代言,不直接与艺人联系,而是通过与经纪人取得联系来获得艺人的代言。代理模式中的一个重要特征就是代理对象和委托对象必须实现同一接口,万物互联,这就好比现实中经纪人和艺人往往属于同一家公司。代理对象不提供直接服务,而是调用委托对象的服务,除了调用对象的服务,代理对象会进行一些预处理消息、消息过滤、服务事后处理等扩展服务,就好比经纪人肯定不是遇到广告商的业务请求,就直接喊艺人去进行商演或者代言,肯定进行相关的调研以及与广告商的协商、酒局等预处理,再让艺人提供商演代言等服务,服务提供完毕可能提供相应的粉丝见面会、路演等服务事后处理等。
在这里插入图片描述
代理模式有两种实现方式,一种是静态代理,另外一种是动态代理,两种方式的区别在于代理类是否在编译前就已存在。在静态代理中,代理类在进行编译前就已经存在,而在动态代理中,并没有代理类这个java文件,而是在运行时通过Java的反射机制生成代理类字节码文件,再通过类加载将代理类字节码加载进内存形成代理类Class对象,以进行使用。静态代理的一个缺陷就是代理类与接口绑定,当需要代理的种类过多时,需要编写的代理类就多,而动态生成代理类的方式解决了代理类和接口绑定的缺陷。(对类加载过程不熟悉的可以翻阅我的这篇博文《类加载过程》)

静态代理

在静态代理实现中,代理类在编译前就存在,代理类与委托类实现相同的接口,并在内部具有一个业务类的对象,当用户需要访问业务时,去访问代理类,代理类进行相应的处理然后调用业务类对象进行服务调用。静态代理将客户端与目标业务隔离,不仅对业务功能进行了拓展、保护了目标业务而且降低了系统的耦合。

package DesignPatterns.Proxy;
/*
@function  代理模式-静态代理
@details   
优点:代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
     代理对象可以扩展目标对象的功能
     代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
缺点:代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。

@author    xuxiang
@time     20-6-2
*/
//接口
 interface  demoAPI{
    public void show();
}

//业务类
 class  realWork implements demoAPI{
     @Override
    public void show(){
      System.out.println("调用业务");
     }
}
//代理类
 class Proxy implements  demoAPI{
     private demoAPI realwork;
    public Proxy(demoAPI work) {
        realwork=work;
    }
     @Override
     public void show(){
      before();
      realwork.show();
      after();
     }
     public void before(){
         System.out.println("业务调用前处理");
     }
     public void  after(){
         System.out.println("业务调用后处理");
     }
 }
public  class StaticProxy{
     public static void main(String[] args){
         demoAPI work=new realWork();
         Proxy proxy=new Proxy(work);
         proxy.show();
     }
 }

动态代理

动态代理的实现是利用Java的反射机制,在运行时根据参数生成代理类的字节码,进而进行类加载,将代理类加载进内存使用。它的优点在于灵活的生成代理类,而不会需要编写很多代理类,减少了代理类的数量,并且使得系统耦合度更低,代理逻辑几乎完全和业务逻辑无关。

Java中动态代理的实现需要使用java.lang.reflect 包中的Proxy类和InvocationHandler 接口实现,通过InvocationHandler 接口实现自己的调度处理器,调度处理器的核心是 invoke()方法,通过该方法将代理类对象执行的方法转发给业务类对象执行以此实现服务调用。例如下面代码中的dynamicProxy代理类对象执行show()方法的过程是执行了invoke()方法,并在invoke方法中调用了 demoapi业务对象的show()方法,实现了任务的转发。Proxy类用于代理类的生成,它提供了 newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法生成代理类对象,过程主要是代理类Class对象的生成,以及代理类构造函数的获取,并通过构造函数来创建代理类实例。创建实例时调用处理器对象h作为参数传入,以进行任务的转发。业务类的类加载器loader和接口信息interfaces用于代理类Class对象的生成。详细过程可以自行去学习该方法的源码。

package DesignPatterns.Proxy;
/*
@function  代理模式-动态代理   使用java.lang.reflect 包中的Proxy类和InvocationHandler 接口实现
@details   与静态代理的区别是,动态代理类是运行时利用反射机制动态产生的,生产了动态代理类字节码文件,再进行类加载,生成相应的.Class对象

步骤:(1)通过实现 InvocationHandler 接口创建自己的调用处理器;调用处理器的invoke()方法存放代理类的执行方法,也是通过该方法将任务转发给业务类执行。
     (2)通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
     (3)通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
     (4)通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
@author    xuxiang
@time     20-6-2
*/

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

//接口1
interface  DemoAPI{
    public void show();
}
//接口2
interface  DemoAPI1{
    public void display();
}
//业务类1
class  RealWork implements DemoAPI{
    @Override
    public void show(){
        System.out.println("动态代理-调用业务show");
    }
}
class  RealWork1 implements  DemoAPI1{
    @Override
    public void display(){
        System.out.println("动态代理-调用业务display");
    }}
//处理调度器  作用是将需要执行的业务方法都集中在一起执行
class MyInvocationHandler implements InvocationHandler{
    private Object object;
    public MyInvocationHandler(Object work){
        object=work;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        before();
        //obejct为业务类对象,objects为代理类对象执行的方法,这里是将任务转发给object,
        //让object对象执行objects方法,将代理类对象执行的方法转发给业务类对象执行以此实现服务调用。
        Object invoke=method.invoke(object,objects);
        after();
        return invoke;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public void before(){
        System.out.println("动态代理-业务调用前处理");
    }
    public void  after(){
        System.out.println("动态代理-业务调用后处理");
    }
}
public class DynamicProxy {
        public static void main(String[] args){
            //在ProxyGenerator.generateProxyClass函数中 saveGeneratedFiles定义如下,其指代是否保存生成的代理类class文件,默认false不保存。
            System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
            DemoAPI demoapi=new RealWork();
            InvocationHandler myInvocation=new MyInvocationHandler(demoapi);
            //该代理类是运行时通过反射机制生成代理类字节码,再通过类加载过程将字节码转换成代理类Class对象
            //InvocationHandler类的对象为代理类指定代理类执行的方法,这个方法除了业务方法,还包括扩展的方法
            DemoAPI dynamicProxy=(DemoAPI) Proxy.newProxyInstance(RealWork.class.getClassLoader(),RealWork.class.getInterfaces(),myInvocation);
            //代理类调用方法时调用的是  InvocationHandler中的invoke方法
            dynamicProxy.show();

            DemoAPI1 demoapi1=new RealWork1();
            ((MyInvocationHandler) myInvocation).setObject(demoapi1);
            DemoAPI1 dynamicProxy1=(DemoAPI1) Proxy.newProxyInstance(RealWork1.class.getClassLoader(),RealWork1.class.getInterfaces(),myInvocation);
            dynamicProxy1.display();
            System.out.println(""+dynamicProxy.getClass());
        }
}

cglib动态代理

我们常用的基于反射机制的动态代理业务类必须实现接口,那么如果不实现接口的类要动态代理怎么办呢?cglib动态代理就可解决关于类的动态代理。cglib是一个强大的、高性能、高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口,在运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。他与基于JDK反射机制的动态代理的主要区别在于他可以实现对类的代理(该类没有实现接口),他生成的代理类是业务类的子类,他重写了业务类的方法以实现代理,与JDK动态代理的区别就相当于它是覆盖业务类,而JDK动态代理类是包含了业务类。

1、引入cglib pom依赖

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2、业务类

ackage com.workit.demo.proxy;

public class CaptainAmerica2MovieImpl {

    public void play(){
        System.out.println("正在播放的电影是《美国队长2》");
    }
}

3、自定义拦截器方法

package com.workit.demo.proxy;

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

import java.lang.reflect.Method;

public class CglibProxyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        playStart();
        Object object = methodProxy.invokeSuper(o, objects);
        playEnd();
        return object;
    }

    public void playStart() {
        System.out.println("电影开始前正在播放广告");
    }

    public void playEnd() {
        System.out.println("电影结束了,接续播放广告");
    }
}

4、测试

ckage com.workit.demo.proxy;

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {
    public static void main(String[] args) {
        // //在指定目录下生成动态代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "C:\\class");
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置目标类的字节码文件
        enhancer.setSuperclass(CaptainAmerica2MovieImpl.class);
        //设置回调函数
        enhancer.setCallback(new CglibProxyInterceptor());
        //这里的creat方法就是正式创建代理类
        CaptainAmerica2MovieImpl captainAmerica2Movie = (CaptainAmerica2MovieImpl)enhancer.create();
        //调用代理类的play方法
        captainAmerica2Movie.play();
        System.out.println("cglib动态代理《美国队长2》:"+captainAmerica2Movie.getClass());
    }
}
//此cglib部分代码来自https://mp.weixin.qq.com/s/oBAAMQ1unTecSUkiIntfuw

自定义拦截器相当于JDK动态代理中的调度处理器(InvocationHandler),区别在于它不是将任务转交给业务类,而是调用父类(业务类)的该方法。

总结

代理模式的作用是对业务对象的访问进行控制,常见的应用场景如日志处理、远程代理、虚拟代理等,实现方式分为静态代理和动态代理两种,静态代理,代理类在编译前就已经存在。动态代理有jdk和cglib两种,代理类通过 Proxy.newInstance()或者ASM 生成。静态代理和动态代理的区别是在于要不要开发者自己编写代理类。动态代理可以在运行期间通过 动态生成代理类Class对象,但是它必须实现一个 InvocationHandler 或者 MethodInterceptor的实现类。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章