十一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对象.
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。
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的动态代理模式