MyBatis
上期我们讲了Java的动态代理是需要有具体的实现类的,所以MyBatis的接口并没有具体的实现类啊,那是怎么进行动态代理的呢?
mapper动态代理
数据库表
create table brand
(
id int auto_increment
primary key,
brand_name varchar(20) null,
company_name varchar(20) null,
ordered int null,
description varchar(100) null,
status int null
);
声明一个POJO类
public class Brand {
private Integer id ;
private String brandName ;
private String companyName;
private Integer ordered ;
private String description ;
private Integer status ;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBrandName() {
return brandName;
}
public void setBrandName(String brandName) {
this.brandName = brandName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public Integer getOrdered() {
return ordered;
}
public void setOrdered(Integer ordered) {
this.ordered = ordered;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered=" + ordered +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
定义一个配置文件mybatis-config.xml
BrandMapper(接口)
public interface BrandMapper {
List selectAll();
}
SQL映射文件
测试用例
public class MyBatisDemo {
public static void main(String[] args) throws IOException {
//1. 加载mybatis的核心配置文件,获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取SqlSession对象,用它来执行sql
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.执行sql语句
//List brands = sqlSession.selectList("com.happyfan.mapper.BrandMapper.selectAll");
BrandMapper brandmapper = sqlSession.getMapper(BrandMapper.class);
List brands = brandmapper.selectAll();
System.out.println(brands);
//4.释放资源
sqlSession.close();
}
}
我们使用sqlSession.getMapper()方法获取BrandMapper对象,实际上这里我们是获取了BrandMapper接口的代理类,然后再由代理类执行方法。那么这个代理类是如何生成的呢?我们利用
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
走进build方法,来看看SqlSessionFactory干了些什么事情。
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return this.build((InputStream)inputStream, environment, (Properties)null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return this.build((InputStream)inputStream, (String)null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException var13) {
}
}
return var5;
}
我们可以看到在 build 方法中有一个 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties) (全局配置的解析在 XMLConfigBuilder 的 parser 方法中)
继续往 XMLConfigBuilder 里走。
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
MyBatis 通过 XML ConfigBuileder读取 mybatis-config 中的配置信息,然后将这些信息保存到 Configuration 中(输入流、环境、属性)。mapperElement(root.evalNode(“mappers”)) 该方法是对全局配置文件中mappers属性的解析,点进去。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var13) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var11) {
var13.addSuppressed(var11);
}
}
throw var13;
}
if (inputStream != null) {
inputStream.close();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
try {
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} catch (Throwable var12) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var10) {
var12.addSuppressed(var10);
}
}
throw var12;
}
if (inputStream != null) {
inputStream.close();
}
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
找到 mapperParser.parse() 方法点进去。
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
buildMapperForNamespeace() 会根据 mapper文件中的 namespeace 属性值(全限定名,就是类的全称,带包的要用.分隔开来)为接口生成动态代理对象。点击bindMapperForNamespeace。
private void bindMapperForNamespace() {
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException var4) {
}
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);
}
}
}
}
然后我们就能看到 Configuration 的 addMapper 方法和 getMapper方法(在解析配置文件时,将需要生成动态代理类的接口注册到其中)。然后 Configuration 将addMapper方法委托给MapperRegistry。
public void addMappers(String packageName, Class> superType) {
this.mapperRegistry.addMappers(packageName, superType);
}
public void addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}
public void addMapper(Class type) {
this.mapperRegistry.addMapper(type);
}
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
点击addMapper方法
public void addMapper(Class type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
我们可以看到addMapper方法内部会判断会判断当前接口是否已经注册,已近注册就会生成对应的 MapperProxyFactory。
再用Configuration 中 getMapper 方法获取对应的MapperProxyFactory,再利用 MapperProxyFactory 生成动态代理类。
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
getMapper 中有一个 mapperProxyFactory.newInstance 方法(将所有的接口重新定向到InvocationHandle ,调用它的 Invoke 方法,InvocationHandle 接口的实现类是 MapperProxy )。
protected T newInstance(MapperProxy mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
点击MapperProxy(我们能看到在Java动态代理里熟悉的 invoke 方法了)
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = 15;
private static final Constructor lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return (MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
if (m.isDefault()) {
try {
return privateLookupInMethod == null ? new DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new DefaultMethodInvoker(this.getMethodHandleJava9(method));
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
throw new RuntimeException(var4);
}
} else {
return new PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
MapperProxy 调用用接口自定义方法进行代理,根据当前的 method 生成了一个 MapperMethod 对象,点击 MapperMethod 。具体的实现方法就在 MapperMethod 的 execute 方法里了。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch (this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + "' attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
MapperMethod 类中有两个内部类,SqlCommand 和 MethodSignature 类。在 execute 方法中,用switch case 语句,用 SqlCommand 类的 getType 方法(获取 calss 对象的声明类型),判断要执行的 sql 类型,再调用 sqlSession 的增删改查方法。
MyBatis 动态代理的内部处理过程
1、找到SQL配置 ->解析 + 参数(从 invoke 方法中获取参数列表Object ->放参数的容器)
2、拼接SQL
3、执行
4、解析结果并组装值返回值
类似于object -> SQL -> result->object,那问题又来了,result结果是如何变成object并返回的呢?其实执行结果是依靠 xml 中配置的每一个 sql 的返回值类型结合 resultType 或者 resultMap 的映射机制来定义如何组装返回结果的。
以上就是 mapper 动态代理的原理过程了。