Spring(SpringBoot)--FactoryBean--使用/原理/详解

原文网址:

简介

说明

        本文介绍Spring中的FactoryBean,包括:作用,用法,原理,Mybatis对FactoryBean的应用。

FactoryBean是干什么的?

        可以通过一个FactoryBean来生产一个对象,可以获取这个对象的类型以及这个对象是不是单例。

        在某些情况下,实例化Bean过程比较复杂,若按照传统的方式,则需要在中提供大量的配置信息,不够灵活,这时采用编码的方式能得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口在Spring中占重要地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。

        从Spring 3.0 开始, FactoryBean开始支持泛型,即接口声明改为FactoryBean 的形式。

源码:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

普通bean与FactoryBean

Spring 容器中有两种bean:普通bean和工厂bean。

普通bean:通过反射实例化被标记为bean的类。例:@Component指定的类,的class属性指定的实现类。

FactoryBean:其返回的对象不是指定类的一个实例,而是FactoryBean#getObject方法返回的对象。若想获取FactoryBean本身,要在bean的名称添加前缀&来获取FactoryBean对象本身(applicationContext.getBean("&" + beanName))。

FactoryBean不遵循Spring的生命周期

        作者的想法是,正是因为Spring的作者想要放权给使用者,让使用者自己实现创建一个bean的逻辑,所以Spring并不会过多的插手该Bean的实例化过程,使得一个Bean的实例化完全由使用者本人去实现。

        这个类并不会像普通bean那样在Spring容器初始化时进行实例化,而是类似于懒加载,在获取时才进行创建和返回。至于是不是单例,要取决于isSingleton()方法的返回值。

        当然,这个创建出来的bean也会被缓存,AOP等逻辑也会对该类生效,当然这都是后话。

简单示例

公共部分

FactoryBean实现类

package com.example.tmp;

import lombok.AllArgsConstructor;
import org.springframework.beans.factory.FactoryBean;

@AllArgsConstructor
public class MyFactoryBean implements FactoryBean {
    private String myBeanName;

    @Override
    public Object getObject() throws Exception {
        MyBean myBean = new MyBean();
        myBean.setName(myBeanName);
        return myBean;
    }

    @Override
    public Class<?> getObjectType() {
        return MyBean.class;
    }
}

bean实体类

package com.example.tmp;

import lombok.Data;

@Data
public class MyBean {
   private Integer id;
   private String name;
}

注册单个FactoryBean

配置类

package com.example.tmp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {
    @Bean
    public MyFactoryBean getMyBean() {
        return new MyFactoryBean("Tony");
    }
}

controller

package com.example.controller;

import com.example.tmp.MyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    MyBean myBean;

    @GetMapping("/test1")
    public String test1() {
        System.out.println(myBean.getName());
        return "test1 success";
    }

}

测试

访问:http://localhost:8080/test1

后台结果:

Tony

注册多个FactoryBean

配置类

package com.example.tmp;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {
    @Bean("bean1")
    public MyFactoryBean getMyBean1() {
        return new MyFactoryBean("Tony");
    }

    @Bean("bean2")
    public MyFactoryBean getMyBean2() {
        return new MyFactoryBean("Pepper");
    }
}

controller

package com.example.controller;

import com.example.tmp.MyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    @Qualifier("bean2")
    MyBean myBean;

    @GetMapping("/test1")
    public String test1() {
        System.out.println(myBean.getName());
        return "test1 success";
    }

}

测试

访问:http://localhost:8080/test1

后台结果:

Pepper

实例分析(Mybatis)

其他网址

【Mybatis源码】Mybatis如何为mapper接口生成代理对象--CSDN博客

二、MyBatis Mapper Bean初始化深度解析 - 奋斗人生 - OSCHINA - 中文开源技术交流社区
FactoryBean简介以及Mybatis-Spring应用
带你跳出源码地狱,从原理上理解MyBatis对Spring源码的扩展实现 - 知乎

简介

本文分析的版本:mybatis-spring-2.0.4.jar、spring-framework-5.2.7.RELEASE

总体流程

