再议 MyBatis 中的动态代理
先看一个静态代理的例子:
抽象接口:
package com.example.myBatisDemo.proxy;
/**
* @author Dongguabai
* @Description 抽象接口
* @Date 创建于 2021-01-26 12:46
*/
public interface ITarget {
void show();
}
目标对象(实现类):
package com.example.myBatisDemo.proxy;
/**
* @author Dongguabai
* @Description 目标对象
* @Date 创建于 2021-01-26 12:47
*/
public class TargetImpl implements ITarget{
@Override
public void show() {
System.out.println("目标对象 show .....");
}
}
代理对象(持有目标对象引用):
package com.example.myBatisDemo.proxy;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 12:47
*/
public class TargetProxy implements ITarget{
/**
* 持有目标引用
*/
private ITarget target;
public TargetProxy(ITarget target) {
this.target = target;
}
@Override
public void show() {
System.out.println("before......");
target.show();
System.out.println("after......");
}
}
简单测试一下:
package com.example.myBatisDemo.proxy;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 12:54
*/
public class Main1 {
public static void main(String[] args) {
ITarget target = new TargetImpl();
ITarget proxy = new TargetProxy(target);
proxy.show();
}
}
运行结果:
before......
目标对象 show .....
after......
JDK 的动态代理
接下来使用 JDK 动态代理实现:
package com.example.myBatisDemo.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 13:00
*/
public class JdkProxyBuilder<T> implements InvocationHandler {
private T target;
public JdkProxyBuilder(T target) {
this.target = target;
}
public T buildProxy(){
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before......");
Object result = method.invoke(target, args);
System.out.println("after......");
return result;
}
}
测试:
package com.example.myBatisDemo.proxy.jdk;
import com.example.myBatisDemo.proxy.ITarget;
import com.example.myBatisDemo.proxy.TargetImpl;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 13:02
*/
public class Main2 {
public static void main(String[] args) {
ITarget target = new TargetImpl();
JdkProxyBuilder<ITarget> proxyBuilder = new JdkProxyBuilder<>(target);
ITarget proxy = proxyBuilder.buildProxy();
proxy.show();
}
}
运行结果:
before......
目标对象 show .....
after......
可以看一下生成的代理类长啥样:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.example.myBatisDemo.proxy.ITarget;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements ITarget {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void show() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.example.myBatisDemo.proxy.ITarget").getMethod("show");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到生成的代理类 $Proxy0
实现了 ITarget
接口,实际执行 show()
方法的时候,会执行 invoke()
方法,也就是 JdkProxyBuilder
的 invoke()
方法,然后 JdkProxyBuilder
又持有目标对象的引用,通过反射执行了目标对象的方法。
JDK 的动态代理基本套路是这样:
MyBatis Mapper 中的动态代理
但是其实我们在使用 MyBatis 的时候只有 Mapper 接口,并未有实现类,所以可以猜测这不是一个“正宗”的动态代理,但是有个前提是得先看下 MyBatis 使用的是哪一种动态代理。
MyBatis 使用的哪种动态代理
其实有一种方式就是根据生成的代理类的名称来,比如我上面使用 JDK 动态代理生成的代理类的名称是:
可以看到是$Proxy
开头,从 ProxyClassFactory
中也能看出:
所以可以看下生成的 Mapper 接口代理名称:
是$Proxy
前缀开头,使用的是 JDK 动态代理。这是在网上找的一个 CGLIB 动态代理类的名称:
当然看类名这种方式不一定严谨,一般可以从依赖和官方文档中查看。
MyBatis Mapper 中的动态代理
现在我们知道 MyBatis 使用的是 JDK 的动态代理,但是一种非常规的动态代理。接下来开始进行分析,在这之前可以先想下,如果没有目标类,即接口实现类,那我们怎么使用动态代理呢。看下面这个例子:
被代理接口:
package com.example.myBatisDemo.proxy.mybatis;
import java.util.List;
/**
* @author Dongguabai
* @Description 被代理接口
* @Date 创建于 2021-01-26 13:46
*/
public interface TestMapper {
int deleteById(Long id);
List<String> selectAll();
}
代理:
package com.example.myBatisDemo.proxy.mybatis;
import org.springframework.util.Assert;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 19:01
*/
public class MapperProxy<T> implements InvocationHandler {
private Class<T> mapperInterface;
private Map<String, Function> methodCache;
public MapperProxy(Class<T> mapperInterface, Map<String, Function> methodCache) {
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("MapperProxy .invoke.....");
Function function = methodCache.get(method.getName());
Assert.notNull(function, "method not found...");
return function.apply(args);
}
@FunctionalInterface
interface Function{
Object apply(Object[] t);
}
}
代理工厂:
package com.example.myBatisDemo.proxy.mybatis;
import com.google.common.collect.Lists;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Dongguabai
* @Description 代理工厂
* @Date 创建于 2021-01-26 19:18
*/
public class MapperProxyFactory<T> {
Map<String, MapperProxy.Function> methodCache = new ConcurrentHashMap<>();
private Class<T> mapperInterface;
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
init();
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance() {
final MapperProxy<T> mapperProxy = new MapperProxy<>(mapperInterface, methodCache);
return newInstance(mapperProxy);
}
private void init() {
//todo load Xml & Annotation
methodCache.putIfAbsent("deleteById", o -> {
System.out.printf("删除 ID 为【%s】的数据\n", o);
return 1;
});
methodCache.putIfAbsent("selectAll", o -> Lists.newArrayList("a", "b", "c"));
}
}
测试:
package com.example.myBatisDemo.proxy.mybatis;
import java.util.List;
/**
* @author Dongguabai
* @Description
* @Date 创建于 2021-01-26 19:17
*/
public class Main3 {
public static void main(String[] args) {
MapperProxyFactory<TestMapper> factory = new MapperProxyFactory<>(TestMapper.class);
TestMapper mapper = factory.newInstance();
int deleteLine = mapper.deleteById(123L);
List<String> all = mapper.selectAll();
System.out.printf("deleteById->result:[%s]\n",deleteLine);
System.out.printf("selectAll->result:[%s]\n",all);
}
}
运行结果:
MapperProxy .invoke.....
删除 ID 为【123】的数据
MapperProxy .invoke.....
deleteById->result:[1]
selectAll->result:[[a, b, c]]
相当于对 TestMapper 来说与代理是这样一种关系:
不需要接口实现类是因为我这里根本就不需要反射去操作什么,这里其实只需要知道要调用哪个接口(Class)的哪个方法(Method)即可,内存中会维护一个映射关系去真正执行函数。
上面的实例也是仿照 MyBatis 写的,可以看下 MyBatis 中的 MapperProxy
:
/**
* Copyright 2009-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.binding;
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;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
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);
//判断是否是 default 方法
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//从内存中获取
final MapperMethod mapperMethod = cachedMapperMethod(method);
//具体执行SQL
return mapperMethod.execute(sqlSession, args);
}
}
总结
- MyBatis 使用了JDK 动态代理机制生成了代理类;
- MyBatis 中 Mapper 的动态代理与常规的动态代理有所不同,这里的 Mapper 接口不需要实现类,即没有对某个具体的类进行代理,而是通过代理获取对应要执行的方法信息,通过映射进而转化成真正要执行的方法;
References
- https://www.google.com/search?q=cglib%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86&sxsrf=ALeKk01qi8z8iB96VBKdKMDtPgg-mbwKYg:1611657627847&source=lnms&tbm=isch&sa=X&ved=2ahUKEwiVzKDDtLnuAhWUyosBHRrABiAQ_AUoAnoECAgQBA&biw=1440&bih=718#imgrc=HosZXOYdpbJFeM