Spring FactoryBean到仿照mybatis @Mapper的实现

目录

    • 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. 代理、动态代理,FactoryBean
  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;}}

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

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的手动实现思考

复习下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.mzph.cn/bicheng/68381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Go】Go数据类型详解—数组与切片

1. 前言 今天需要学习的是Go语言当中的数组与切片数据类型。很多编程语言当中都有数组这样的数据类型&#xff0c;Go当中的切片类型本质上也是对 数组的引用。但是在了解如何定义使用数组与切片之前&#xff0c;我们需要思考为什么要引入数组这样的数据结构。 1.1 为什么需要…

flutter Get GetMiddleware 中间件不起作用问题

当使用 get: ^5.0.0-release-candidate-9.2.1最新版本时&#xff0c;中间件GetMiddleware各种教程都是让我们在redirect中实现&#xff0c;比如&#xff1a; overrideRouteSettings? redirect(String? route) {return RouteSettings(name: "/companyAuthIndexPage"…

【Idea启动项目报错NegativeArraySizeException】

项目场景&#xff1a; Idea启动项目报错&#xff08;打包不报错&#xff09;&#xff0c;项目在服务器部署运行没有问题&#xff0c;尝试了重启idea、重启电脑、maven clean/install 都不行 maven-resources-production:sample: java.lang.NegativeArraySizeException: -5833…

微信小程序:播放音频

在小程序开发中&#xff0c;音频播放是一个重要的功能。本文将详细介绍小程序音频播放的相关知识点&#xff0c;帮助开发者更好地掌握小程序音频播放的实现方法。 一、小程序音频播放的基本流程 在小程序中&#xff0c;音频播放的基本流程如下&#xff1a; 获取音频数据&#…

运行fastGPT 第四步 配置ONE API 添加模型

上次已经装好了所有的依赖和程序。 下面在网页中配置One API &#xff0c;这个是大模型的接口。配置好了之后&#xff0c;就可以配置fastGPT了。 打开 OneAPI 页面 添加模型 这里要添加具体的付费模型的API接口填进来。 可以通过ip:3001访问OneAPI后台&#xff0c;**默认账号…

RocketMQ 学习笔记01

一、MQ简介 1. 什么是MQ&#xff1f; MQ&#xff08;Message Queue&#xff0c;消息队列&#xff09; 是一种在分布式系统中用于实现进程间通信和数据传输的中间件。它通过在不同进程或应用程序之间传递消息&#xff0c;实现数据的异步处理、解耦和削峰填谷等功能。MQ广泛应用…

梁山派入门指南3——串口使用详解,包括串口发送数据、重定向、中断接收不定长数据、DMA+串口接收不定长数据,以及对应的bsp文件和使用示例

梁山派入门指南3——串口使用详解&#xff0c;包括串口发送数据、重定向、中断接收不定长数据、DMA串口接收不定长数据&#xff0c;以及对应的bsp文件和使用示例 1. 串口发送数据1.1 串口简介1.2 梁山派上的串口开发1.3 bsp_uart文件&#xff08;只发送不接收&#xff0c;兼容串…

Linux和Docker常用终端命令:保姆级图文详解

文章目录 前言1、Docker 常用命令1.1、镜像管理1.2、容器管理1.3、网络管理1.4、数据卷管理1.5、监控和性能管理 2、Linux 常用命令分类2.1、文件和目录管理2.2、用户管理2.3、系统监控和性能2.4、软件包管理2.5、网络管理 前言 亲爱的家人们&#xff0c;创作很不容易&#xf…

智能科技与共情能力加持,哈曼重新定义驾乘体验

2025年1月6日&#xff0c;拉斯维加斯&#xff0c;2025年国际消费电子展——想象一下&#xff0c;当您步入一辆汽车&#xff0c;它不仅能响应您的指令&#xff0c;更能理解您的需求、适应您的偏好&#xff0c;并为您创造一个独特且专属的交互环境。作为汽车科技领域的知名企业和…

关于2025年智能化招聘管理系统平台发展趋势

2025年&#xff0c;招聘管理领域正站在变革的十字路口&#xff0c;全新的技术浪潮与不断变化的职场生态相互碰撞&#xff0c;促使招聘管理系统成为重塑企业人才战略的关键力量。智能化招聘管理系统平台在这一背景下迅速崛起&#xff0c;其发展趋势不仅影响企业的招聘效率与质量…

机器视觉5-全连接神经网络

机器视觉5-全连接神经网络1 图像表示多层感知器全连接神经网络一、两层全连接网络表达式二、三层全连接网络表达式三、关于非线性操作的说明四、全连接神经网络的映射原理 全连接神经网络的权值一、线性分类器二、两层全连接网络三、总结 全连接神经网络线性不可分全连接神经网…

解锁转型密码:不同方向的技能与素质修炼手册

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 解锁…

ChatGPT提示词合集(国内大模型可参考使用)

行为迅速的Linux终端我想让你充当 linux 终端。我将输入命令&#xff0c;您将回复终端应显示的内容。我希望您只在一个唯一的代码块内回复终端输出&#xff0c;而不是其他任何内容。不要写解释。除非我指示您这样做&#xff0c;否则不要键入命令。当我需要用英语告诉你一些事情…

第三十八章 Spring之假如让你来写MVC——适配器篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

深度剖析RabbitMQ:从基础组件到管理页面详解

文章目录 一、简介二、Overview2.1 Overview->Totals2.2 Overview->Nodesbroker的属性2.3 Overview->Churn statistics2.4 Overview->Ports and contexts2.5 Overview->Export definitions2.6 Overview->Import definitions 三、Connections连接的属性 四、C…

使用 Python 编写一个简单的聊天机器人

&#x1f496; 欢迎来到我的博客&#xff01; 非常高兴能在这里与您相遇。在这里&#xff0c;您不仅能获得有趣的技术分享&#xff0c;还能感受到轻松愉快的氛围。无论您是编程新手&#xff0c;还是资深开发者&#xff0c;都能在这里找到属于您的知识宝藏&#xff0c;学习和成长…

Unity 自定义批量打包工具

打包配置项 using UnityEngine; using System.Collections.Generic;namespace MYTOOL.Build {[System.Flags]public enum VersionOptions{None 0,Major 1,Minor 4,Build 8,Revision 0x10,}/// <summary>/// 批量打包配置文件/// </summary>[CreateAssetMenu]…

JAVA实现五子棋小游戏(附源码)

文章目录 一、设计来源捡金币闯关小游戏讲解1.1 主界面1.2 黑棋胜利界面1.3 白棋胜利界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/145161039 JA…

Flink概述

一、Flink是什么 二、Flink特点 三、Flink vs SparkStreaming 表 Flink 和 Streaming对比 Flink Streaming 计算模型 流计算 微批处理 时间语义 事件时间、处理时间 处理时间 窗口 多、灵活 少、不灵活&#xff08;窗口必须是批次的整数倍&#xff09; 状态 有 …

基于SpringBoot的企业级工位管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的企业级工位管理系统采用前后端分离架构方式&#xff0c;系统设计了管理员、员工两种角色&#xff0c;系统实现了用户登录与注册、个人中心、员工管理、部门信息管理、工位信息管理、使用情况管理、工位分配管理等功能。 技术选型 开发工具&…