Back

Java静态代理模式

代理模式

  • 代理模式基本介绍

    1. 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的**好处是:**可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能

    2. 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象

    3. 代理模式有不同的形式,主要有三种静态代理、动态代理(JDK代理、接口代理)和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,他是属于动态代理的范畴的)

    4. 代理模式示意图

      image1

静态代理模式

间接的“美”  中国人是一个含蓄的民族,讲究微妙和间接的交流方式。对象间的间接通信也同样是面向对象设计中一条重要的审美观。间接性的通信可以给出较低的耦合关系,较强的合作关系,以及微妙的结构和易于复用的设计架构。                           —-«Java与模式»

  • 代理模式的定义

    ​ 代理模式的英文叫Proxy或Surrogate。所谓代理就是指一个人或者一个机构代表另一个人或者另一个机构行动。有一些情况下,一个客户不想或者不能直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

  • 意图

    ​ 为其他对象提供一种代理以控制对这个对象的访问。

  • 代理模式的结构

    • 抽象主题角色(接口):声明了正是主题和代理主题的共同接口。这样一来任何使用真是主题的地方都可以使用代理主题。
    • 代理主题(Peoxy)角色(代理类):代理主题角色内部含有一个对真实主题角色的引用。从而可以在任何时候操作真实主体对象;代理主题角色提供一个与真实主题角色相同的接口,以便在任何时候都可以代替真实主题;控制对真实主题的引用,负责在需要的时候创建真实主题对象(或删除真实主题对象);代理主题通常在客户端调用真实主题之前或之后都要执行某个操作,而不是单纯将调用传给真实主题。
    • 真实主题角色(被代理类):定义了代理角色所代表的真实对象。
  • 静态代理

    ​ 由程序员创建或特定工具自动生成源码,也就是说在编译时就已经将接口,被代理类,代理类等确定了下来。在程序运行之前,代理类的.class文件就已经生成。

  • 静态代理简单实现

    ​ 假设现在jack要结婚,jack把他的婚礼包办给了婚礼公司,在这个案例中,我们可以知道抽象主题角色便是婚礼(结婚),然后代理主题角色是婚礼公司真实主题角色是jack

    ​ 首先,我们创建一个Marry接口。这个接口就是jack(被代理类)和婚礼公司(代理类)的公共接口,他们都有举办婚礼的行为。这样,jack的婚礼就可以让婚礼公司来代理执行。

    创建接口Marry

    interface Marry{
        void HoldWedding();
    }
    

    创建被代理类Customer

    class Customer implements Marry{
        @Override
        public void HoldWedding() {
            System.out.println("我是jack,我结婚了!");
        }
    }
    

    创建代理类WeddingCompany

    class WeddingCompany implements Marry{
    
        private Customer customer;
    
        public WeddingCompany(Customer customer){
            this.customer = customer;
        }
    
        @Override
        public void HoldWedding() {
            before();
            this.customer.HoldWedding();
            after();
        }
    
        private void before() {
            System.out.println("结婚之前,布置场地");
        }
        private void after(){
            System.out.println("结婚之后,收尾款");
        }
    }
    

    创建测试类StaticProxy

    package com.polymorphic;
    
    public class StaticProxy {
        public static void main(String[] args) {
            Marry marry = new WeddingCompany(new Customer());
            marry.HoldWedding();
        }
    }
    

    运行结果

    StaticProxyImage1

  • 总结

    • 真实对象和代理对象都要实现同一个接口
    • 代理对象要代理真实角色
    • 代理对象可以做很多真实对象做不了的事情
    • 真实对象可以专注自己的事情
  • 而在通过Runnable接口创建线程时,启动线程需要借助Thread类,这里就涉及到了静态代理模式。

    Marry marry = new WeddingCompany(new Customer());
    marry.HoldWedding();
    //如果我们把上述代码简写一下
    new WeddingCompany(new Customer()).HoldWedding();
    //对照Runnable接口创建线程时,启动线程的方法
    new Thread(new TestThread()).start();
    

    可以发现上面代码块的第四行与第六行有异曲同声的美妙!

  • 代理对象的优点与缺点

    • 优点:在不修改目标对象的功能的前提下,能通过代理对象对目标功能扩展
    • 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。一旦接口增加方法,目标对象和代理对象都需要维护

动态代理模式

