一、BeanFactory和FactoryBean区别?

BeanFactory是工厂类,提供了获取和检索Bean的接口。它代表着Spring的IoC容器,负责Bean实例化以及管理Bean之间的依赖关系。作为Spring框架中最核心的模块,它提供容器的基本规范。

FactoryBean是一个bean,可以作为其他bean的工厂。FactoryBean像其他bean一样在注入到IoC容器中,但是当从IoC容器中获取FactoryBean的时候,实际返回的FactoryBean#getObject()方法返回的对象。如果想获取FactoryBean本身,则需要在bean的名称添加前缀&来获取FactoryBean对象本身(applicationContext.getBean("&" + beanName))。

二、如何使用FactoryBean?

  1. 在了解如何使用FactoryBean之前,先看看FactoryBean接口的定义。
public interface FactoryBean<T> {

    /**
* 实际返回的bean对象
*/
@Nullable
T getObject() throws Exception; /**
* 实际返回bean的class对象
*/
@Nullable
Class<?> getObjectType(); /**
* 指定bean是否是单例,默认为true(Spring bean默认都是单例)。
*/
default boolean isSingleton() {
return true;
} }

FactoryBean接口定义了三个方法,其中getObject方法返回bean对象。

  1. 接下来通过一个简单的获取加密工具演示如何使用FactoryBean

定义一个FactoryBean类

@RequiredArgsConstructor
public class MessageDigestFactoryBean implements FactoryBean<MessageDigest> {
// 算法名称
private final String algorithmName; @Override
public MessageDigest getObject() throws Exception {
return MessageDigest.getInstance(algorithmName);
} @Override
public Class<?> getObjectType() {
return MessageDigest.class;
}
}

通过上面定义的类,传入不同的算法名称实现获取不同算法的加密类

@Configuration
public class MessageDigestConfiguration { @Bean
public MessageDigestFactoryBean md5() {
return new MessageDigestFactoryBean("MD5");
} @Bean
public MessageDigestFactoryBean sha1() {
return new MessageDigestFactoryBean("SHA-1");
} }

这里定义了两种加密算法MD5和SHA-1,这样即可通过applicationContext对象获取不同的加密工具

public class App {

    public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MessageDigestConfiguration.class);
MessageDigest md5 = applicationContext.getBean("md5", MessageDigest.class);
System.out.println(Arrays.toString(md5.digest("test".getBytes())));
MessageDigest sha1 = applicationContext.getBean("sha1", MessageDigest.class);
System.out.println(Arrays.toString(sha1.digest("test".getBytes())));
} } // 输出
/*
[9, -113, 107, -51, 70, 33, -45, 115, -54, -34, 78, -125, 38, 39, -76, -10]
[-87, 74, -113, -27, -52, -79, -101, -90, 28, 76, 8, 115, -45, -111, -23, -121, -104, 47, -69, -45]
*/

可以看到,通过同一个FactoryBean类,向IoC容器中注入了两个不同的bean。

如果让FactoryBean#isSingleton方法返回false,那么得到的输出如下

public class App {

    public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MessageDigestConfiguration.class); System.out.println(applicationContext.getBean("md5", MessageDigest.class).hashCode());
System.out.println(applicationContext.getBean("md5", MessageDigest.class).hashCode());
}
} // 输出
/*
768192757
1697752980
*/

可以看出每次通过applicationContext获取的对象都是一个新的对象,从而每个bean都是原型作用域。

三、FactoryBean在Mybatis集成Spring Boot中的应用

使用过Spring Boot的同学都知道,当我们需要扫描Mapper的时候,需要添加@MapperScan注解完成对Mapper对象的扫描,@MapperScan导入MapperScannerRegistrar类完成扫描。

但是Mapper类都是接口,无法被实例化,那么为什么在Spring中能够直接注入Mapper对象呢?

实际上Mybatis是通过FactoryBean对象创建Mapper对象的代理对象,完成Mapper接口的注入。

下面跟随Mybatis-Spring源码了解如何动态创建Mapper对象的实现类。

首先看@MapperScan注解源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class) // 导入MapperScannerRegistrar配置类
@Repeatable(MapperScans.class)
public @interface MapperScan { String[] basePackages() default {}; } /**
* 完成Mapper接口扫描
*/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader; /**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
} /**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
} void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
// 创建scanner对象,扫描Mapper
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader); 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));
} Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
} scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); // 扫描的包
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList())); basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList())); basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList())); scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
} static class RepeatingRegistrar extends MapperScannerRegistrar {
/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScansAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
if (mapperScansAttrs != null) {
Arrays.stream(mapperScansAttrs.getAnnotationArray("value"))
.forEach(mapperScanAttrs -> registerBeanDefinitions(mapperScanAttrs, registry));
}
}
} }

ClassPathMapperScan通过doScan方法扫描Mapper

  private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<>();

  @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 {
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 省略其余代码 // 记录bean的类名(即Mapper接口类名),将Mapper类名传递给MapperFactoryBean作为构造方法参数
// 这样MapperFactoryBean的getObject方法即可通过动态代理创建Mapper的动态代理对象
String beanClassName = definition.getBeanClassName();
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// 修改BeanClass
definition.setBeanClass(this.mapperFactoryBean.getClass());
}
}

ClassPathMapperScan扫描到Mapper类之后,修改BeanClass为MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  // Mapper的class对象,FactoryBean创建该Mapper的动态代理对象
private Class<T> mapperInterface; public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} @Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}

继续跟踪getSqlSession().getMapper(this.mapperInterface)方法

Configuration

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

可以看出来这里是通过MapperProxyFactory对象创建Mapper对象

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// JDK动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} /**
* 创建Mapper对象的代理对象MapperProxy,MapperProxy实现了InvocationHandler接口
*/
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }

