十一、Mybatis 中动态代理模式的应用

参考文章:

  • Java 动态代理机制详解:https://blog.csdn.net/luanlouis/article/details/24589193
  • Java 动态代理:https://www.jianshu.com/p/9bcac608c714

本文从以下几个方面介绍:

  • 1、什么是动态代理
  • 2、动态代理的常见实现
  • 3、Mybatis 中对动态代理模式的应用

1、什么是动态代理

动态代理就是,在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

代理类在程序运行期间,创建的代理对象称之为动态代理对象。这种情况下,创建的代理对象,并不是事先在Java代码中定义好的。而是在运行期间,根据我们在动态代理对象中的“指示”,动态生成的。也就是说,你想获取哪个对象的代理,动态代理就会为你动态的生成这个对象的代理对象。动态代理可以对被代理对象的方法进行功能增强。有了动态代理的技术,那么就可以在不修改方法源码的情况下,增强被代理对象的方法的功能,在方法执行前后做任何你想做的事情。

1.1、静态代理

创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

接口

public interface HelloInterface {
   
    void sayHello();
}

被代理类

public class Hello implements HelloInterface{
   
    @Override
    public void sayHello() {
   
        System.out.println("Hello world!");
    }
}

代理类

public class HelloProxy implements HelloInterface{
   
    private HelloInterface helloInterface = new Hello();
    @Override
    public void sayHello() {
   
        System.out.println("Before invoke sayHello" );
        helloInterface.sayHello();
        System.out.println("After invoke sayHello");
    }
}

演示

    public static void main(String[] args) {
   
        HelloProxy helloProxy = new HelloProxy();
        helloProxy.sayHello();
    }
    

输出:
Before invoke sayHello
Hello zhanghao!
After invoke sayHello

缺点:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

1.2、动态代理

1.2.1、class文件简介及加载

Java编译器编译好Java文件之后,产生.class 文件在磁盘中。这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码。JVM虚拟机读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息,生成对应的 Class对象.

img

class字节码文件是根据JVM虚拟机规范中规定的字节码组织规则生成的、具体class文件是怎样组织类信息的,可以参考 此博文:深入理解Java Class文件格式系列。或者是Java虚拟机规范。

下面通过一段代码演示手动加载 class文件字节码到系统内,转换成class对象,然后再实例化的过程:

定义一个 Demo 类

package samples;

public class Demo {
   
 
	public void code()
	{
   
		System.out.println("I'm a Demo,Just testing.....");
	}
}

自定义一个类加载器

package samples;
/**
 * 自定义一个类加载器,用于将字节码转换为class对象
 * @author archer
 */
public class MyClassLoader extends ClassLoader {
   
 
	public Class<?> defineMyClass( byte[] b, int off, int len) 
	{
   
		return super.defineClass(b, off, len, null);
	}
	
}

编译成Demo.class文件,在程序中读取字节码,然后转换成相应的class对象,实例化再调用方法

package com.luo.ibatis.test.classtest;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;

/**
 * @author :archer
 * @date :Created in 2021/7/14 19:45
 * @description:
 */
public class ClassTest {
   

    public static void main(String[] args) throws IOException {
   
        //读取本地的class文件内的字节码,转换成字节码数组
        InputStream input = new FileInputStream("D:\\justforfun\\customize-mybatis\\target\\test-classes\\com\\luo\\ibatis\\test\\classtest\\Demo.class");
        byte[] result = new byte[1024];

        int count = input.read(result);
        // 使用自定义的类加载器将 byte字节码数组转换为对应的class对象
        MyClassLoader loader = new MyClassLoader();
        Class clazz = loader.defineMyClass(result, 0, count);
        //测试加载是否成功,打印class 对象的名称
        System.out.println(clazz.getCanonicalName());

        //实例化一个 Demo 对象
        try {
   
            Object o = clazz.newInstance();
            //调用Programmer的code方法
            clazz.getMethod("code", null).invoke(o, null);
        } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException e) {
   
            e.printStackTrace();
        }
    }
}

结果演示

com.luo.ibatis.test.classtest.Demo
I’m a Demo,Just testing…

1.2.2、系统运行期生成二进制字节码
  • 由于 JVM 通过字节码的二进制信息加载类的,那么,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类,这样,就完成了在代码中,动态创建一个类的能力了。
  • 有很多开源框架可以完成这些功能,如ASM,Javassist。

img

2、Jdk 动态代理的常见代码实现

3、Mybatis 中对动态代理模式的应用

3.1、Mapper 接口实现类

3.1.1、MapperRegistry
  • Mapper注册器,主要用于获取Mapper接口的代理实现类

  • knownMappers 用于注册Mapper接口Class对象,和 MapperProxyFactory 对象对应关系

  • SqlSession#getMapper(Class type) 实际上调用的是 MapperRegistry#getMapper(Class type) ,

    它是从 knownMappers 获取 MapperProxyFactory 对象,调用 MapperProxyFactory#newInstance(SqlSession sqlSession)

    生成 Mapper 接口的代理实现类

  • 运用的是 Jdk代理,Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

package com.luo.ibatis.binding;

import com.luo.ibatis.builder.annotation.MapperAnnotationBuilder;
import com.luo.ibatis.io.ResolverUtil;
import com.luo.ibatis.session.Configuration;
import com.luo.ibatis.session.SqlSession;

import java.util.*;

/**
 * @author :archer
 * @date :Created in 2021/6/29 21:37
 * @description:mapper注册器
 */
public class MapperRegistry {
   

    // Configuration对象引用
    private final Configuration config;
    // 用于注册Mapper接口Class对象,和MapperProxyFactory对象对应关系
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

    public MapperRegistry(Configuration config) {
   
        this.config = config;
    }
    // 根据Mapper接口Class对象获取Mapper动态代理对象
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
   
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
   
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
   
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
   
        return knownMappers.containsKey(type);
    }
    // 根据Mapper接口Class对象,创建MapperProxyFactory对象,并注册到knownMappers属性中
    public <T> void addMapper(Class<T> type) {
   
        if (type.isInterface()) {
   
            if (hasMapper(type)) {
   
                throw new BindingException("Type
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值