简介
之前分析过Spring的源码,里面涉及到很多Spring的拓展接口,具体的介绍文字在http://blog.csdn.net/lgq2626/article/details/78729368
文章中,文章中介绍了InitializingBean这个接口的是在依赖注入完成之后调用了此接口中的afterPropertiesSet()方法,具体的几个重要的接口方法的执行简单总结下:
首先大致看代码:
populateBean(beanName, mbd, instanceWrapper);//实例化
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);//处理了BeanPostProcessor实现了接口类的调用 和 init-method配置和InitializingBean实现了接口类的调用
}
在initializeBean(beanName, exposedObject, mbd)方法中,有几个重要的方法的调用,大致执行顺序为: 1.BeanPostProcessor#postProcessBeforeInitialization()方法
2.InitializingBean#afterPropertiesSet()方法
3.配置文件中init-method()方法
4.BeanPostProcessor#postProcessAfterInitialization()方法
下面开始介绍Mybitis和分析源码:
1.Mybatis在Spring中的使用
Spring和mybatis的整合的pom.xml部分内容如下:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
Spring和Mybatis整合的配置文件如下:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.study.www.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
有了以上的配置在代码中就可以这样使用mybatis了:
@autowired
private TestDao testDao;
2.Mybitis源码分析
2.1Mybatis读取配置文件并且解析配置文件(SqlSessionFactoryBean类解读)
从上面的配置文件中可以获得在Spring容器中注册了两个bean,一个是SqlSessionFactoryBean Bean,一个是MapperScannerConfigurer Bean。
我们首先分析SqlSessionFactoryBean 源码:
在buildSqlSessionFactory()方法中,
我们看到了有如下重要代码:
configuration = new Configuration();//Mybatis的大管家,所有的信息都会放在里面,包括plugins插件, environments环境...
configuration.addInterceptor(plugin);//Mybatis插件
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);//Mybatis环境
configuration.setEnvironment(environment);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();//解析xml,下面会重点分析
2.1.1 在parse()方法中,我们首先分析configurationElement方法
configurationElement(parser.evalNode("/mapper"));
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");//得到namespace
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));//处理<resultMap>这类标签,然后再MapperBuilderAssistant类的addResultMap()方法中把每个ResultMap对象加到Configuration对象中的resultMaps属性中(中间会把<resultMap>标签中的每一个子标签封装成ResultMapping对象,然后封装成ResultMap对象,最后put到Configuration对象中,id规则为:namespace+<resultMap>的id属性 )
sqlElement(context.evalNodes("/mapper/sql"));//解析文件中的<sql>标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析每条sql语句,会在2.1.2分析
} catch (Exception e) {
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
}
}
2.1.2分析buildStatementFromContext方法
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
public void parseStatementNode() {
String id = context.getStringAttribute("id");
//中间省略很多获取属性的代码...
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否是select查询
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes,
// in case if IncompleteElementException (issue #291)
List<XNode> selectKeyNodes = context.evalNodes("selectKey");
if (configuration.getDatabaseId() != null) {
parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());//解析<selectKey>标签
}
...
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//解析sql下面会在2.1.3分析
...
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver);//把MappedStatement对象装到Configuration对象的mappedStatements属性中id同样是namespace+标签id
}
2.1.3 下面分析langDriver.createSqlSource();
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
public SqlSource parseScriptNode() {
//解析动态标签比如if、foreach...每个动态标签都会有不同的handel去处理,如果动态标签嵌套动态标签的话,还会递归去调用parseDynamicTags(XNode node)方法,每一个标签封装成一个sqlNode
List<SqlNode> contents = parseDynamicTags(context);
//封装一个MixedSqlNode对象,有一个apply方法,用户解析动态sql
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
return sqlSource;
}
到这里整个configurationElement(XNode context)方法就分析完了。
2.1.4 在parse()方法中,我们在分析bindMapperForNamespace()方法
我们接着回到parse()方法中看 bindMapperForNamespace();方法,这个方法最重要的解析就是把每个Mapper对象也就是Interface接口放到了configuration对象中
knownMappers.put(type, new MapperProxyFactory(type));
其中key为接口对象的class,value为MapperProxyFactory对象 。
——–到这里,SqlSessionFactoryBean类中重要的方法基本分析完了,若遗漏某个重点,还望大神指出来—–
下面开始MapperScannerConfigurer源码
2.2Mybatis读取配置文件并且解析配置文件(MapperScannerConfigurer类解读)
我们直接看postProcessBeanDefinitionRegistry()方法的 代码块
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
2.2.1 doScan()方法解析
解析doScan我们只解析到使用类似@Autowired注解处理过的接口是什么样的对象为止,其他的就是执行时候处理的东西了
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//直接调用Spring的东西,递归扫描所有的文件,封装成beandefinition对象,并且把beanName放到DefaultListableBeanFactory容器的beanDefinitionNames属性中,这个比较简单
definition.setBeanClass(MapperFactoryBean.class);//设置beanDefinition的class
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);//把sqlSessionTemplate属性设置为SqlSessionTemplate对象
到目前为止,我们知道DefaultListableBeanFactory中beanDefinitionMap属性所有key为Mybatis扫描接口的class均为MapperFactoryBean对象,也就是说在依赖注入的时候会反射出一个MapperFactoryBean对象,但是MapperFactoryBean对象,但是MapperFactoryBean实现了FactoryBean接口,那么在IOC的时候就会调用到getObject()方法,在getObject()方法中SqlSession的属性是
SqlSessionTemplate对象(注意:SqlSessionTemplate类的构造方法中把sqlSessionProxy属性设置了代理类,这也是我纠结了好久的一个东西,最后发现在这里进行了代理)。
一直顺着getObject()方法跟进去发现最后getObject()返回的是一个对接口类进行代理的代理对象,实现InvocationHandler接口的对象为MapperProxy对象(这一点也可以从代码debug的时候发现)
到目前为止,整个@Autowired注解的类进行IOC的时候得到的对象就完全明确了,下面2.3会分析执行过程
2.2.2 扫描注册注解类解析
这里主要是解析registerAnnotationConfigProcessors()方法,在这个方法中,会注册到容器中一些直接注解的类例如:AutowiredAnnotationBeanPostProcessor类支持@Autowired注解和@value注解…,如果使用Spring的话,一般在解析<context:component-scan>
标签的时候就会解析了,这里一般不会执行到。
2.3Mybitis调用时候的执行过程源码解读
上面说到@autowired得到的一个对象就是一个接口的代理对象,并且代理类为MapperProxy,那么就从Mapperproxy类的invoke作为入口,
2.3.1 MapperProxy invoke()方法解析
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);//把接口类,方法,和Configuration封装成MapperMethod对象
return mapperMethod.execute(sqlSession, args);//下面重点分析
}
下面会以selectList查询,重点分析(我们只看类似selectList查询)mapperMethod.execute(sqlSession, args);
一直跟进去,跟到executeForMany()方法,发现
result = sqlSession.<E>selectList(command.getName(), param);
这个地方相当饶了,不了解动态代理的就别看下去了,首先来说,这个sqlSession对象是SqlSessionTemplate对象,点进去之后发现
return this.sqlSessionProxy.<E> selectList(statement, parameter);//上文分析过这个sqlSessionProxy是代理对象,代理类是SqlSessionInterceptor
我们只能看SqlSessionInterceptor的invoke方法了,
SqlSessionInterceptor#invoke方法主要分为三块:
1.得到session:
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
在getSession方法中,有一个openSession调用,这里走的是一个DefaultSqlSessionFactory()对象,因为在解析SqlSessionFactoryBean的时候就返回的是一个DefaultSqlSessionFactory对象,然后这个openSession比较重要的东西就是configuration.newExecutor(tx, execType, autoCommit);方法,这个Executor是mybatis的执行器,
executor有四种:
SimpleExecutor普通执行器,
ReuseExecutor处理预编译语句执行器
BatchExecutor批量执行器
CachingExecutor缓存执行器
这里newExecutor方法中还有一个重要的配合用户拓展的代interceptorChain.pluginAll(executor);可以实现Interceptor接口,然后封装插件,比如分页插件。这个天getSession()返回一个DefaultSqlSession对象,也就是说这次的sqlSession是DefaultSqlSession的session。到这里,就算是得到了一个sqlSession
2.处理sql并且执行sql
Object result = method.invoke(sqlSession, args);
这个invoke方法会调到DefaultSqlSession的方法,我们跟进selectList()方法,首先会从Configuration得到一个MappedStatement对象,
然后回调用一个wrapCollection();方法。
private Object wrapCollection(final Object object) {
if (object instanceof List) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("list", object);//如果是list的话封装为一个key为list,,value为object的map
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);//解析数组
return map;
}
return object;
}
然后会通过执行器CachingExecutor执行。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);//得到sqlbound
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
这个getBoundsql 解析sql有四种解析器
这里解析得到了select * from user where id = ?这种sql。
然后执行query方法,会执行到执行器SimpleExecutor.query(),也就是BaseExecutor的query方法中来,
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//会走到BaseExecutor的queryFromDatabase方法中去调用simpleexecutor的doQuery方法下面会贴出代码
}
会一直走到simpleExecutor类的doQuery方法,到这个时候熟悉的jdbc就要来了,
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);//获取一个PreparedStatementHandler对象
stmt = prepareStatement(handler, ms.getStatementLog());//获取一个jdbc连接,从连接获取一个PreparedStatement对象,并且set值,下面有贴出代码
return handler.<E>query(stmt, resultHandler);//最终会调用到PreparedStatementHandler类的query
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);//获取jdbc连接对象
stmt = handler.prepare(connection);//获取一个PreparedStatement对象
handler.parameterize(stmt);//对占位符?进行set值
return stmt;
}
PreparedStatementHandler类的query代码如下
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);//通过FastResultSetHandler的handleResultSets进行返回值装配
}
到这里整个SqlSessionInterceptor的invoke方法中的 method.invoke(sqlSession, args);就执行完了。
3.关闭session
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
到这里整个Spring源码执行流程就已经完了,中间还有好多东西没有分析到,其中包括:jdbcType和javaType的对应,解析${id}这种表达式 …等等。希望能帮到你们,如果其中有什么理解的不到位的地方,还望大神指出,共同学习,进步