日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)

發(fā)布時間:2023/12/8 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

小傅哥 | https://bugstack.cn
沉淀、分享、成長,讓自己和他人都能有所收獲。專注于原創(chuàng)專題案例編寫,目前已完成的專題有;Netty4.x實戰(zhàn)專題案例、用Java實現(xiàn)JVM、基于JavaAgent的全鏈路監(jiān)控、手寫RPC框架、架構(gòu)設(shè)計專題案例、源碼分析等。

你用劍🗡、我用刀🔪,好的代碼都很燒,望你不吝出招!

一、前言介紹

一個知識點的學(xué)習(xí)過程基本分為;運行helloworld、熟練使用api、源碼分析、核心專家。在分析mybaits以及mybatis-spring源碼之前,我也只是簡單的使用,因為它好用。但是他是怎么做的多半是憑自己的經(jīng)驗去分析,但始終覺得這樣的感覺缺少點什么,在幾次夙興夜寐,靡有朝矣之后決定徹底的研究一下,之后在去仿照著寫一版核心功能。依次來補全自己的技術(shù)棧的空缺。在現(xiàn)在技術(shù)知識像爆炸一樣迸發(fā),而我們多半又忙于工作業(yè)務(wù)開發(fā)。就像一個不會修車的老司機,只能一腳油門,一腳剎車的奔波。車速很快,但經(jīng)不起壞,累覺不愛。好!為了解決這樣問題,也為了錢程似錦(形容錢多的想家里的棉布一樣),努力!

開動之前先慶祝下我的iPhone4s又活了,還是那么好用(嗯!有點卡);

二、以往章節(jié)

關(guān)于mybaits & spring 源碼分析以及demo功能的章節(jié)匯總,可以通過下列內(nèi)容進行系統(tǒng)的學(xué)習(xí),同時以下章節(jié)會有部分內(nèi)容涉及到demo版本的mybaits;

  • 源碼分析 | Mybatis接口沒有實現(xiàn)類為什么可以執(zhí)行增刪改查
  • 源碼分析 | 像盜墓一樣分析Spring是怎么初始化xml并注冊bean的
  • 源碼分析 | 基于jdbc實現(xiàn)一個Demo版的Mybatis

三、一碟小菜類代理

往往從最簡單的內(nèi)容才有抓手。先看一個接口到實現(xiàn)類的使用,在將這部分內(nèi)容轉(zhuǎn)換為代理類。

1. 定義一個 IUserDao 接口并實現(xiàn)這個接口類

public interface IUserDao {String queryUserInfo();}public class UserDao implements IUserDao {@Overridepublic String queryUserInfo() {return "實現(xiàn)類";}}

2. new() 方式實例化

IUserDao userDao = new UserDao(); userDao.queryUserInfo();

這是最簡單的也是最常用的使用方式,new 個對象。

3. proxy 方式實例化

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("測試結(jié)果:{}", res);
  • Proxy.newProxyInstance 代理類實例化方式,對應(yīng)傳入類的參數(shù)即可
  • ClassLoader,是這個類加載器,我們可以獲取當前線程的類加載器
  • InvocationHandler 是代理后實際操作方法執(zhí)行的內(nèi)容,在這里可以添加自己業(yè)務(wù)場景需要的邏輯,在這里我們只返回方法名

測試結(jié)果:

23:20:18.841 [main] INFO org.itstack.demo.test.ApiTest - 測試結(jié)果:你被代理了 queryUserInfoProcess finished with exit code 0

四、盛宴來自Bean工廠

在使用Spring的時候,我們會采用注冊或配置文件的方式,將我們的類交給Spring管理。例如;

<bean id="userDao" class="org.itstack.demo.UserDao" scope="singleton"/>

UserDao是接口IUserDao的實現(xiàn)類,通過上面配置,就可以實例化一個類供我們使用,但如果IUserDao沒有實現(xiàn)類或者我們希望去動態(tài)改變他的實現(xiàn)類比如掛載到別的地方(像mybaits一樣),并且是由spring bean工廠管理的,該怎么做呢?

1. FactoryBean的使用

FactoryBean 在spring起到著二當家的地位,它將近有70多個小弟(實現(xiàn)它的接口定義),那么它有三個方法;

  • T getObject() throws Exception; 返回bean實例對象
  • Class<?> getObjectType(); 返回實例類類型
  • boolean isSingleton(); 判斷是否單例,單例會放到Spring容器中單實例緩存池中

那么我們現(xiàn)在就將上面用到的代理類交給spring的FactoryBean進行管理,代碼如下;

ProxyBeanFactory.java & bean工廠實現(xiàn)類

