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

Posted archerLuo罗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了十一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 " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    /**
     * @since 3.2.2
     */
    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(knownMappers.keySet());
    }

    /**
     * @since 3.2.2
     */
    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
            addMapper(mapperClass);
        }
    }

    /**
     * @since 3.2.2
     */
    public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
    }
}

3.1.2、MapperProxyFactory

package com.luo.ibatis.binding;

import com.luo.ibatis.session.SqlSession;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author :archer
 * @date :Created in 2021/6/29 21:39
 * @description:Mapper代理工厂
 */
public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}

3.1.3、MapperProxy

package com.luo.ibatis.binding;

import com.luo.ibatis.lang.UsesJava7;
import com.luo.ibatis.reflection.ExceptionUtil;
import com.luo.ibatis.session.SqlSession;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * @author :archer
 * @date :Created in 2021/6/30 17:15
 * @description:
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 从Object类继承的方法不做处理
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (isDefaultMethod(method)) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // 对Mapper接口中定义的方法进行封装,生成MapperMethod对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
    }

    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        Mybatis的动态代理模式

设计模式之动态代理

Java实战应用50篇-SSM框架中的设计模式:动态代理

Java实战应用50篇-SSM框架中的设计模式:动态代理

单例模式之代理模式

Mybatis源码剖析:使用了工厂模式构建者模式动态代理模式等等知识实现对Mybatis的自定义