美文网首页
Java 代理模式(静态代理和动态代理 源码解析)

Java 代理模式(静态代理和动态代理 源码解析)

作者: 一个头发茂密的程序员 | 来源:发表于2020-11-09 17:53 被阅读0次

    代理模式:

    1. 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增 强额外的功能操作,即扩展目标对象的功能.
      这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法


      image.png

    静态代理:

    看下面的静态代理的例子:

    普通的接口实现方式:

    package dao;
    
    /**
     * 定义代理类真实调用对外公共接口
     */
    public interface Mapper {
        void connectionJDBC();
    }
    
    package impl;
    
    import dao.Mapper;
    
    public class UserMapperImpl implements Mapper {
    
        //连接数据库方法
        @Override
        public void connectionJDBC(){
            System.out.println("建立数据库连接");
            System.out.println("进行数据库操作 CURD");
            System.out.println("关闭数据库连接");
        }
    
    }
    

    connectionJDBC方法中的数据库连接 和 关闭数据库连接属于一个通用的操作,我们没有必要在每一个方法中都去重复写数据库连接和关闭数据库连接,所以我们可以利用一个代理对象来执行数据库连接和关闭数据库连接的操作。
    更改代码:
    添加ProxyMapperImpl 类 来进行数据库连接 和 关闭数据库连接的操作

    package impl;
    
    import dao.Mapper;
    
    /**
     * 静态代理
     */
    public class ProxyMapperImpl implements Mapper{
        //被代理对象
        private UserMapperImpl userMapperImpl;
    
        public ProxyMapperImpl(UserMapperImpl userMapperImpl) {
            this.userMapperImpl = userMapperImpl;
        }
    
        @Override
        public void connectionJDBC() {
            System.out.println("建立数据库连接");
            userMapperImpl.connectionJDBC();
            System.out.println("关闭数据库连接");
        }
    }
    
    package impl;
    
    import dao.Mapper;
    
    public class UserMapperImpl implements Mapper {
    
        //连接数据库方法
        @Override
        public void connectionJDBC(){
            System.out.println("进行数据库操作 CURD");
        }
    
    }
    
    

    用main方法测试一下

    public class Test {
        public static void main(String[] args) {
            UserMapperImpl userMapperImpl = new UserMapperImpl();
            Mapper proxyMapper = new ProxyMapperImpl(userMapperImpl);
            proxyMapper.connectionJDBC();
    
        }
    }
    
    
    image.png

    这就实现了我们简单的静态代理,但是静态代理每次添加Mapper就要重新建代理类,所以静态代理的重用性不强。所以有了JDK的动态代理

    动态代理:

    运用反射机制,通过实现 InvocationHandler 接口

    package impl;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 动态代理实现
     */
    public class ProxyHandler implements InvocationHandler {
        //被代理对象
        private Object target;
    
        public ProxyHandler(Object target) {
            this.target = target;
        }
    
        public Object getNewInstance(){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开始数据库连接========");
            method.invoke(target,args);
            System.out.println("关闭数据库连接==========");
            return null;
        }
    }
    
    
    import dao.Mapper;
    import impl.ProxyHandler;
    import impl.UserMapperImpl;
    
    public class Test {
        public static void main(String[] args) {
            UserMapperImpl userMapperImpl = new UserMapperImpl();
            ProxyHandler proxyHandler = new ProxyHandler(userMapperImpl);
            Mapper mapper = (Mapper) proxyHandler.getNewInstance();
            mapper.connectionJDBC();
        }
    }
    
    

    源码解析:

    Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    方法

    image.png

    getProxyClass0 方法返回的就是代理对象生成的新类

    getProxyClass0方法:


    image.png

    默认会现在内存里去拿代理类,如果内存中没有的话,就通过ProxyClassFactory 新创建一个


    image.png

    ProxyClassFactory :


    image.png

    这里我们就可以看到我们Debug的时候里边的$Proxy的代理类名称以及其他是怎样拼接出来的,拼接后的代理类保存在内存里

    image.png

    生成class 字节码文件:


    image.png

    generateProxyClass:


    image.png
    因为accessFlags 默认为false ,不保存文件,所以我们可以通过设置参数
    sun.misc.ProxyGenerator.saveGeneratedFiles 来讲字节码 class文件进行保存 image.png

    代理对象调用代理类中的目标方法connectionJDBC()


    image.png

    生成代理类的字节码文件: 代理类中的目标方法connectionJDBC()


    image.png

    InvacationHandler invoke()方法


    image.png

    MyBatis 中的 jdk 动态代理

    我写了一个Mybatis 的动态代理实现的例子

    /**
     * 模拟 Mybatis 的做法
     */
    public class Test2 {
        public static void main(String[] args) {
            Mapper2 mapper2 = (Mapper2) Proxy.newProxyInstance(Mapper.class.getClassLoader(), new Class[]{Mapper2.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println(method.getDeclaringClass());
                    System.out.println(Object.class);
                    if (Object.class.equals(method.getDeclaringClass())) {
                        method.invoke(this,args);
                    }
                    System.out.println("执行数据库操作-----------");
                    return null;
                }
    
            });
        }
    
    }
    
    

    执行结果


    image.png

    可以看出,Mybatis mapper层接口 可以进行实现也可以不做实现。mapper接口实现的时候 就会走 有目标方法的

                    if (Object.class.equals(method.getDeclaringClass())) {
                        method.invoke(this,args);
                    }
    

    如果用xml 配置方式 或者 注解配置方式的话,就会直接执行数据库操作逻辑

    看一下Mybatis 执行数据库查询的部分的动态代理实现

     @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //判断是不是基础方法 比如toString() hashCode()等,这些方法直接调用不需要处理
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
           //调用的方法是否是默认方法,一般来说我们在Mapper接口类中的方法都是接口方法,所以这里的判断往往都是false。
          //isDefaultMethod 方法中规定必须是接口类型 Public 修饰,不被static abstract 修饰的
          } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
       //进行缓存
        final MapperMethod mapperMethod = cachedMapperMethod(method);
       //调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用
        return mapperMethod.execute(sqlSession, args);
      }
    

    这里就没有对接口进行实现,所以不去执行目标方法,直接执行数据库查询的逻辑

    相关文章

      网友评论

          本文标题:Java 代理模式(静态代理和动态代理 源码解析)

          本文链接:https://www.haomeiwen.com/subject/weglbktx.html