Spring-Mybatis核心思想
经过漫长的学习,我们总算对Spring IOC 和DI的整体流程有了一定的认识, 可能读者读完之后并不觉得Spring的设计有多牛逼,甚至觉得Spring的设计过于复杂,那么本章的内容将会让大家大开眼界,震撼大家如此的想法,但前提是对笔者的前面所提及的内容、Spring的主干一定要熟悉。
Spring如何实现对外拓展
在Spring源码的第五章,笔者提到了Import注解,不知大家是否还有印象,如果没有印象请读者自行回顾IOC的知识,因为笔者这里将重点介绍Spring提供拓展的接口ImportSelector、ImportBeanDefinitionRegistrar、BeanDefinitionRegistryPostProcessor,当然读者可能会提到BeanFactoryPostProcessor、BeanPostProcessor这两个接口,确实Spring在内部也大量使用了这两个接口,但笔者这里为什么没有提出这两个接口,我们看下面的实例
public class UserBeanPostProcessor implements BeanFactoryPostProcessor,BeanPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
GenericBeanDefinition bd= (GenericBeanDefinition) beanFactory.getBeanDefinition("user1");
bd.getConstructorArgumentValues().addGenericArgumentValue("1","dsadasdsa");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return null;
}
}
可以看到我们只能获取BeanFactory或者BeanName,也就是说我们只能拿到我们所需要的对象进行修改,而我所提出的ImportSelector、ImportBeanDefinitionRegistrar、BeanDefinitionRegistryPostProcessor这三个接口,他们都可以直接对工厂添加BeanDefination,我们对Spring IOC 源码熟悉的读者肯定知道,创建了BeanDefination就等于创建了Bean,我们看实例:
public class
MyImportBeanDefinitionRegistrarimplements ImportBeanDefinitionRegistrar,BeanDefinitionRegistryPostProcessor {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry) {
}
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throwsBeansException {
}
@Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throwsBeansException {
}
}
可以看到我们通过实现这些接口就可以获取registry,这个registry能干什么呢,那就是注册BeanDefination,我们看下图:
,好了现在我们对Spring拓展Bean的一些重要接口做了简要的分析
,现在我们回到我们的主题。
Spring-Mybatis
首先我们思考一下Mybatis到底干了什么事情,无非就是通过我们创建的接口进行代理,创建代理对象,然后通过代理对象绑定表字段和对象属性、方法名和SQL语句等,然后执行sql语句返回对象。了解Mybatis的核心思想后,Spring-Mybatis就很简单了,无非就是将我们的代理对象放进Spring进行管理,这又回到上文我所提出的Spring如何实现对外拓展的知识了,那么现在我们已经知道先用代理的方式代理通过接口创建对象,然后在用Spring扩展的技术将代理对象放进Spring里,是不是就完成了呢?好像是这么回事,那么我们开始实操吧:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 执行包扫描,获取所需要的Mapper 这一步省略掉
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class},new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(userMapper.getClass());
GenericBeanDefinition bd= (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
registry.registerBeanDefinition("userMapper",bd);
}
}
抛出异常,可以看到代理类似乎不能直接放入Spring,道理很简答,我们再看下面的输出
我在代码里打印了两种获取class的方式,可以看到是不一样的,因此我们创建BD的时候传入的参数是class com.sun.proxy.$Proxy12,如果我们改成接口形式的class
然后再次执行,可以发现还是无法创建
因为我们交给Spring的是接口,并没有对该接口进行实现,因此Spring并不知道如何创建对象,所以总的来说就是我们不能按照这种思路去将我们的代理对象交给Spring管理,那Mybatis是如何解决这种问题的呢?不知道读者时候了解过FactoryBean接口,我们点开此接口
public interface FactoryBean {
@Nullable
T getObject() throws Exception;
@Nullable
Class> getObjectType();
default boolean isSingleton() {
return true;
}
}
我们可以看到FactoryBean有三个方法,返回对象、返回类型、是否是单例对象,我们重点关注前面两个方法FactoryBean提供了返回对象和类型的方式,笔者结合代理的方式简单梳理一下,希望读者能够逐渐明白我上文提出的问题,以及解决方案
public class MappperFactoryBean implements FactoryBean, InvocationHandler {
private Class clazz;
public MapperFactoryBean(Class clazz){
this.clazz=clazz;
}
@Override
public Object getObject() throws Exception {
Class[] clazzs=new Class[]{clazz};
return Proxy.newProxyInstance(this.getClass().getClassLoader(),clazzs,this);
}
@Override
public Class> getObjectType() {
return clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Annotation annotation=method.getAnnotation(Select.class);
System.out.println(((Select) annotation).value());
return clazz;
}
}
这里我们创建了一个实现了FactoryBean, InvocationHandler的类,然后将 getObject() 改变成返回一个代理对象,getObjectType()返回我们的接口类型,然后我们在对MyImportBeanDefinitionRegistrar进行改造:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) throws IllegalAccessException, InstantiationException {
// 执行包扫描,获取所需要的Mapper 这一步省略掉
BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class);
GenericBeanDefinition bd= (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 改变自动装配的模式
/*bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);*/
registry.registerBeanDefinition("userMapper",bd);
}
}
在改造测试类
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext=
new AnnotationConfigApplicationContext(AppConfig.class);
UserMapper userMapper = (UserMapper) annotationConfigApplicationContext.getBean("userMapper");
userMapper.selectUserById();
}
查看执行结果
这里我们已经执行了代理方法,并获取了我们注解的内容,这里可能读者有一些疑问,为什么我们实现了FactoryBean就能够成功,笔者简单描述下吧,如果我们的一个类实现了FactoryBean,那么Spring调用我们的getBean()也就是我们所说的依赖查找就不会走单例对象的普通路线,而是回调我们实现了FactoryBean的getObject() ,getObjectType()方法,这样设置什么好处呢?个人认为这样设置其实是为了对象的灵活性,我们的对象不一定类型和Object是同一种东西,就好比我们的代理对象,好了多余的就不在描述了,我们简单看看Spring-Mybatis的源码部分吧
Spring-Mybatis源码
在对笔者上文有所了解后,其实Spring-Mybatis源码基本就大差不差了,但有一点或许会有所区别,那就是属性设置的问题,在Spring-Mybatis源码中不同的版本可能采用不同的方式实现,笔者上文采用的是构造器方式进行属性注入,而Spring-Mybatis源码也会采用set方式注入,我们回到IDEA
然后我们直接进入@MapperScan注解:
可以看到在Mybatis它使用到了我们在案例中写到的@Import注解,我们跟踪MapperScannerRegistrar这个类发现它实现了ImportBeanDefinitionRegistrar,我们找到registerBeanDefinitions方法
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
if (resourceLoader != null) { // this check is needed in Spring 3.1
scanner.setResourceLoader(resourceLoader);
}
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
前面都是一些准备工作,我们直接看到最后一行 scanner.doScan(StringUtils.toStringArray(basePackages));点进去:
@Override
public Set doScan(String... basePackages) {
Set 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;
}
简单解释下他的代码思路,获取扫描出来的beanDefinitions,然后对beanDefinition属性设置,注意这个beanDefinition,其实是一个MapperFactoryBean,在上面的代码中有这么一句代码definition.setBeanClass(MapperFactoryBean.class);意思就是将beanDefinition的class设置为MapperFactoryBean,所以Mybatis对将所有的创建的代理对象都转换成MapperFactoryBean,而这个MapperFactoryBean是什么呢?我们点开这个类
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean
它继承了SqlSessionDaoSupport 还实现了FactoryBean,SqlSessionDaoSupport 就简单说两句,我们一直跟踪SqlSessionDaoSupport,
会找到InitializingBean,InitializingBean是干什么的可能大家比较陌生,@PostConstruct大家都不陌生吧,其实他们两个都差不多的,那Mybatis为什么实现了它呢?其实目的就是在将Mapper交给Spring后自己还有其他的操作吧,比如绑定xml的sql到方法上,比如数据库和对象之前的关系映射等等就不多说了FactoryBean大家应该有印象吧,我已在上文对它做了一些铺垫,至于作用就不多说了,我们继续回到代码可以看到这有点区别 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());这里是并非采用的构造器方式属性注入,我们可以看到整个MapperFactoryBean都没有构造器去传入我们的被代理接口,那他是怎么实现属性注入的呢?我看可以找到一个setXX的方法
说明我们的Mybatis是采用的set方式去实现属性注入的,有人可能有疑问了,为什么不用@Autowired的方式呢?大家可以思考一下,我们的需要注入的属性是不是class类型,如果是class类型,我们对class注入有意义吗?就好比每一个类都是Object类型,我们却对Object注入类型一样,毫无意义,而且Spring在属性注入已经排除了这种类型,所以你就算想@Autowired也没有办法@Autowired,好了说了那么多,回到正轨,既然我们确认需要setXXX的方式,那么我们怎么通过代码的方式改变它的注入类型呢?不知大家是否还记得AUTOWIRE_MODE,自动装配模式,记得我在IOC的文章提到过Spring对属性注入的模式是NO,为什么时候,Spring认为如果你对属性没有加@Autowired,那么就不会对属性进行赋值,为什么这么做,其实也很简单,因为Spring并不知道你的属性需不需赋值,好了,我看到AbstractBeanDefinition这个类
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
implements BeanDefinition, Cloneable {
/**
* Constant that indicates no external autowiring at all.
* @see #setAutowireMode
*/
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
/**
* Constant that indicates autowiring bean properties by name.
* @see #setAutowireMode
*/
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
/**
* Constant that indicates autowiring bean properties by type.
* @see #setAutowireMode
*/
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
/**
* Constant that indicates autowiring a constructor.
* @see #setAutowireMode
*/
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
@Deprecated
public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
private int autowireMode = AUTOWIRE_NO;
可以发现Spring确实默认是 AUTOWIRE_NO;那么他还有其他几种方式,其中有AUTOWIRE_BY_TYPE,AUTOWIRE_BY_NAME 这么两个模式,这两种模式都可以用来改变Spring注入的方式,改成SetXXX的方式,我们写个实例:首先改造一下之前的MyImportBeanDefinitionRegistrar这个类
其次我们再改变之前的MapperFactoryBean
然后debug一下将代码停在setClazz上面
可以看到笔者所讲的已经得到验证,是不是觉得Spring-Mybatis设计的十分优雅啊,好了,对Spring-Mybatis的核心思想,笔者已经对其描述完了,相信真正理解到的读者一定大有收获,那么本章内容就结束了,咱们下个世纪见。