public class ProxyBeanFactory implements FactoryBean<IUserDao> {@Overridepublic 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);}@Overridepublic Class<?> getObjectType() {return IUserDao.class;}@Overridepublic 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("測試結(jié)果:{}", res); }

測試結(jié)果:

一月 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 - 測試結(jié)果:你被代理了 queryUserInfoProcess finished with exit code 0

咋樣,神奇不!你的接口都不需要實現(xiàn)類,就被安排的明明白白的。記住這個方法FactoryBean和動態(tài)代理。

2. BeanDefinitionRegistryPostProcessor 類注冊

你是否有懷疑過你媳婦把你錢沒收了之后都存放到哪去了,為啥你每次get都那么費勁,像垃圾回收了一樣,不可達。

好嘞,媳婦那就別想了,研究下你的bean都被注冊到哪了就可以了。在spring的bean管理中,所有的bean最終都會被注冊到類DefaultListableBeanFactory中,接下來我們就主動注冊一個被我們代理了的bean。

RegisterBeanFactory.java & 注冊bean的實現(xiàn)類

public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {@Overridepublic 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());}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// left intentionally blank}}
  • 這里包含4塊主要內(nèi)容,分別是;
    • 實現(xiàn)BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry方法,獲取bean注冊對象
    • 定義bean,GenericBeanDefinition,這里主要設(shè)置了我們的代理類工廠。我們已經(jīng)測試過他獲取一個代理類
    • 創(chuàng)建bean定義處理類,BeanDefinitionHolder,這里需要的主要參數(shù);定義bean、bean名稱
    • 最后將我們自己的bean注冊到spring容器中去,registry.registerBeanDefinition()

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("測試結(jié)果:{}", res); }

測試結(jié)果:

信息: 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 - 測試結(jié)果:你被代理了 queryUserInfoProcess finished with exit code 0

納尼?是不有一種滿腦子都是騷操作的感覺,自己注冊的bean自己知道在哪了,咋回事了。

五、老板郎上主食呀(mybaits-spring)

如果通過上面的知識點;代理類、bean工廠、bean注冊,將我們一個沒有實現(xiàn)類的接口安排的明明白白,讓他執(zhí)行啥就執(zhí)行啥,那么你是否可以想到,這個沒有實現(xiàn)類的接口,可以通過我們的折騰,去調(diào)用到我們的mybaits呢!

如下圖,通過mybatis使用的配置,我們可以看到數(shù)據(jù)源DataSource交給SqlSessionFactoryBean,SqlSessionFactoryBean實例化出的SqlSessionFactory,再交給MapperScannerConfigurer。而我們要實現(xiàn)的就是MapperScannerConfigurer這部分;

1. 需要實現(xiàn)哪些核心類

為了更易理解也更易于對照,我們將實現(xiàn)mybatis-spring中的流程核心類,如下;

  • MapperFactoryBean {給每一個沒有實現(xiàn)類的接口都代理一個這樣的類,用于操作數(shù)據(jù)庫執(zhí)行crud}
  • MapperScannerConfigurer {掃描包下接口類,免去配置。這樣是上圖中核心配置類}
  • SimpleMetadataReader {這個類完全和mybaits-spring中的類一樣,為了解析class文件。如果你對類加載處理很好奇,可以閱讀我的《用java實現(xiàn)jvm虛擬機》}
  • SqlSessionFactoryBean {這個類核心內(nèi)容就一件事,將我們寫的demo版的mybaits結(jié)合進來}

在分析之前先看下我們實現(xiàn)主食是怎么食用的,如下;

<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>

2. (類介紹)SqlSessionFactoryBean

這類本身比較簡單,主要實現(xiàn)了FactoryBean, InitializingBean用于幫我們處理mybaits核心流程類的加載處理。(關(guān)于demo版的mybaits已經(jīng)在上文中提供學(xué)習(xí)鏈接)

SqlSessionFactoryBean.java

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {private String resource;private SqlSessionFactory sqlSessionFactory;@Overridepublic void afterPropertiesSet() throws Exception {try (Reader reader = Resources.getResourceAsReader(resource)) {this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);} catch (Exception e) {e.printStackTrace();}}@Overridepublic SqlSessionFactory getObject() throws Exception {return sqlSessionFactory;}@Overridepublic Class<?> getObjectType() {return sqlSessionFactory.getClass();}@Overridepublic boolean isSingleton() {return true;}public void setResource(String resource) {this.resource = resource;}}
  • 實現(xiàn)InitializingBean主要用于加載mybatis相關(guān)內(nèi)容;解析xml、構(gòu)造SqlSession、鏈接數(shù)據(jù)庫等
  • FactoryBean,這個類我們介紹過,主要三個方法;getObject()、getObjectType()、isSingleton()

