做者:小傅哥
博客:https://bugstack.cn - 彙總系列原創專題文章
html
沉澱、分享、成長,讓本身和他人都能有所收穫!😄java
一個知識點的學習過程基本分爲;運行helloworld、熟練使用api、源碼分析、核心專家。在分析mybaits以及mybatis-spring源碼以前,我也只是簡單的使用,由於它好用。可是他是怎麼作的多半是憑本身的經驗去分析,但始終以爲這樣的感受缺乏點什麼,在幾回夙興夜寐,靡有朝矣以後決定完全的研究一下,以後在去仿照着寫一版核心功能。依次來補全本身的技術棧的空缺。在如今技術知識像爆炸同樣迸發,而咱們多半又忙於工做業務開發。就像一個不會修車的老司機,只能一腳油門,一腳剎車的奔波。車速很快,但經不起壞,累覺不愛。好!爲了解決這樣問題,也爲了錢程似錦(形容錢多的想家裏的棉布同樣),努力!mysql
開動以前先慶祝下個人iPhone4s又活了,仍是那麼好用(嗯!有點卡);面試
關於mybaits & spring 源碼分析以及demo功能的章節彙總,能夠經過下列內容進行系統的學習,同時如下章節會有部份內容涉及到demo版本的mybaits;spring
每每從最簡單的內容纔有抓手。先看一個接口到實現類的使用,在將這部份內容轉換爲代理類。sql
public interface IUserDao { String queryUserInfo(); } public class UserDao implements IUserDao { @Override public String queryUserInfo() { return "實現類"; } }
IUserDao userDao = new UserDao(); userDao.queryUserInfo();
這是最簡單的也是最經常使用的使用方式,new 個對象。數據庫
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class<?>[] classes = {IUserDao.class}; InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName(); IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler); String res = userDao.queryUserInfo(); logger.info("測試結果:{}", res);
測試結果:api
23:20:18.841 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo Process finished with exit code 0
在使用Spring的時候,咱們會採用註冊或配置文件的方式,將咱們的類交給Spring管理。例如;緩存
<bean id="userDao" class="org.itstack.demo.UserDao" scope="singleton"/>
UserDao是接口IUserDao的實現類,經過上面配置,就能夠實例化一個類供咱們使用,但若是IUserDao沒有實現類或者咱們但願去動態改變他的實現類好比掛載到別的地方(像mybaits同樣),而且是由spring bean工廠管理的,該怎麼作呢?session
FactoryBean 在spring起到着二當家的地位,它將近有70多個小弟(實現它的接口定義),那麼它有三個方法;
那麼咱們如今就將上面用到的代理類交給spring的FactoryBean進行管理,代碼以下;
ProxyBeanFactory.java & bean工廠實現類
public class ProxyBeanFactory implements FactoryBean<IUserDao> { @Override public IUserDao getObject() throws Exception { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class<?>[] classes = {IUserDao.class}; InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName(); return (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler); } @Override public Class<?> getObjectType() { return IUserDao.class; } @Override public boolean isSingleton() { return true; } }
spring-config.xml & 配置bean類信息
<bean id="userDao" class="org.itstack.demo.bean.ProxyBeanFactory"/>
ApiTest.test_IUserDao() & 單元測試
@Test public void test_IUserDao() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml"); IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class); String res = userDao.queryUserInfo(); logger.info("測試結果:{}", res); }
測試結果:
一月 20, 2020 23:43:35 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [spring-config.xml] 23:43:35.440 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo Process finished with exit code 0
咋樣,神奇不!你的接口都不須要實現類,就被安排的明明白白的。記住這個方法FactoryBean和動態代理。
你是否有懷疑過你媳婦把你錢沒收了以後都存放到哪去了,爲啥你每次get都那麼費勁,像垃圾回收了同樣,不可達。
好嘞,媳婦那就別想了,研究下你的bean都被註冊到哪了就能夠了。在spring的bean管理中,全部的bean最終都會被註冊到類DefaultListableBeanFactory中,接下來咱們就主動註冊一個被咱們代理了的bean。
RegisterBeanFactory.java & 註冊bean的實現類
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(ProxyBeanFactory.class); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao"); registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition()); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // left intentionally blank } }
spring-config.xml & 配置bean類信息
<bean id="userDao" class="org.itstack.demo.bean.RegisterBeanFactory"/>
ApiTest.test_IUserDao() & 單元測試
@Test public void test_IUserDao() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml"); IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class); String res = userDao.queryUserInfo(); logger.info("測試結果:{}", res); }
測試結果:
信息: Loading XML bean definitions from class path resource [spring-config.xml] 一月 20, 2020 23:42:29 上午 org.springframework.beans.factory.support.DefaultListableBeanFactory registerBeanDefinition 信息: Overriding bean definition for bean 'userDao' with a different definition: replacing [Generic bean: class [org.itstack.demo.bean.RegisterBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=1; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring-config.xml]] with [Generic bean: class [org.itstack.demo.bean.ProxyBeanFactory]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] 23:42:29.754 [main] INFO org.itstack.demo.test.ApiTest - 測試結果:你被代理了 queryUserInfo Process finished with exit code 0
納尼?是不有一種滿腦子都是騷操做的感受,本身註冊的bean本身知道在哪了,咋回事了。
若是經過上面的知識點;代理類、bean工廠、bean註冊,將咱們一個沒有實現類的接口安排的明明白白,讓他執行啥就執行啥,那麼你是否能夠想到,這個沒有實現類的接口,能夠經過咱們的折騰,去調用到咱們的mybaits呢!
以下圖,經過mybatis使用的配置,咱們能夠看到數據源DataSource交給SqlSessionFactoryBean,SqlSessionFactoryBean實例化出的SqlSessionFactory,再交給MapperScannerConfigurer。而咱們要實現的就是MapperScannerConfigurer這部分;
爲了更易理解也更易於對照,咱們將實現mybatis-spring中的流程核心類,以下;
在分析以前先看下咱們實現主食是怎麼食用的,以下;
<bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean"> <property name="resource" value="spring/mybatis-config-datasource.xml"/> </bean> <bean class="org.itstack.demo.like.spring.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> <!-- 給出須要掃描Dao接口包 --> <property name="basePackage" value="org.itstack.demo.dao"/> </bean>
這類自己比較簡單,主要實現了FactoryBean
SqlSessionFactoryBean.java
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean { private String resource; private SqlSessionFactory sqlSessionFactory; @Override public void afterPropertiesSet() throws Exception { try (Reader reader = Resources.getResourceAsReader(resource)) { this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } catch (Exception e) { e.printStackTrace(); } } @Override public SqlSessionFactory getObject() throws Exception { return sqlSessionFactory; } @Override public Class<?> getObjectType() { return sqlSessionFactory.getClass(); } @Override public boolean isSingleton() { return true; } public void setResource(String resource) { this.resource = resource; } }
這類的內容看上去可能有點多,可是核心事情也就是將咱們的dao層接口掃描、註冊
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor { private String basePackage; private SqlSessionFactory sqlSessionFactory; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { try { // classpath*:org/itstack/demo/dao/**/*.class String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/**/*.class"; ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resourcePatternResolver.getResources(packageSearchPath); for (Resource resource : resources) { MetadataReader metadataReader = new SimpleMetadataReader(resource, ClassUtils.getDefaultClassLoader()); ScannedGenericBeanDefinition beanDefinition = new ScannedGenericBeanDefinition(metadataReader); String beanName = Introspector.decapitalize(ClassUtils.getShortName(beanDefinition.getBeanClassName())); beanDefinition.setResource(resource); beanDefinition.setSource(resource); beanDefinition.setScope("singleton"); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory); beanDefinition.setBeanClass(MapperFactoryBean.class); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); } } catch (IOException e) { e.printStackTrace(); } } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // left intentionally blank } public void setBasePackage(String basePackage) { this.basePackage = basePackage; } public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } }
這個類就很是有意思了,由於你全部的dao接口類,實際就是他。他這裏幫你執行你對sql的全部操做的分發處理。爲了更加簡化清晰,目前這裏只實現了查詢部分,在mybatis-spring源碼中分別對select、update、insert、delete、其餘等作了操做。
public class MapperFactoryBean<T> implements FactoryBean<T> { private Class<T> mapperInterface; private SqlSessionFactory sqlSessionFactory; public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) { this.mapperInterface = mapperInterface; this.sqlSessionFactory = sqlSessionFactory; } @Override public T getObject() throws Exception { InvocationHandler handler = (proxy, method, args) -> { System.out.println("你被代理了,執行SQL操做!" + method.getName()); try { SqlSession session = sqlSessionFactory.openSession(); try { return session.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]); } finally { session.close(); } } catch (Exception e) { e.printStackTrace(); } return method.getReturnType().newInstance(); }; return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler); } @Override public Class<?> getObjectType() { return mapperInterface; } @Override public boolean isSingleton() { return true; } }
T getObject(),中是一個java代理類的實現,這個代理類對象會被掛到你的注入中。真正調用方法內容時會執行到代理類的實現部分,也就是「你被代理了,執行SQL操做!」
InvocationHandler,代理類的實現部分很是簡單,主要開啓SqlSession,並經過固定的key;「org.itstack.demo.dao.IUserDao.queryUserInfoById」執行SQL操做;
session.selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
<mapper namespace="org.itstack.demo.dao.IUserDao"> <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User"> SELECT id, name, age, createTime, updateTime FROM user where id = #{id} </select> </mapper>
最終返回了執行結果,關於查詢到結果信息會反射操做成對象類,這部份內容能夠遇到demo版本的mybatis
好!到這一切開發內容就完成了,測試走一個。
mybatis-config-datasource.xml & 數據源配置
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack_demo_ddd?useUnicode=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/User_Mapper.xml"/> <mapper resource="mapper/School_Mapper.xml"/> </mappers> </configuration>
test-config.xml & 配置xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" default-autowire="byName"> <context:component-scan base-package="org.itstack"/> <aop:aspectj-autoproxy/> <bean id="sqlSessionFactory" class="org.itstack.demo.like.spring.SqlSessionFactoryBean"> <property name="resource" value="spring/mybatis-config-datasource.xml"/> </bean> <bean class="org.itstack.demo.like.spring.MapperScannerConfigurer"> <!-- 注入sqlSessionFactory --> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> <!-- 給出須要掃描Dao接口包 --> <property name="basePackage" value="org.itstack.demo.dao"/> </bean> </beans>
SpringTest.java & 單元測試
public class SpringTest { private Logger logger = LoggerFactory.getLogger(SpringTest.class); @Test public void test_ClassPathXmlApplicationContext() { BeanFactory beanFactory = new ClassPathXmlApplicationContext("test-config.xml"); IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class); User user = userDao.queryUserInfoById(1L); logger.info("測試結果:{}", JSON.toJSONString(user)); } }
測試結果;
一月 20, 2020 23:51:43 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@30b8a058: startup date [Mon Jan 20 23:51:43 CST 2020]; root of context hierarchy 一月 20, 2020 23:51:43 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [test-config.xml] 你被代理了,執行SQL操做!queryUserInfoById 2020-01-20 23:51:45.592 [main] INFO org.itstack.demo.SpringTest[26] - 測試結果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000} Process finished with exit code 0
酒乾熱火笑紅塵,春秋幾載年輪,不問。回首皆是Spring!Gun!變心!你被代理了!