首先我们来看看mybatis简单的使用方法.
我们需要配置一个UserDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaoyu.dao.UserDao">
<select id="getUser" resultType="com.xiaoyu.entity.User">
select
a.id as id,
a.name as name,
from biz_user as a
where a.id =#{userId}
</select>
然后我们需要有个dao层接口,并使用spring的注解@Repository(高版本好像注解都不需要了)
@Repository
public class UserDao {
User getUser(String userId);
}
这样我们就可以使用mybatis了,但是一方面我们会乐见于其使用的方便性,另一方面也可能会疑惑为什么我们的dao层(或mapper层)只需要写好一个接口就可以了,而完全没有任何实现.mybatis是如何去实现以及执行xml文件中的sql的了.我们接下来就研究下.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring.xml" })
public class TestService {
@Autowired
private UserDao userDao;
@Test
public void testUser() {
User a = userDao.getUser("1");
a.toString();
}
}
我们写个简单的测试类并进行debug,可以看到代码会进入org.apache.ibatis.binding.MapperProxy类中调用方法.
顾名思义,这个MapperProxy是个代理类,并且实现了InvocationHandler.
public class MapperProxy<T> implements InvocationHandler, Serializable {}
其实从这里我们就可以想到,实现的原理就是用到了动态代理.也就是实际调用的是spring帮我们生成的代理对象.
好像就是这么回事?但是这样显得浅尝辄止,如何生成代理对象,以及生成了怎样的代理对象的才是我们应该知道的.
之前我一篇文章讲过spring扩展的问题.也同样适用于mybatis.我们在不深究之前可以大致想下处理流程.肯定是mybatis通过spring的扩展后,在启动的时候,spring按规则扫描mybatis的xml,并解析最后生成了一个代理对象存入spring中以供使用.所以我们来看下mybatis在spring启动的时候都做了什么吧.
在进行spring扩展的时候,我们需要创建spring.handlers文件,因此我们只需要在mybatis-spring-1.2.2.jar(当然我用的是这个版本的jar)中找到即可.
http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler
核心类已经列出来了,我们看看NamespaceHandler是怎么处理的吧.
public class NamespaceHandler extends NamespaceHandlerSupport {
/**
* {@inheritDoc}
*/
public void init() {
registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
}
}
再找到核心的解析类MapperScannerBeanDefinitionParser.
public class MapperScannerBeanDefinitionParser implements BeanDefinitionParser {
private static String ATTRIBUTE_BASE_PACKAGE = "base-package";
private static String ATTRIBUTE_ANNOTATION = "annotation";
private static String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
private static String ATTRIBUTE_NAME_GENERATOR = "name-generator";
private static String ATTRIBUTE_TEMPLATE_REF = "template-ref";
private static String ATTRIBUTE_FACTORY_REF = "factory-ref";
/**
* {@inheritDoc}
*/
public synchronized BeanDefinition parse(Element element, ParserContext parserContext) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry());
ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
XmlReaderContext readerContext = parserContext.getReaderContext();
scanner.setResourceLoader(readerContext.getResourceLoader());
try {
String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
if (StringUtils.hasText(annotationClassName)) {
@SuppressWarnings("unchecked")
Class<? extends Annotation> markerInterface = (Class<? extends Annotation>) classLoader.loadClass(annotationClassName);
scanner.setAnnotationClass(markerInterface);
}
String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
if (StringUtils.hasText(markerInterfaceClassName)) {
Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
scanner.setMarkerInterface(markerInterface);
}
String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
if (StringUtils.hasText(nameGeneratorClassName)) {
Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
BeanNameGenerator nameGenerator = BeanUtils.instantiateClass((Class<?>) nameGeneratorClass, BeanNameGenerator.class);
scanner.setBeanNameGenerator(nameGenerator);
}
} catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF);
scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName);
String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF);
scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName);
scanner.registerFilters();
String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
return null;
}
}
代码有点多,我们抓住重点,一个是开头的定义”base-package”;这个也是我们每次做新项目需要配置的(ps其实新项目每次都是copy),通过这个配置我们即告诉spring去哪里找到mapper.
然后一眼扫去,这段代码里面大致都是scanner这个变量,好像设置了一些属性之类的,到最后我们看到.
String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
这里用到了basePackage,并好像去做了写扫描(scan)的工作,应该就是去扫描我们的mapper了.
我们点进去看下,还好这是个类,并没有其他的实现ClassPathMapperScanner.
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
return beanDefinitions;
}
这里果然就是扫描bean的地方.第一行代码就是调用父类方法,按路径从basePackages扫描到指定的beanDefinitions.接下来是对这些粗糙的bean进行处理,我们可以看到设置了mapperInterface,addToConfig,sqlSessionFactory,sqlSessionTemplate,这些都是mybatis调用所需的必要条件.不过这里有个定义是我们需要关注的.
definition.setBeanClass(MapperFactoryBean.class);
setBeanClass是指定spring实例化的bean是哪一个类的class,而这里似乎不是我们的dao层的接口,而是一个指定的通用的类MapperFactoryBean,那我们实例化后的bean都是这玩意吗.
的确,我们知道spring是个bean容器,每个bean都是需要实例化后存放在spring的bean工厂的.那么,dao层从我们最上面的分析来看,其实她最后是一个cglib动态代理类.也就是说她是无法被spring通过构造函数进行实例化的.这是啥意思了,也许你会问,动态代理类,不是已经是个活生生的实例化的对象了吗.哈哈,好像是的,不过我们设置的是class,spring会在她必须要实例化的时候,才会进行实例化对象,就是通过class对象反射无参构造函数进行实例化的哦,而动态代理类是无法通过构造函数再次实例化的.
所以这就是矛盾的所在,我们需要为dao层生成动态代理,但是却无法设置bean的class.怎么办呢,spring作为一个了不起的框架已经为我们提供了方案了,就是工厂bean.我们看看上面的MapperFactoryBean.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
/**
* If addToConfig is false the mapper will not be added to MyBatis. This means
* it must have been included in mybatis-config.xml.
* <p>
* If it is true, the mapper will be added to MyBatis in the case it is not already
* registered.
* <p>
* By default addToCofig is true.
*
* @param addToConfig
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
/**
* {@inheritDoc}
*/
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
public Class<T> getObjectType() {
return this.mapperInterface;
}
/**
* {@inheritDoc}
*/
public boolean isSingleton() {
return true;
}
}
这个类实现了spring的FactoryBean,里面的核心方法是getObject(),也就是说我们在spring里面的bean最后实际使用的对象是getObject返回的也就是我们开头所说的MapperProxy动态代理类,如果你愿意的话,可以试试debug的.
概括下整个实现过程在于动态代理+工厂bean.联想下的话,其实我们常用的rpc也是一样的,rpc中consumer暴露的也是一个api,如果愿意去深入研究下的,会看到套路是一样的哦.好了,分析结束,非常nice.