继续看MapperProxy对象的invoke方法

  @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
} /**
* 根据接口定义方法执行SQL,并返回结果
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

可以看出Mybatis通过JDK动态代理的方式,创建Mapper接口的代理对象,并通过接口声明的方法查找并执行SQL。

FactoryBean简介以及Mybatis-Spring应用的更多相关文章

  1. MyBatis学习(一)、MyBatis简介与配置MyBatis+Spring+MySql

    一.MyBatis简介与配置MyBatis+Spring+MySql 1.1MyBatis简介 MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的J ...

  2. 一、MyBatis简介与配置MyBatis+Spring+MySql

    //备注:该博客引自:http://limingnihao.iteye.com/blog/106076 1.1MyBatis简介 MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架 ...

  3. MyBatis学习 之 一、MyBatis简介与配置MyBatis+Spring+MySql

    目录(?)[-] 一MyBatis简介与配置MyBatisSpringMySql MyBatis简介 MyBatisSpringMySql简单配置 搭建Spring环境 建立MySql数据库 搭建My ...

  4. MyBatis简介与配置MyBatis+Spring+MySql

    MyBatis学习 之 一.MyBatis简介与配置MyBatis+Spring+MySql MyBatis学习 之 二.SQL语句映射文件(1)resultMap MyBatis学习 之 二.SQL ...

  5. mybatis 学习二 MyBatis简介与配置MyBatis+Spring+MySql

    1.2.2建立MySql数据库 在C:\Program Files\MySQL\MySQL Server 5.7\bin下面: 首先连接MySQL:        mysql  -u root -p ...

  6. SpringMVC+Mybatis+Spring整合

    Maven引入需要的JAR包 pom.xml <properties> <!-- spring版本号 --> <spring.version>4.0.2.RELEA ...

  7. MyBatis详解 与配置MyBatis+Spring+MySql

    MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架.MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获.MyBatis 只使用简单的XML 和注解来配置和映射基本 ...

  8. MyBatis+Spring+Spring MVC整合开发

    MyBatis+Spring+Spring MVC整合开发课程观看地址:http://www.xuetuwuyou.com/course/65课程出自学途无忧网:http://www.xuetuwuy ...

  9. MyBatis Spring整合配置映射接口类与映射xml文件

    本文转自http://blog.csdn.net/zht666/article/details/38706083 Spring整合MyBatis使用到了mybatis-spring,在配置mybati ...

  10. MyBatis Spring SqlSessionFactoryBean 配置

    在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建.而在 MyBatis-Spring 中,则使用 SqlSessionFactory ...

随机推荐

  1. 【NX二次开发】Block UI 选择单元

    属性说明 属性   类型   描述   常规           BlockID    String    控件ID    Enable    Logical    是否可操作    Group    ...

  2. Django基础之路由层

    内容概要 路由匹配 无名有名分组 反向解析 无名有名分组反向解析(难理解) 路由分发 名称空间 伪静态 内容详细 1 路由匹配 urls.py url()方法第一个参数其实是一个正则表达式 第一个参数 ...

  3. 面试热点|理解TCP/IP传输层拥塞控制算法

    0x00.前言 通过本文你将了解到以下内容: 拥塞控制概念以及其背景 流量控制和拥塞控制的区别与联系 拥塞控制主要过程详解 伙伴们认真学习一下,让offer来得更猛烈些吧! 0x01.TCP/IP协议 ...

  4. NOIP模拟测试8「寿司」

    考试时打的类似$n^2$暴力,然后炸了只有10分 后来验证我的算法伪了. 题解 显然你有一种解法,假设你要在一个B点断开将R分别移向最左 最右,这样只用分别计算B点右面蓝色数量左面蓝色数量就得到了一个 ...

  5. 【题解】Luogu P1011 车站

    题目描述 火车从始发站(称为第1站)开出,在始发站上车的人数为a,然后到达第2站,在第2站有人上.下车,但上.下车的人数相同,因此在第2站开出时(即在到达第3站之前)车上的人数保持为a人.从第3站起( ...

  6. 在微信小程序中使用阿里图标库Iconfont

    首先想要使用图标,只用上图的五个iconfont相关文件就可以了.(下下来的文件iconfont.wxss开始是.css的后缀,手动改成.wxss就可以在小程序中使用) 然后在app.wxss中引入i ...

  7. Docker 优雅终止方案

    作为一名系统工程师,你可能经常需要重启容器,毕竟Kubernetes的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复,实在不行再重启系统,这就是系统重启工程师的杀手锏.然而现实 ...

  8. SonarQube 启动无报错但是拒绝访问的解决过程及方案

    启动sonarqube [sonar_user@Sonnarqube-dev linux-x86-64]$ ./sonar.sh start 查看网页: 排错步骤 第一步输入启动过程命令查看启动信息 ...

  9. 01_JVM与Java体系结构

    JVM发展历程 Sun Classic VM Exact VM 为了解决上一个虚拟机问题,jdk1.2时,sun提供了此虚拟机. Exact Memory Management:准确式内存管理 SUN ...

  10. RNA

    原始地球 你会想,我们每一个细胞中都有一个遗传分子叫做DNA?那么,DNA之前有没有什么遗传分子呢?我的答案是:"有".在远古地球,那个海底有无数火山,喷发的火山口两侧都是喷涌出的 ...