代理模式在设计模式中还是很常见的,因为他可以动态生成一个类,所以非常灵活,接手一个需求,可以不通过修改之前的代码就完成逻辑的修改,那么代理模式就派上用场了,jdk代理模式又分动态和静态,除了jdk动态代理外还有cglib等代理,现在就来说说Mybatic中对jdk动态代理的使用。
public void test_query() {
String resource = "spring/mybatis-config-datasource.xml";
Reader reader;
try {
//1 将资源文件写入到字符流中
reader = Resources.getResourceAsReader(resource);
//2 通过SqlSessionFactoryBuilder创建SqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
User user = session.selectOne("com.didspace.queryUserInfoById", 1L);
} finally {
session.close();
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
1 将资源文件写入到字符流中
2 通过SqlSessionFactoryBuilder创建SqlSessionFactory,如果了解Mybatis的人,估计大部分都会知道SqlSessionFactory是Mybatis的核心,那下面我们就具体分析一下
new SqlSessionFactoryBuilder().build(reader); 看下这段代码的调用链路
image.png再看下SqlSessionFactory的实现
image.png从实现上看SqlSessionFactory和DefaultSqlSessionFactory有很大联系,先不管,我们继续看
new SqlSessionFactoryBuilder().build(reader)
//这个方法是我们调用的
public SqlSessionFactory build(Reader reader) {
return this.build((Reader)reader, (String)null, (Properties)null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return this.build((Reader)reader, environment, (Properties)null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return this.build((Reader)reader, (String)null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
;
}
}
return var5;
}
我们看到builer是四个的,但是是用了方法的重载,方法名称相同,但是参数不同,最终调用的是
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException var13) {
;
}
}
这个不是设计模式,但是这种写法,是很不错的,值得借鉴,然后我们看下代码
先说new XMLConfigBuilder(reader, environment, properties);
这是一个构造方法,源码中很多都是通过构造方法来初始化对象
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
这里面又套了一层,也是和上面的处理方式类似,一个构造方法又去调用另外一个构造方法,另一个构造方法如下
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
这里我们看到初始化了Configuration对象,最终我们创建了XMLConfigBuilder对象,然后我们执行下一步
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
build(parser.parse()) 方法,先执行parser.parse(),我们会调用到下面的方法
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这里调动了 mapperElement(root.evalNode("mappers")),看下代码,就是在这里将Mapperxml文件中内容放到Configuration对象中,也就是之前初始化的对象并返回
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
现在我们看看 build(parser.parse())的build方法代码
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
这里面最终我们返回了DefaultSqlSessionFactory对象,而在最开始时的类关系,我们知道DefaultSqlSessionFactory实现了SqlSessionFactory,所以也就等于返回了一个SqlSessionFactory,这时候我们的SqlSessionFactory初始化也就完成了。
那Mybatis既然使用了动态代理,那是在哪里使用了呢,回到之前提到的方法,这个方法就包含了代理类的生成以及mapper的加载
// 通过package方式自动搜索加载,生成对应的mapper代理类
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//mybatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或者继承于某个类/接口)的类,默认使用Thread.currentThread().getContextClassLoader()返回的加载器,和spring的工具类殊途同归。
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
......
我们看下调用链路
image.png从这个链路中我们看到addMapper方法,看他做了什么
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 {
// 为mapper接口创建一个MapperProxyFactory代理
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);
}
}
}
}
knownMappers.put(type, new MapperProxyFactory<T>(type)),这里就创建了代理,注意他是没有生成代理类的,他做了一个接口类和代理类的映射,MapperProxyFactory就是动态代理的核心类,看下他的内容
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);
}
注意这个newInstance方法,我们看他的参数是MapperProxy<T> 对象,看下他的源码
public class MapperProxy<T> implements InvocationHandler, Serializable {
他实现了InvocationHandler,也就是动态代理一定要实现的类,说明newInstance这个方法就是创建动态代理类的方法的,通过查看他的调用会发现,他不是在初始化容器的时候被调用,而是在查询方法执行时被调用。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从初始化时放入map中的接口代理类映射中取出代理核心类
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);
}
}
通过查看调用,我们发现是由这个方法调用,在查询时,mybatis会根据mapper.xml文件中配置的nameSpace中得到类路径找到类,然后进行动态代理。
网友评论