Spring FactoryBean到仿照mybatis @Mapper的实现

devtools/2025/1/15 18:18:02/

目录

    • FactoryBean原理
      • FactoryBean例子
      • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
    • mybatis mapper bean的手动实现思考
      • 复习下Jdbc传统sql查询做法
      • Mapper接口实现思路
      • 复习批量注册beanDefinition: ConfigurationClassPostProcessor
      • 自定义实现@Mapper功能
        • mapper结构
        • 定义FactoryBean
        • 编写代理对象
        • 将mapper Bean定义出来给容器,即往容器加入所有mapper的BeanDefinition
        • 测试
    • 总结

本文想说明的是: 只要懂Java反射以及Spring中FactoryBean, BeanFactoryPostProcessorBeanPostProcessor 知识点,就能自己实现spring-mybatis中的@Mapper了。

知识点简单记忆即:

  1. 代理,动态代理
  2. BeanFactoryPostProcessor 干预BeanDefinition(Bean怎么来的,依靠BeanDefinition)
  3. BeanPostProcessor干预Bean生命周期

FactoryBean原理

FactoryBean

The FactoryBean interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.

The FactoryBean interface provides three methods:

  1. Object getObject(): Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.

  2. boolean isSingleton(): Returns true if this FactoryBean returns singletons or false otherwise.

  3. Class getObjectType(): Returns the object type returned by the getObject() method or null if the type is not known in advance.

首先FactoryBean它是一个Bean,但又不仅仅是一个Bean,它是一个能生产或修饰对象生成的工厂Bean,类似于设计模式中的工厂模式和装饰器模式。它能在需要的时候生产一个对象,且不仅仅限于它自身,它能返回任何Bean的实例。

FactoryBean例子

即然是工厂,就成批量大规模生产,直接来看例子:

接口和实现类:

public interface Go {void out();
}
public class BikeGo implements Go {@Overridepublic void out() {System.out.println("go by bike");}
}
public class CarGo implements Go {@Overridepublic void out() {System.out.println("go by car");}
}

定义FactoryBean

@Service
public class MyGoFactoryBean implements FactoryBean<Go> {/*** {@link GoEnum}*/private String type;private Go getDefaultGo(){return new Go() {@Overridepublic void out() {System.out.println("just go on foot");}};}public String getType() {return type;}public void setType(String type) {this.type = type;}@Overridepublic Go getObject(){if (type == null) {return getDefaultGo();}if (type.equalsIgnoreCase(GoEnum.BIKE.getType())) {return new BikeGo();}if (type.equalsIgnoreCase(GoEnum.CAR.getType())) {return new CarGo();}return getDefaultGo();}@Overridepublic Class<Go> getObjectType() { return Go.class ; }@Overridepublic boolean isSingleton() { return false; }
}

批量产生:

emum:

public enum GoEnum {BIKE("bike"),CAR("car");String type;GoEnum(String type) {this.type = type;}public String getType() {return type;}
}

使用FactoryBean:

@Configuration
public class GoConfig {@Bean("go1")public MyGoFactoryBean type1Instance() {MyGoFactoryBean factoryBean = new MyGoFactoryBean();factoryBean.setType(GoEnum.BIKE.getType());return factoryBean;}@Bean("go2")public MyGoFactoryBean type2Instance() {MyGoFactoryBean factoryBean = new MyGoFactoryBean();factoryBean.setType(GoEnum.CAR.getType());return factoryBean;}}

测试如下:
在这里插入图片描述

springframeworkbeansfactorysupportAbstractBeanFactorydoGetBean_155">org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

"go1"获取的时候sharedInstance为MyGoFactoryBean

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

是FactoryBean类型的时候会判断并从FactoryBean获取
在这里插入图片描述

具体执行:org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean

最后执行到org.springframework.beans.factory.FactoryBean#getObject

获取到实例对象

mybatis_mapper_bean_186">mybatis mapper bean的手动实现思考

复习下Jdbc传统sql查询做法

  1. 导入jdbc驱动包
  2. 通过DriverManager注册驱动
  3. 创建连接
  4. 创建statement
  5. 执行curd sql语句
  6. 操作结果集
  7. 关闭连接

Mapper接口实现思路

一般的mapper定义如下,其实只要解析出sql, 其执行都是jdbc执行,然后返回结果。

想想每个mapper都是这样,完全可以写一个通用的代理类