初始化

  1. 在配置类标注@MapperScan("Mapper接口所在包路径")
    1. @MapperScan上边有:@Import(MapperScannerRegistrar.class)。向Spring注入此类。
  2. 创建Mapper扫描器的配置器(MapperScannerConfigurer)
    1. MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar,会被Spring自动回调此接口的registerBeanDefinitions方法。
      1. MapperScannerRegistrar#registerBeanDefinitions方法
        1. 构建MapperScannerConfigurer并注册进Spring容器。
  3. 创建扫描器,调用其scan方法。
    1. MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,会被Spring自动回调此接口的postProcessBeanDefinitionRegistry方法。
      1. postProcessBeanDefinitionRegistry方法:
        1. 创建一个自定义的扫描器:ClassPathMapperScanner(继承自Spring的ClassPathBeanDefinitionScanner),调用其scan方法,即:ClassPathMapperScanner#scan
      2. ClassPathMapperScanner#scan
        1. 此方法未覆写,是调用父类(ClassPathBeanDefinitionScanner)的实现
          1. 即:ClassPathBeanDefinitionScanner#scan
          2. 调用到:ClassPathBeanDefinitionScanner#doScan
            1. 此方法被ClassPathMapperScanner覆写,所以调用到:ClassPathMapperScanner#doScan
  4. 扫描你指定的包路径下的所有Mapper接口,并转换为BeanDefinition
    1. ClassPathMapperScanner#doScan
      1. ClassPathMapperScanner#processBeanDefinitions
        1. 扫描你指定的包路径下的所有Mapper接口,并转换为BeanDefinition
        2. 设置每一个BeanDefinition的BeanClass为:MapperFactoryBean.class(实现FactoryBean接口)
          1. 之后实例化时会调用这个FactoryBean的getObject()方法来构建一个代理Mapper对象。这就是将map接口转变成一个对象交给Spring管理的关键。
        3. 通过definition.getPropertyValues().add()方法,传入该BeanDefinition代表的接口。
        4. 将所有的BeanDefinition通过上边步骤设置之后,全部注册到BeanFactory中,由BeanFactory对这些FactoryBean进行管理。
  5. 注入时,实例化Mapper的代理类(refresh时,完成对注入对象的实例化)
    1. MapperFactoryBean#getObject返回Mapper的代理类:MapperProxy.class。
    2. MapperProxy.class里有invoke方法,使用时会走这个方法。

实例化步骤

  1. 在使用或者获取这些bean的时候,Spring首先获取你要使用的接口类型。
  2. 遍历当前容器内所有的bean逐个对比,当有匹配的直接返回。但是,因为Mapper接口还并没有被实例化,所以没有找到,所以在遍历到FactoryBean的时候,会调用getObjectType方法,将返回值与你要使用的接口类型作比对。
  3. 当 FactoryBean的返回类型匹配的时候,Spring会调用FactoryBean的getObject方法将对象创建出来。
  4. 创建过程中,通过之前传入的接口,做jdk动态代理,完成MyBatis的代理逻辑。
  5. 对象创建完成后,通过isSingleton方法的返回值判断,如果是单例对象,就将该对象缓存起来。并返回。

1.配置类标注@MapperScan("XXX")

2.创建MapperScannerConfigurer

对应方法:MapperScannerRegistrar#registerBeanDefinitions

概述

构建类型为MapperScannerConfigurer的BeanDefinition并注册进Spring容器。

流程

refresh() //AbstractApplicationContext
    invokeBeanFactoryPostProcessors(beanFactory); //AbstractApplicationContext
        invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors()); //PostProcessorRegistrationDelegate
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //PostProcessorRegistrationDelegate
                postProcessor.postProcessBeanDefinitionRegistry(registry); //PostProcessorRegistrationDelegate
                    processConfigBeanDefinitions(registry); //ConfigurationClassPostProcessor.class
                        ......
                            registerBeanDefinitions //MapperScannerRegistrar

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
        builder.addPropertyValue("annotationClass", annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
        builder.addPropertyValue("markerInterface", markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
        builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
        builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
        builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    List<String> basePackages = new ArrayList();
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
    if (basePackages.isEmpty()) {
        basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
        builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

3.扫描Mapper接口,转为BeanDefinition

对应方法:MapperScannerConfigurer#postProcessBeanDefinitionRegistry

概述

  1. 创建一个自定义的扫描器ClassPathMapperScanner(继承自Spring的ClassPathBeanDefinitionScanner),它扫描你传入的包路径下的所有的接口,并转换为BeanDefinition
  2. 遍历上一步转化的BeanDefinition,修改他们的BeanClass为MapperFactoryBean类(实现FactoryBean接口),为后面的设置动态代理打下基础。

流程

调用到本方法的流程

refresh() //AbstractApplicationContext
    invokeBeanFactoryPostProcessors(beanFactory); //AbstractApplicationContext
        //PostProcessorRegistrationDelegate
        invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors());
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //PostProcessorRegistrationDelegate
                postProcessor.postProcessBeanDefinitionRegistry(registry); //PostProcessorRegistrationDelegate
                    postProcessBeanDefinitionRegistry //MapperScannerConfigurer.class

postProcessBeanDefinitionRegistry //MapperScannerConfigurer
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); //MapperScannerConfigurer
        scan(String... basePackages) //ClassPathBeanDefinitionScanner
            doScan(basePackages); //ClassPathBeanDefinitionScanner
                postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName); //ClassPathBeanDefinitionScanner
                    definition.setBeanClass(this.mapperFactoryBeanClass); //ClassPathBeanDefinitionScanner

