背景

  • 有时目标对象不可直接访问,只能通过代理对象访问

  • 图示:

mybatis 01: 静态代理 + jdk动态代理-LMLPHP

  • 示例1:
    • 房东 ===> 目标对象
    • 房屋中介 ===> 代理对象
    • 你,我 ===> 客户端对象
  • 示例2:
    • 运营商(电信,移动,联通) ===> 目标对象
    • 第三方公司 ===> 代理对象
    • 开发的应用程序需要发送短信的功能(或者需要支付功能) ===> 客户端对象

代理模式的作用

  • 控制客户对目标对象的访问
  • 增强访问功能

代理模式的分类

  • 静态代理
  • 动态代理
    • JDK动态代理
    • CGLib动态代理

静态代理

特点

  • 目标对象和代理对象实现同一个业务接口
  • 目标对象必须实现接口
  • 代理对象在程序运行前就已经存在

静态代理示例与原理分析

业务背景

mybatis 01: 静态代理 + jdk动态代理-LMLPHP

分析

  • 定义业务接口:面向接口编程,定义业务
  • 目标对象实现接口:业务的核心功能到底怎么实现
  • 代理对象(扩展业务 + 核心业务)
    • 实现了目标对象所实现的接口,说明代理对象有资历进行代理
    • 对核心业务进行扩展
    • 调用目标对象实现核心业务(只能目标对象自己完成)
  • 客户:无法直接访问目标对象,要访问代理对象

代码实现

  • 面向接口编程

    • 成员变量是接口类型
    • 传入目标对象,方法的参数设计为接口
    • 调用时,接口指向实现类
  • 静态代理对象代码

    package com.example.service.impl;
    
    import com.example.service.Service;
    
    public class Agent implements Service {
    
        //定义接口对象
        public Service target;
    
        public Agent(){}
    
        //传入接口对象
        public Agent(Service target){
            this.target = target;
        }
    
        @Override
        public void sing() {
            System.out.println("协商演出时间......");
            System.out.println("协商演出地点......");
    
            //目标对象完成核心业务,接口指向实现类,调用实现类的方法
            target.sing();
    
            System.out.println("协商演出费用......");
        }
    }
    

静态代理优缺点

  • 优点:能够灵活的进行目标对象的切换
    • 适用于业务固定,目标对象可灵活切换的场景
  • 缺点:无法进行功能的灵活处理,当业务发生改变时,所有涉及到的实现类代码和代理对象代码都要改变

动态代理

JDK动态代理

特点

  • 目标对象必须实现业务接口
  • JDK代理对象不需要实现业务接口
  • JDK代理对象在程序运行前不存在,程序运行时动态的在内存中构建(根据受代理的对象动态创建)
  • JDK动态代理可以灵活的进行业务功能的切换

JDK动态代理用到的类和接口

  • 使用现有的工具类完成JDK动态代理
  • 先了解两个单词的意思
    • InvocationHandler:调用处理程序
    • invoke:调用

Method类

  • 反射时用的类,用来进行目标对象的目标方法的反射调用
  • method对象,接住我们正在调用的方法 sing(),show()
    • method == sing(),show(),即:待调用的方法
    • method.invoke() ==> 相当于手工调用目标方法 sing(),show();

InvocationHandler接口

  • 用来实现代理和业务功能,我们在调用时使用匿名内部实现
    • 匿名内部实现:new接口的同时,重写接口中的方法(相当于定义了该接口的一个实现类)

Proxy类

  • 位于:java.lang.reflect.Proxy包下

  • 有一个核心方法:Proxy.newProxyInstance(....),专门获取动态代理对象,有三个参数

    • 参数1:ClassLoader loader

      • 目标对象的类加载器
      • 目的:获取类方法等信息,毕竟底层还是要调用受代理对象所实现的方法
      • 传入:targetObj.getClass().getClassLoader();
    • 参数2:Class<?>[] interfaces

      • 目标对象实现的所有接口,类的接口可以有多个
      • 目的:获取目标对象实现的所有接口以及接口的相关信息,毕竟底层要知道目标对象都可以完成哪些业务操作
      • 传入:targetObj.getClass().getInterfaces();
    • 上面两个参数为代理对象动态的创建和调用目标对象的方法提供了数据支持,第3个参数相当于调用程序

    • 参数3:InvocationHandler

      • 实现代理功能的接口,这里代理功能包括:扩展的功能 + 核心业务功能,传入的匿名内部实现如下
      new InvocationHandler() {
          @Override
          public Object invoke(
                  Object obj,
                  //用来反射调用方法
                  Method method,
                  //待调用方法需要的参数
                  Object[] args)
                  throws Throwable {
      
              //扩展业务
              System.out.println("协商演出时间......");
              System.out.println("协商演出地点......");
      
              //核心业务,具体调用什么方法根据外层业务来反射调用对应方法
              Object res = method.invoke(target, args);
      
              //扩展业务
              System.out.println("协商演出费用......");
      
              //目标对象执行的目标方法的返回值
              return res;
          }
      }
      

JDK动态代理示例

  • 代理工厂代码

    package com.example.proxy;
    
    import com.example.service.Service;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyFactory {
        //目标对象
        Service target;
    
        public ProxyFactory(){}
    
        public ProxyFactory(Service target){
            this.target = target;
        }
    
        //返回代理对象
        public Object getAgent(){
            return Proxy.newProxyInstance(
                    //需要知道受代理对象的类信息
                    target.getClass().getClassLoader(),
                    //需要知道受代理对象实现的所有接口信息
                    target.getClass().getInterfaces(),
                    //反射调用目标对象的目标方法
                    new InvocationHandler() {
                        @Override
                        public Object invoke(
                                Object obj,
                                //用来反射调用方法
                                Method method,
                                //待调用方法需要的参数
                                Object[] args)
                                throws Throwable {
    
                            //扩展业务
                            System.out.println("协商演出时间......");
                            System.out.println("协商演出地点......");
    
                            //核心业务,具体调用什么方法根据外层业务来反射调用对应方法
                            Object res = method.invoke(target, args);
    
                            //扩展业务
                            System.out.println("协商演出费用......");
    
                            //目标对象执行的目标方法的返回值
                            return res;
                        }
                    }
            );
        }
    }
    
  • 测试代码示例

    package com.example.proxy;
    
    import com.example.service.Service;
    import com.example.service.impl.SuperStarZhou;
    import org.junit.Test;
    
    public class TestProxyFactory {
        @Test
        public void testGetProxy(){
            //确定客户需求
            ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
            //根据需求动态返回对应类型的代理对象
            Service agent = (Service) factory.getAgent();
            //依托对应类型的动态代理对象完成业务:扩展业务(动态代理对象完成) + 核心业务(目标对象完成)
            agent.sing();
        }
    
        @Test
        public void testGetProxy2(){
            ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
            Service agent = (Service) factory.getAgent();
            String res = (String) agent.show(60);
            System.out.println(res);
        }
    }
    

注意

  • 可被代理的方法应该是受代理对象实现的所有接口中的方法与其所有实体方法的交集

    • 本类中独有的方法不被代理
  • 类型的转变

     @Test
     public void testGetProxy2() {
         ProxyFactory factory = new ProxyFactory(new SuperStarZhou());
         Service agent = (Service) factory.getAgent();
         Service liu = new SuperStarLiu();
         System.out.println("类型1: " + liu.getClass());
         System.out.println("类型2: " + agent.getClass());
     }
     /*
      输出结果:
      类型1: class com.example.service.impl.SuperStarLiu
      类型2: class com.sun.proxy.$Proxy7
     */
    
08-07 21:18