public interface TbUserMapper {/*** select 映射* @param id* @return*/@Select("SELECT * FROM tb_user WHERE id = #{id}")TbUser selectUserById(int id);
}

然后这些mapper注册beanDefinition到Spring容器中,那么就自己实现了@Mapper注解了

复习批量注册beanDefinition: ConfigurationClassPostProcessor

之前看过@Configuration全注解的ConfigurationClassPostProcessor,这个beanFactoryPostProcessor就是批量扫描并注册BeanDefinition的。

比如处理@Import注解的

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);

见文章:https://blog.csdn.net/qq_26437925/article/details/144865082

自定义实现@Mapper功能

mapper结构

在这里插入图片描述

public interface TbUserMapper {/*** select 映射* @param id* @return*/@Select("SELECT * FROM t_user WHERE id = #{id}")TbUser selectUserById(int id);
}

sql执行过程要代理掉,然后所有的mapper都是这么个代理执行且要成为spring容器的bean。那么显然想到的是FactoryBean, 返回代理对象就好了

定义FactoryBean
  • 这个FactoryBean的属性就是mapper interface的class了, getObject返回就是代理对象(和本文中的测试例子传递不同type返回不同类型的Go类似)
@Service
public class DbMapperFactoryBean implements FactoryBean<Object> {Class<?> mapperInterface;public DbMapperFactoryBean() {}public DbMapperFactoryBean(Class<?> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic Object getObject() throws Exception {Object object = Proxy.newProxyInstance(DbMapperFactoryBean.class.getClassLoader(),new Class<?>[]{mapperInterface},new DbInvocationHandler());return object;}@Overridepublic Class<?> getObjectType() {return mapperInterface;}
}
编写代理对象

一个简单的Java动态代理,就是要代理mapper有@Select注解的方法,代理其执行, 除了sql和返回外,其它就是jdbc数据库的操作了

public class DbInvocationHandler implements InvocationHandler {/*** mapper 里面的方法,逻辑是一个* 1. 解析sql* 2. 执行sql(jdbc连接)*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();if ("toString".equals(methodName)) {return proxy.getClass().getName();}// 1. parse sqlSelect annotation = method.getAnnotation(Select.class);String sql = annotation.value();Object val = args[0];String parseSql = sql.replace("#{id}", String.valueOf(val));System.out.println("parse sql:" + parseSql);// 2. execute sqlreturn exeSql(parseSql);}static TbUser exeSql(String sql) {Connection conn = null;Statement stmt = null;Integer id = 0;String name = null;String sno = null;String password = null;try {//STEP 1: Register JDBC driverClass.forName("com.mysql.jdbc.Driver");//STEP 2: Open a connectionconn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root", "123456");//STEP 3: Execute a querystmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql);//STEP 4: Get resultswhile (rs.next()) {id = Integer.valueOf(rs.getString("id"));sno = rs.getString("sno");name = rs.getString("name");password = rs.getString("password");break;}rs.close();} catch (Exception e) {}//end tryreturn new TbUser(id, sno, name, password);}
}
将mapper Bean定义出来给容器,即往容器加入所有mapper的BeanDefinition

借鉴ConfigurationClassPostProcessor加载ImportBeanDefinitionRegistrar的做法,实现如下

public class DbImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// TODO 这里可以利用反射,for循环mapper package下的所有Mapper类,然后添加所有String[] mappers = {"com.aop.test.mymapper.mapper.TbUserMapper","com.aop.test.mymapper.mapper.TbUser2Mapper"};for (String mapperInterface : mappers) {// bean类型是个FactoryBean, 即 DbMapperFactoryBean, 有一个mapperInterface属性BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(DbMapperFactoryBean.class);AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(mapperInterface);String beanName = convertToVariableName(mapperInterface);registry.registerBeanDefinition(beanName, beanDefinition);}}private String convertToVariableName(String className) {// 将类名转换为单词数组String[] words = className.split("\\.");// 获取最后一个单词,即Mapper类名String mapperName = words[words.length - 1];// 将Mapper类名转换为驼峰式命名StringBuilder sb = new StringBuilder();for (int i = 0; i < mapperName.length(); i++) {char c = mapperName.charAt(i);if (i == 0) {sb.append(Character.toLowerCase(c));} else {sb.append(c);}}// 转换为变量名形式return sb.toString();}
}

上面例子就是拿到所有mapper interface,然后用了GenericBeanDefinition,只要这个对象的类型和构造参数即可以的,也就是定义出来的FactoryBean类型了。

然后注解就是@Import了, 自己重载下

@Retention(RetentionPolicy.RUNTIME)
@Import(DbImportBeanDefinitionRegistrar.class)
public @interface DbMapperScan {
}

需要说明的是,这里方法很多,目的就是往beanFactory加BeanDefinition

测试

把自定义的注解配置到全注解类上面
在这里插入图片描述

测试如下:

拿到bean,注解执行sql方法,正常返回sql数据
在这里插入图片描述

debug加入beanDefinition:
在这里插入图片描述

总结

本文例子显然没有优雅的处理不同的sql语句返回对象,也没有反射拿到所有的mapper inteface(或自己写一个ConfigurationClassPostProcessor扫描) , 不过这些也绝非难事。 只要清楚Spring执行过程中的扩展点,就能自行扩展加入。

Spring面试的时候好像也会问BeanFactory和FactoryBean的区别:所以怎么用的,例子说明清楚就行了


http://www.ppmy.cn/devtools/150338.html

相关文章

29、Spark写数据到Hudi时,同步hive表的一些坑

1.hudi的同步hive表没有comment 原以为hudi同步的hive表是根据数据写入的dataframe的schema创建的。就和spark write hive时类似&#xff0c;查看源码后发现不是。 1.1 hudi同步hive的模式 HMS , JDBC , HIVESQL。我这儿常用的是HMS和JDBC 各个同步模式对应的执行器&#x…

深入Android架构(从线程到AIDL)_27 Messager框架与IMessager接口03

目录 3、 双向沟通的Messenger框架 基本設計原則 4、 IMessenger接口 使用AIDL 3、 双向沟通的Messenger框架 这个Messenger框架是对Binder框架加以扩充而来的。 在双向沟通上&#xff0c;也继承了Binder框架机制。Binder框架双向沟通的应用情境是&#xff1a;当myActivit…

求矩阵不靠边元素之和(PTA)C语言

求矩阵的所有不靠边元素之和&#xff0c;矩阵行的值m从键盘读入(2<m<10)&#xff0c;调用自定义函数Input实现矩阵元素从键盘输入&#xff0c;调用Sum函数实现求和。(只考虑float型&#xff0c;且不需考虑求和的结果可能超出float型能表示的范围)。 函数接口定义&#x…

R语言的数据库编程

R语言的数据库编程 引言 在当今大数据时代&#xff0c;数据分析已成为推动各行业发展的重要力量。R语言&#xff0c;作为一种专为统计分析和数据挖掘而设计的编程语言&#xff0c;逐渐成为数据科学家和分析师的首选工具。然而&#xff0c;仅仅使用R语言进行数据分析往往无法满…

将node节点加入k8s集群

1、k8s master集群安装完成之后即可以开始将node节点加入到集群 2、首先要进行基础环境的配置&#xff0c;包括关闭防火墙、关闭selinux&#xff0c;关闭swap分区&#xff0c;这都是基础操作&#xff0c;不在粘贴代码。 3、进行yum源的配置&#xff0c;这里最简单方法是把mas…

Python 扫描枪读取发票数据导入Excel

财务需要一个扫描枪扫描发票文件&#xff0c;并将主要信息录入Excel 的功能。 文件中sheet表的列名称&#xff0c;依次为&#xff1a;发票编号、发票编码、日期、金额、工号、扫描日期。 扫描的时候&#xff0c;Excel 文件需要关闭&#xff0c;否则会报错。 import openpyxl …

计算机网络之---公钥基础设施(PKI)

公钥基础设施 公钥基础设施&#xff08;PKI&#xff0c;Public Key Infrastructure&#xff09; 是一种用于管理公钥加密的系统架构&#xff0c;它通过结合硬件、软件、策略和标准来确保数字通信的安全性。PKI 提供了必要的框架&#xff0c;用于管理密钥对&#xff08;包括公钥…

基于 Selenium 实现上海大学校园网自动登录

基于 Selenium 实现上海大学校园网自动登录 一、技术方案 核心工具&#xff1a; Selenium&#xff1a;一个用于自动化测试的工具&#xff0c;能够模拟用户在浏览器上的操作。Edge WebDriver&#xff1a;用于控制 Edge 浏览器的驱动程序。 功能设计&#xff1a; 检测网络状…