3. (類介紹)MapperScannerConfigurer

這類的內(nèi)容看上去可能有點多,但是核心事情也就是將我們的dao層接口掃描、注冊

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {private String basePackage;private SqlSessionFactory sqlSessionFactory;@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {try {// classpath*:org/itstack/demo/dao/**/*.classString 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();}}@Overridepublic 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;} }
  • 類的掃描注冊,classpath*:org/itstack/demo/dao/**/*.class,解析calss文件獲取資源信息;Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
  • 遍歷Resource,這里就你的class信息,用于注冊bean。ScannedGenericBeanDefinition
  • 這里有一點,bean的定義設(shè)置時候,是把beanDefinition.setBeanClass(MapperFactoryBean.class);設(shè)置進去的。同時在前面給他設(shè)置了構(gòu)造參數(shù)。(細細品味)
  • 最后執(zhí)行注冊registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

4. (類介紹)MapperFactoryBean

這個類就非常有意思了,因為你所有的dao接口類,實際就是他。他這里幫你執(zhí)行你對sql的所有操作的分發(fā)處理。為了更加簡化清晰,目前這里只實現(xiàn)了查詢部分,在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;}@Overridepublic T getObject() throws Exception {InvocationHandler handler = (proxy, method, args) -> {System.out.println("你被代理了,執(zhí)行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);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}}
  • T getObject(),中是一個java代理類的實現(xiàn),這個代理類對象會被掛到你的注入中。真正調(diào)用方法內(nèi)容時會執(zhí)行到代理類的實現(xiàn)部分,也就是“你被代理了,執(zhí)行SQL操作!”

  • InvocationHandler,代理類的實現(xiàn)部分非常簡單,主要開啟SqlSession,并通過固定的key;“org.itstack.demo.dao.IUserDao.queryUserInfoById”執(zhí)行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, updateTimeFROM userwhere id = #{id}</select></mapper>
  • 最終返回了執(zhí)行結(jié)果,關(guān)于查詢到結(jié)果信息會反射操作成對象類,這部分內(nèi)容可以遇到demo版本的mybatis

六、酒倒?jié)M走一個

好!到這一切開發(fā)內(nèi)容就完成了,測試走一個。

mybatis-config-datasource.xml & 數(shù)據(jù)源配置

<?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);@Testpublic void test_ClassPathXmlApplicationContext() {BeanFactory beanFactory = new ClassPathXmlApplicationContext("test-config.xml");IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);User user = userDao.queryUserInfoById(1L);logger.info("測試結(jié)果:{}", JSON.toJSONString(user));}}

測試結(jié)果;

一月 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] 你被代理了,執(zhí)行SQL操作!queryUserInfoById 2020-01-20 23:51:45.592 [main] INFO org.itstack.demo.SpringTest[26] - 測試結(jié)果:{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}Process finished with exit code 0

酒干熱火笑紅塵,春秋幾載年輪,不問?;厥捉允荢pring!Gun!變心!你被代理了!

七、綜上總結(jié)

  • 通過這些核心關(guān)鍵類的實現(xiàn);SqlSessionFactoryBean、MapperScannerConfigurer、SqlSessionFactoryBean,我們將spring與mybaits集合起來使用,解決了沒有實現(xiàn)類的接口怎么處理數(shù)據(jù)庫CRUD操作
  • 那么這個知識點可以用到哪里,不要只想著面試!在我們業(yè)務(wù)開發(fā)中是不會有很多其他數(shù)據(jù)源操作,比如ES、Hadoop、數(shù)據(jù)中心等等,包括自建。那么我們就可以做成一套統(tǒng)一數(shù)據(jù)源處理服務(wù),以優(yōu)化服務(wù)開發(fā)效率
  • 由于這次工程類是在itstack-demo-code-mybatis中繼續(xù)開發(fā),如果需要獲取源碼可以關(guān)注公眾號:bugstack蟲洞棧,回復(fù):源碼分析
  • 最后祝福大家在新的一年里;萬事如意、恭賀新禧、喜氣洋洋、福星高照、歡天喜地、吉祥如意、一帆風(fēng)順、萬事大吉、龍鳳呈祥、步步高升,一家瑞氣,二氣雍和,三星拱戶,四季平安,五星高照。六六大順,七星高照,八方來財,九九同心,十全十美。

八、文末驚喜

小傅哥 | 沉淀、分享、成長,讓自己和他人都能有所收獲!關(guān)注我與你一起成為努力奮斗的人

總結(jié)

以上是生活随笔為你收集整理的源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。