一、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欢迎界面

    环境变量 变量名:UGII_WELCOME_PAGE 变量值:http://www.baidu.com/

  2. 练习(time.tick定时器监控数据库)

    设立个定时器,监控数据库里fb_state(发布状态),并将数据库中一条记录的开始时间与截止时间和当前时间进行比对.若当前时间在开始时间与结束时间之间,则将发布状态设为1,否则为0. 同时,将此段代码 ...

  3. NOIP模拟测试17「入阵曲&#183;将军令&#183;星空」

    入阵曲 题解 应用了一种美妙移项思想, 我们先考虑在一维上的做法 维护前缀和$(sum[r]-sum[l-1])\%k==0$可以转化为 $sum[r]\% k==sum[l-1]\%k$开个桶维护一 ...

  4. VBS脚本编程(4)——流程控制语句

    分支结构--If .. Then .. Else .. 根据表达式的值有条件地执行一组语句. If condition Then statements [Else elsestatements ] 或 ...

  5. Springboot集成Spring Security实现JWT认证

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐.而作为前后端分离的SSO方案,JWT ...

  6. 每日三道面试题,通往自由的道路4——JVM篇

    茫茫人海千千万万,感谢这一秒你看到这里.希望我的面试题系列能对你的有所帮助!共勉! 愿你在未来的日子,保持热爱,奔赴山海! 每日三道面试题,成就更好自我 昨天既然你有讲到字符串常量池是吧,那这样吧 1 ...

  7. Opencv 播放mp4文件和读取摄像头图以及可能会发生的一些异常问题解决方法

    学习内容 学习Opencv 读取并播放本地视频和打开摄像头图像以及可能会发生的一些异常问题解决方法 代码演示 电脑环境信息: OpenCV版本:4.5.2 ,vs2017 1.视频文件读取与播放 加载 ...

  8. TCP 的 Keepalive 和 HTTP 的 Keep-Alive 是一个东西吗?

    大家好,我是小林. TCP 的 Keepalive 和 HTTP 的 Keep-Alive 是一个东西吗? 这是个好问题,应该有不少人都会搞混,因为这两个东西看上去太像了,很容易误以为是同一个东西. ...

  9. Mysql 主键的操作

    ​ 主键:primary key ,主要的键.一张表只能有一个字段可以使用对应的键,用来唯一的约束字段里面的数据,数据不能重复,这种键称之为主键,一张表只能最多有一个主键.  一.增加主键 方法一:在 ...

  10. Extjs中由于ID重复引起的各种异常的解决方法

    很多人使用EXTJS中的Tabpanel遇到一个问题: 那就是在点击Tabpanel后,有时会发现Tabpanel出现错误,或无法Destroy已经关闭的panel,发现已经关闭的panel 中的组件 ...