这里的this.mapperFactoryBeanClass就是:MapperFactoryBean。

4.注入时:创建Mapper的代理类

概述

        一般情况下,我们Controller会使用@Autowired注入Service,而Service会默认注入Mapper,此时就会

        MapperFactoryBean#getObject返回Mapper的代理类:MapperProxy.class。

        MapperProxy.class里有invoke方法,使用时会走这个方法。

流程

refresh() //AbstractApplicationContext
  finishBeanFactoryInitialization(beanFactory); //AbstractApplicationContext
    beanFactory.preInstantiateSingletons(); //DefaultListableBeanFactory
      getBean() //DefaultListableBeanFactory
        ......
          getObject() //MapperFactoryBean.class
            this.getSqlSession().getMapper(this.mapperInterface); //MapperFactoryBean.class
              ......
                getMapper(Class type, SqlSession sqlSession) //MapperRegistry.class
                  getConfiguration().getMapper(type, this) //SqlSessionTemplate.class
                    // mybatisMapperRegistry为MybatisMapperRegistry类型
                    mybatisMapperRegistry.getMapper(type, sqlSession); // MybatisConfiguration.class
                      getMapper(Class type, SqlSession sqlSession) //MybatisMapperRegistry.class

MybatisMapperRegistry继承MapperRegistry

MybatisMapperRegistry#getMapper具体如下:

getMapper(Class type, SqlSession sqlSession) //MybatisMapperRegistry.class
    mapperProxyFactory.newInstance(sqlSession) //MybatisMapperRegistry.class
        newInstance(SqlSession sqlSession) // MybatisMapperProxyFactory.class
            // MybatisMapperProxyFactory.class(下边所有代码都在这里)
            MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
            newInstance(mapperProxy);
                Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);

MapperProxy 

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -4724728412955527868L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor<Lookup> lookupConstructor;
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperProxy.MapperMethodInvoker> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperProxy.MapperMethodInvoker> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) 
                     ? method.invoke(this, args) 
                     : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }
    // 省略其他代码
}

5.调用

概述

我们一般在使用时,直接@Autowired注入一个Mapper接口,然后调用方法即可。

注入Mapper:

上边“注入时:创建Mapper的代理类”,已经说明了,注入时会返回包含MapperProxy的代理。

调用Mapper方法:

会调用到MapperProxy#invoke

  1. 创建实例来执行方法。(有DefaultMethodInvoker、PlainMethodInvoker两种类)
    1. 调用MapperMethod#execute方法

流程

invoke //MapperProxy
    Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);

追踪后者

cachedInvoker(Method method) //MapperProxy
    new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));

它再调用invoke(proxy, method, args, this.sqlSession)

invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) //MapperProxy.PlainMethodInvoker
    mapperMethod.execute(sqlSession, args); //MapperProxy.PlainMethodInvoker
        execute(SqlSession sqlSession, Object[] args) //MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
            if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
            }
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

其他网址

FactoryBean简介以及Mybatis-Spring应用
Spring中FactoryBean的使用_goodluckwj的博客-CSDN博客
spring之手写mybatis-spring_yangyanping20108的博客-CSDN博客

上一篇:第二节:REST风格的案例及源码分析


下一篇:href=javascript的用法