Spring-Mybatis核心思想
經(jīng)過(guò)漫長(zhǎng)的學(xué)習(xí),我們總算對(duì)Spring IOC 和DI的整體流程有了一定的認(rèn)識(shí)崎淳, 可能讀者讀完之后并不覺(jué)得Spring的設(shè)計(jì)有多牛逼削锰,甚至覺(jué)得Spring的設(shè)計(jì)過(guò)于復(fù)雜,那么本章的內(nèi)容將會(huì)讓大家大開眼界官辈,震撼大家如此的想法翘瓮,但前提是對(duì)筆者的前面所提及的內(nèi)容贮折、Spring的主干一定要熟悉。
Spring如何實(shí)現(xiàn)對(duì)外拓展
在Spring源碼的第五章资盅,筆者提到了Import注解调榄,不知大家是否還有印象,如果沒(méi)有印象請(qǐng)讀者自行回顧IOC的知識(shí)呵扛,因?yàn)楣P者這里將重點(diǎn)介紹Spring提供拓展的接口ImportSelector每庆、ImportBeanDefinitionRegistrar、BeanDefinitionRegistryPostProcessor择份,當(dāng)然讀者可能會(huì)提到BeanFactoryPostProcessor扣孟、BeanPostProcessor這兩個(gè)接口烫堤,確實(shí)Spring在內(nèi)部也大量使用了這兩個(gè)接口荣赶,但筆者這里為什么沒(méi)有提出這兩個(gè)接口,我們看下面的實(shí)例
?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鸽斟,也就是說(shuō)我們只能拿到我們所需要的對(duì)象進(jìn)行修改拔创,而我所提出的ImportSelector、ImportBeanDefinitionRegistrar富蓄、BeanDefinitionRegistryPostProcessor這三個(gè)接口剩燥,他們都可以直接對(duì)工廠添加BeanDefination,我們對(duì)Spring IOC 源碼熟悉的讀者肯定知道立倍,創(chuàng)建了BeanDefination就等于創(chuàng)建了Bean灭红,我們看實(shí)例:
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 {
}
}
可以看到我們通過(guò)實(shí)現(xiàn)這些接口就可以獲取registry,這個(gè)registry能干什么呢口注,那就是注冊(cè)BeanDefination变擒,我們看下圖:
,好了現(xiàn)在我們對(duì)Spring拓展Bean的一些重要接口做了簡(jiǎn)要的分析
寝志,現(xiàn)在我們回到我們的主題娇斑。
Spring-Mybatis
首先我們思考一下Mybatis到底干了什么事情策添,無(wú)非就是通過(guò)我們創(chuàng)建的接口進(jìn)行代理,創(chuàng)建代理對(duì)象毫缆,然后通過(guò)代理對(duì)象綁定表字段和對(duì)象屬性唯竹、方法名和SQL語(yǔ)句等,然后執(zhí)行sql語(yǔ)句返回對(duì)象苦丁。了解Mybatis的核心思想后浸颓,Spring-Mybatis就很簡(jiǎn)單了,無(wú)非就是將我們的代理對(duì)象放進(jìn)Spring進(jìn)行管理旺拉,這又回到上文我所提出的Spring如何實(shí)現(xiàn)對(duì)外拓展的知識(shí)了猾愿,那么現(xiàn)在我們已經(jīng)知道先用代理的方式代理通過(guò)接口創(chuàng)建對(duì)象,然后在用Spring擴(kuò)展的技術(shù)將代理對(duì)象放進(jìn)Spring里账阻,是不是就完成了呢蒂秘?好像是這么回事,那么我們開始實(shí)操吧:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
?
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 執(zhí)行包掃描淘太,獲取所需要的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,道理很簡(jiǎn)答蒲牧,我們?cè)倏聪旅娴妮敵?/p>
我在代碼里打印了兩種獲取class的方式撇贺,可以看到是不一樣的,因此我們創(chuàng)建BD的時(shí)候傳入的參數(shù)是class com.sun.proxy.$Proxy12冰抢,如果我們改成接口形式的class
然后再次執(zhí)行松嘶,可以發(fā)現(xiàn)還是無(wú)法創(chuàng)建
因?yàn)槲覀兘唤oSpring的是接口,并沒(méi)有對(duì)該接口進(jìn)行實(shí)現(xiàn)挎扰,因此Spring并不知道如何創(chuàng)建對(duì)象翠订,所以總的來(lái)說(shuō)就是我們不能按照這種思路去將我們的代理對(duì)象交給Spring管理,那Mybatis是如何解決這種問(wèn)題的呢遵倦?不知道讀者時(shí)候了解過(guò)FactoryBean接口尽超,我們點(diǎn)開此接口
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
?
}
我們可以看到FactoryBean有三個(gè)方法,返回對(duì)象梧躺、返回類型似谁、是否是單例對(duì)象,我們重點(diǎn)關(guān)注前面兩個(gè)方法FactoryBean提供了返回對(duì)象和類型的方式掠哥,筆者結(jié)合代理的方式簡(jiǎn)單梳理一下巩踏,希望讀者能夠逐漸明白我上文提出的問(wèn)題,以及解決方案
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;
}
}
這里我們創(chuàng)建了一個(gè)實(shí)現(xiàn)了FactoryBean, InvocationHandler的類续搀,然后將 getObject() 改變成返回一個(gè)代理對(duì)象塞琼,getObjectType()返回我們的接口類型,然后我們?cè)趯?duì)MyImportBeanDefinitionRegistrar進(jìn)行改造:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
?
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) throws IllegalAccessException, InstantiationException {
// 執(zhí)行包掃描目代,獲取所需要的Mapper 這一步省略掉
BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class);
GenericBeanDefinition bd= (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
// 改變自動(dòng)裝配的模式
/*bd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);*/
registry.registerBeanDefinition("userMapper",bd);
}
}
在改造測(cè)試類
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext=
new AnnotationConfigApplicationContext(AppConfig.class);
?
UserMapper userMapper = (UserMapper) annotationConfigApplicationContext.getBean("userMapper");
userMapper.selectUserById();
}
查看執(zhí)行結(jié)果
這里我們已經(jīng)執(zhí)行了代理方法屈梁,并獲取了我們注解的內(nèi)容嗤练,這里可能讀者有一些疑問(wèn),為什么我們實(shí)現(xiàn)了FactoryBean就能夠成功在讶,筆者簡(jiǎn)單描述下吧煞抬,如果我們的一個(gè)類實(shí)現(xiàn)了FactoryBean,那么Spring調(diào)用我們的getBean()也就是我們所說(shuō)的依賴查找就不會(huì)走單例對(duì)象的普通路線,而是回調(diào)我們實(shí)現(xiàn)了FactoryBean的getObject() 构哺,getObjectType()方法革答,這樣設(shè)置什么好處呢?個(gè)人認(rèn)為這樣設(shè)置其實(shí)是為了對(duì)象的靈活性曙强,我們的對(duì)象不一定類型和Object是同一種東西残拐,就好比我們的代理對(duì)象,好了多余的就不在描述了碟嘴,我們簡(jiǎn)單看看Spring-Mybatis的源碼部分吧
Spring-Mybatis源碼
在對(duì)筆者上文有所了解后溪食,其實(shí)Spring-Mybatis源碼基本就大差不差了,但有一點(diǎn)或許會(huì)有所區(qū)別娜扇,那就是屬性設(shè)置的問(wèn)題错沃,在Spring-Mybatis源碼中不同的版本可能采用不同的方式實(shí)現(xiàn),筆者上文采用的是構(gòu)造器方式進(jìn)行屬性注入雀瓢,而Spring-Mybatis源碼也會(huì)采用set方式注入枢析,我們回到IDEA
然后我們直接進(jìn)入@MapperScan注解:
可以看到在Mybatis它使用到了我們?cè)诎咐袑懙降腀Import注解,我們跟蹤MapperScannerRegistrar這個(gè)類發(fā)現(xiàn)它實(shí)現(xiàn)了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<String> basePackages = new ArrayList<String>();
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));
}
前面都是一些準(zhǔn)備工作醒叁,我們直接看到最后一行 scanner.doScan(StringUtils.toStringArray(basePackages));點(diǎn)進(jìn)去:
@Override
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;
}
簡(jiǎn)單解釋下他的代碼思路,獲取掃描出來(lái)的beanDefinitions泊业,然后對(duì)beanDefinition屬性設(shè)置把沼,注意這個(gè)beanDefinition,其實(shí)是一個(gè)MapperFactoryBean脱吱,在上面的代碼中有這么一句代碼definition.setBeanClass(MapperFactoryBean.class);意思就是將beanDefinition的class設(shè)置為MapperFactoryBean智政,所以Mybatis對(duì)將所有的創(chuàng)建的代理對(duì)象都轉(zhuǎn)換成MapperFactoryBean,而這個(gè)MapperFactoryBean是什么呢箱蝠?我們點(diǎn)開這個(gè)類
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
它繼承了SqlSessionDaoSupport 還實(shí)現(xiàn)了FactoryBean,SqlSessionDaoSupport 就簡(jiǎn)單說(shuō)兩句垦垂,我們一直跟蹤SqlSessionDaoSupport宦搬,會(huì)找到InitializingBean,InitializingBean是干什么的可能大家比較陌生劫拗,@PostConstruct大家都不陌生吧间校,其實(shí)他們兩個(gè)都差不多的,那Mybatis為什么實(shí)現(xiàn)了它呢页慷?其實(shí)目的就是在將Mapper交給Spring后自己還有其他的操作吧憔足,比如綁定xml的sql到方法上胁附,比如數(shù)據(jù)庫(kù)和對(duì)象之前的關(guān)系映射等等就不多說(shuō)了FactoryBean大家應(yīng)該有印象吧,我已在上文對(duì)它做了一些鋪墊滓彰,至于作用就不多說(shuō)了控妻,我們繼續(xù)回到代碼可以看到這有點(diǎn)區(qū)別 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());這里是并非采用的構(gòu)造器方式屬性注入,我們可以看到整個(gè)MapperFactoryBean都沒(méi)有構(gòu)造器去傳入我們的被代理接口揭绑,那他是怎么實(shí)現(xiàn)屬性注入的呢弓候?我看可以找到一個(gè)setXX的方法
說(shuō)明我們的Mybatis是采用的set方式去實(shí)現(xiàn)屬性注入的,有人可能有疑問(wèn)了他匪,為什么不用@Autowired的方式呢菇存?大家可以思考一下,我們的需要注入的屬性是不是class類型邦蜜,如果是class類型依鸥,我們對(duì)class注入有意義嗎?就好比每一個(gè)類都是Object類型悼沈,我們卻對(duì)Object注入類型一樣毕籽,毫無(wú)意義,而且Spring在屬性注入已經(jīng)排除了這種類型井辆,所以你就算想@Autowired也沒(méi)有辦法@Autowired关筒,好了說(shuō)了那么多,回到正軌杯缺,既然我們確認(rèn)需要setXXX的方式蒸播,那么我們?cè)趺赐ㄟ^(guò)代碼的方式改變它的注入類型呢?不知大家是否還記得AUTOWIRE_MODE萍肆,自動(dòng)裝配模式袍榆,記得我在IOC的文章提到過(guò)Spring對(duì)屬性注入的模式是NO,為什么時(shí)候塘揣,Spring認(rèn)為如果你對(duì)屬性沒(méi)有加@Autowired包雀,那么就不會(huì)對(duì)屬性進(jìn)行賦值,為什么這么做亲铡,其實(shí)也很簡(jiǎn)單才写,因?yàn)镾pring并不知道你的屬性需不需賦值,好了奖蔓,我看到AbstractBeanDefinition這個(gè)類
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;
可以發(fā)現(xiàn)Spring確實(shí)默認(rèn)是 AUTOWIRE_NO;那么他還有其他幾種方式赞草,其中有AUTOWIRE_BY_TYPE,AUTOWIRE_BY_NAME 這么兩個(gè)模式吆鹤,這兩種模式都可以用來(lái)改變Spring注入的方式厨疙,改成SetXXX的方式,我們寫個(gè)實(shí)例:首先改造一下之前的MyImportBeanDefinitionRegistrar這個(gè)類
其次我們?cè)俑淖冎暗腗apperFactoryBean
然后debug一下將代碼停在setClazz上面
可以看到筆者所講的已經(jīng)得到驗(yàn)證疑务,是不是覺(jué)得Spring-Mybatis設(shè)計(jì)的十分優(yōu)雅啊沾凄,好了梗醇,對(duì)Spring-Mybatis的核心思想,筆者已經(jīng)對(duì)其描述完了撒蟀,相信真正理解到的讀者一定大有收獲叙谨,那么本章內(nèi)容就結(jié)束了,咱們下個(gè)世紀(jì)見牙肝。