动态代理的作用非常大,在很多底层框架中都会用得到,比如struts,Spring等都用到了动态代理,它的作用很简单,就是将你要使用的类,重新生成一个子类或本类,这样框架就可以利用这个新生成的类做一些事情,比如在该类的方法前后加一些代码。。 这样的话,你想像一下,你是不是不用修改任何已经编写好的代码,只要使用代理就可以灵活的加入任何东西,将来不喜欢了,不用也不会影响原来的代码。

  • 动态代理模式的基本介绍

    1. 代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
    2. 代理对象的生产,是利用JDK的API,动态的在内存中构建代理对象
    3. 动态代理也叫做:JDK代理、接口代理
  • 动态代理模式原理图

    image2

    getProxyInstance():

    1. 根据传入的对象(TeacherDao)即目标对象;
    2. 利用反射机制,返回一个代理对象;
    3. 然后通过代理对象,调用目标对象的方法
  • JDK中生成动态代理对象的API

    1. 代理类所在包:java.lang.refelect.Proxy

    2. JDK 实现代理只需要使用 newProxyInstance 方法,但是该方法需要接收三个参数,完整的写法是:

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

      上述方法中

      1. ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
      2. Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型
      3. InvocationHandler h:事件处理,执行目标对象方法时,会触发事件处理器的方法,会把当前执行的目标对象方法作为参数传入
  • 动态代理模式应用实例

    代码演示:

    创建接口ITeacherDao

    package com.proxy.dynamic;
    
    /**
     * @Author: minster
     * @Date: 2021/9/23 16:43
     */
    public interface ITeacherDao {
        void teach();
        void say(String name);
    }
    

    创建接口实现类TeacherDao

    package com.proxy.dynamic;
    
    /**
     * @Author: minster
     * @Date: 2021/9/23 16:45
     */
    public class TeacherDao implements ITeacherDao{
        @Override
        public void teach() {
            System.out.println("teacher 上课了!");
        }
    
        @Override
        public void say(String name) {
            System.out.println("hello " + name);
        }
    }
    

    创建代理工厂类ProxyFactory

    package com.proxy.dynamic;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @Author: minster
     * @Date: 2021/9/23 16:45
     */
    public class ProxyFactory {
        //维护一个目标对象target
        private Object target;
        //构造器:对target进行初始化
        ProxyFactory(Object target){
            this.target = target;
        }
        //给目标对象生成一个代理对象
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("JDK代理开始了");
                            //反射机制调用目标对象的方法
                            Object invoke = method.invoke(target,args);
                            System.out.println("JDK代理提交了");
                            return invoke;
                        }
                    });
        }
    }
    

    被代理类Cilent

    package com.proxy.dynamic;
    
    /**
     * @Author: minster
     * @Date: 2021/9/23 16:52
     */
    public class Client {
        public static void main(String[] args) {
            //创建目标对象
            ITeacherDao target = new TeacherDao();
          //把目标对象传到代理对象里面,创建代理对象,并且把类型转为ITercherDao
            ProxyFactory proxyFactory = new ProxyFactory(target);
            ITeacherDao proxyInstance =(ITeacherDao) proxyFactory.getProxyInstance();
          //通过代理对象,调用目标对象的方法
            proxyInstance.teach();
    
            proxyInstance.say("minster");
    
        }
    }
    

    运行结果:

    image03

Cglib代理

  • 基本介绍

    1. 静态代理和 JDK 代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理-这就是 Cglib 代理
    2. Cglib 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib 代理归属到动态代理。
    3. Cglib 是一个强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现 java 接口.它广泛的被许多 AOP 的框架使用,例如 Spring AOP,实现方法拦截
    4. 在 AOP 编程中如何选择代理模式:
      • 目标对象需要实现接口,用 JDK 代理
      • 目标对象不需要实现接口,用 Cglib 代理
    5. Cglib 包的底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类
  • Cglib代理实现步骤

    1. 导入Cglib的jar包

      image04

    2. 在内存中动态构建子类,注意代理的类不能为 final,否则报错java.lang.IllegalArgumentException:

    3. 目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

  • Cglib实现类图

    image05

    ProxyFactory类主要完成了

    1. getInstance()给目标对象target创建一个代理对象
    2. 重写intecept方法,实现对被代理对象(目标对象)方法的调用
  • 代码实现

    创建被代理对象的类TeacherDao02

    package com.proxy.cglib;
    
    /**
     * @Author: minster
     * @Date: 2021/9/24 8:54
     */
    public class TeacherDao02 {
        public String name =  "jack";
        public void teach(){
            System.out.println("cglib代理 老师上课了!");
        }
        public void say(String name){
            System.out.println("hello " + name);
        }
    }
    

    创建代理类ProxyFactory02

    package com.proxy.cglib;
    
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * @Author: minster
     * @Date: 2021/9/24 8:55
     */
    public class ProxyFactory02 implements MethodInterceptor {
    	//维护一个目标对象
        private Object target;
    	//构造器,传入一个被代理对象
        public ProxyFactory02(Object target) {
            this.target = target;
        }
    
        //使用cglib的工具类返回一个代理对象(target对象的代理对象)
        public Object getProxyInstance(){
            //1. 创建工具类
            Enhancer enhancer = new Enhancer();
            //2. 设置父类
            enhancer.setSuperclass(target.getClass());
            //3. 设置回调函数
            enhancer.setCallback(this);
            //4. 创建子类对象,即代理对象
            return enhancer.create();
        }
        //重写intercept方法,会调用目标对象(被代理对象的方法)
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("Cglib代理开始了");
            Object returnVal = method.invoke(target, args);
            System.out.println("Cglib代理提交了");
            return null;
        }
    }
    

    创建Client类

    package com.proxy.cglib;
    
    /**
     * @Author: minster
     * @Date: 2021/9/24 9:12
     */
    public class Client02 {
        public static void main(String[] args) {
            //1. 创建目标对象
            TeacherDao02 teacherDao02 = new TeacherDao02();
    
            //2. 把目标对象传递给代理对象
            TeacherDao02 proxyInstance = (TeacherDao02) new ProxyFactory02(teacherDao02).getProxyInstance();
    
            proxyInstance.teach();
            System.out.println("=============");
            proxyInstance.say("minster");
        }
    }
    

    运行结果

    image06

    使用Cglib代理时,代理对象是通过Cglib包的方法拦截器MethodInterceptor中的拦截方法intercept实现的!

代理模式的几种变体

  1. 防火墙代理 内网通过代理穿透防火墙,实现对公网的访问。

  2. 缓存代理 比如:当请求图片文件等资源时,先到缓存代理取,如果取到资源则 ok,如果取不到资源,再到公网或者数据库取,然后缓存。

  3. 远程代理 远程对象的本地代表,通过它可以把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息。

  4. 同步代理

    主要使用在多线程编程中,完成多线程间同步工作同步代理:主要使用在多线程编程中,完成多线程间同步工作

  • 正向代理

    正向代理:如科学上网,隐藏客户端信息

    image07

    其实,正向与反向是对于我们的客户端来说的,我们要上网,如果它帮我们去上网,那它就是一个正向代理;如果它帮我们的对方服务器,那它就是一个反向代理。 比如上网,我们想要访问谷歌,如果访问不上,那么我们可以搭建一台正向代理服务器,去购买网上的代理服务器,我们为客户端电脑配置上代理服务器的地址,以后电脑想要访问所有的网址,都会由代理服务器帮我们去访问,访问拿到内容以后再帮我们返回。 所以我们看到的是,我们自己搭建的服务器是帮我们进行上网,这个就是正向代理。 如果我们使用了正向代理,由于我们的请求是发给我们的代理服务器,由代理服务器转给我们的整个互联网,所以互联网上看到的所有的访问的ip地址都是来源于代理服务器的ip,这就隐藏了客户端地址信息

  • 反向代理

    image08

    反向代理在我们搭建集群环境的时候非常重要。 比如有人访问我们的商城,我们的商城有我们的后台服务集群,这个集群的每一个服务器我们都可能要在内网部署,这是一个内网ip,不可能把每一个服务器的外网ip暴露给外界,这样容易引起攻击。那我们为了别人能够找到我们的内网集群,我们就可以在集群的前面前置一个服务器,这个服务器就叫反向代理。 比如我们前置一个nginx,这个nginx是拥有公网ip的,大家都可以进行访问,无论你在中国还是美国我们都可以来进行访问。但是如果我们去访问我们这个公网的服务器,它会代转给我们的服务集群。它相当于对外界屏蔽了我们整个内网服务集群的信息。

Built with Hugo
Theme Stack designed by Jimmy