Spring(SpringBoot)--FactoryBean -- use / principle / detailed explanation

Posted by mikie46 on Wed, 17 Nov 2021 12:45:27 +0100

Original website:

brief introduction

explain

        This article introduces FactoryBean in Spring, including its function, usage, principle and the application of Mybatis to FactoryBean.

What does FactoryBean do?

        An object can be produced through a FactoryBean, and the type of the object and whether the object is a singleton can be obtained.

        In some cases, the process of instantiating beans is complex. If you follow the traditional method, you need to provide a large amount of configuration information in the, which is not flexible enough. At this time, you can get a simple scheme by coding. Spring provides a factory class interface of org.springframework.bean.factory.FactoryBean for this purpose. Users can customize the logic of instantiating beans by implementing this interface. FactoryBean interface plays an important role in spring. Spring itself provides the implementation of more than 70 factorybeans. They hide the details of instantiating some complex beans and bring convenience to the upper application.

        Starting from Spring 3.0, FactoryBean began to support generics, that is, the interface declaration was changed to the form of FactoryBean.

Source code:

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

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

Ordinary bean and FactoryBean

There are two kinds of beans in the Spring container: normal beans and factory beans.

Ordinary beans: instantiate classes marked as beans through reflection. Example: the class specified by @ Component and the implementation class specified by the class attribute of.

FactoryBean: the object returned is not an instance of the specified class, but an object returned by the FactoryBean#getObject method. If you want to get the FactoryBean itself, you need to add a prefix & to the bean name to get the FactoryBean object itself (ApplicationContext. GetBean ("&" + beanname)).

Factorybeans do not follow the Spring lifecycle

        The author's idea is that precisely because the author of Spring wants to delegate power to the user and let the user implement the logic of creating a bean, Spring will not interfere too much in the instantiation process of the bean, so that the instantiation of a bean is completely implemented by the user himself.

        This class is not instantiated when the Spring container is initialized like ordinary bean s, but is similar to lazy loading, which is created and returned only when it is obtained. Whether it is a singleton depends on the return value of the isSingleton() method.

        Of course, the created bean will also be cached, and AOP and other logic will also take effect on this class. Of course, this will be discussed later.

Simple example

Public part

FactoryBean implementation class

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 entity class

package com.example.tmp;

import lombok.Data;

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

Register a single FactoryBean

Configuration class

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";
    }

}

test

visit: http://localhost:8080/test1

Background results:

Tony

Register multiple factorybeans

Configuration class

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";
    }

}

test

visit: http://localhost:8080/test1

Background results:

Pepper

Case analysis (Mybatis)

Other web sites

[Mybatis source code] how does Mybatis generate a proxy object for the mapper interface -- CSDN blog

2, MyBatis Mapper Bean initialization depth analysis - struggling life - OSCHINA - Chinese open source technology exchange community
FactoryBean introduction and mybatis spring application
Take you out of the source code hell and understand MyBatis's extended implementation of Spring source code in principle - know

brief introduction

The versions analyzed in this article are: mybatis-spring-2.0.4.jar, spring-framework-5.2.7.RELEASE

Overall process

initialization

  1. Mark @ mapperscan ("package path of mapper interface") in the configuration class
    1. @There is @ import (mappercannerregister. Class) above mappercan. Inject this class into Spring.
  2. Create MapperScannerConfigurer for Mapper scanner
    1. Mappercannerregister class implements importbeandefinitionregister, and Spring will automatically call back the registerBeanDefinitions method of this interface.
      1. Mappercannerregister#registerbeandefinitions method
        1. Build mappercannerconfigurer and register it into the Spring container.
  3. Create a scanner and call its scan method.
    1. Mappercannerconfigurer implements the BeanDefinitionRegistryPostProcessor interface. Spring will automatically call back the postProcessBeanDefinitionRegistry method of this interface.
      1. postProcessBeanDefinitionRegistry method:
        1. Create a custom scanner: ClassPathMapperScanner (inherited from Spring's ClassPathBeanDefinitionScanner), and call its scan method, that is, ClassPathMapperScanner#scan
      2. ClassPathMapperScanner#scan
        1. This method is not overridden. It is the implementation of calling the parent class (ClassPathBeanDefinitionScanner)
          1. That is, ClassPathBeanDefinitionScanner#scan
          2. Call to: ClassPathBeanDefinitionScanner#doScan
            1. This method is overridden by classpathmappercanner, so it is called to: classpathmappercanner#doscan
  4. Scan all Mapper interfaces under the package path you specify and convert them to BeanDefinition
    1. ClassPathMapperScanner#doScan
      1. ClassPathMapperScanner#processBeanDefinitions
        1. Scan all Mapper interfaces under the package path you specify and convert them to BeanDefinition.
        2. Set the BeanClass of each BeanDefinition to MapperFactoryBean.class (implement the FactoryBean interface)
          1. Then, when instantiating, the getObject() method of the FactoryBean will be called to build a proxy Mapper object. This is the key to turning the map interface into an object for Spring management.
        3. Pass in the interface represented by the BeanDefinition through the definition.getPropertyValues().add() method.
        4. After setting all beandefinitions in the above steps, all beandefinitions are registered in BeanFactory, which manages these factorybeans.
  5. When injecting, instantiate Mapper's proxy class (when refresh, instantiate the injected object)
    1. MapperFactoryBean#getObject returns Mapper's proxy class: MapperProxy.class.
    2. There is an invoke method in MapperProxy.class, which will be used.

Instantiation step

  1. When using or obtaining these bean s, Spring first obtains the interface type you want to use.
  2. Traverse all beans in the current container and compare them one by one. When there is a match, it will be returned directly. However, Mapper interface has not been instantiated yet, so it is not found. When traversing FactoryBean, getObjectType method will be called to compare the return value with the interface type you want to use.
  3. When the return type of FactoryBean matches, Spring will call the getObject method of FactoryBean to create the object.
  4. During the creation process, the jdk dynamic proxy is made through the previously passed in interface to complete the proxy logic of MyBatis.
  5. After the object is created, judge by the return value of the isSingleton method. If it is a singleton object, cache the object. And return.

1. Configure class label @ MapperScan("XXX")

2. Create mappercannerconfigurer

Corresponding method: mappercannerregister#registerbeandefinitions

summary

Build a BeanDefinition of type mappercannerconfigurer and register it into the Spring container.

technological process

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. Scan Mapper interface to BeanDefinition

Corresponding method: mappercannerconfigurer #postprocessbeandefinitionregistry

summary

  1. Create a custom scanner classpathmappercanner (inherited from Spring's ClassPathBeanDefinitionScanner), which scans all interfaces under the package path you pass in and converts them to BeanDefinition
  2. Traverse the BeanDefinition converted in the previous step, modify their BeanClass to MapperFactoryBean class (implement FactoryBean interface), and lay the foundation for setting dynamic proxy later.

technological process

Process calling this method

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 here is MapperFactoryBean.

4. During injection: create Mapper proxy class

summary

        In general, our Controller will inject the Service using @ Autowired, and the Service will inject Mapper by default. At this time, the

        MapperFactoryBean#getObject returns Mapper's proxy class: MapperProxy.class.

        There is an invoke method in MapperProxy.class, which will be used.

technological process

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 is of type MybatisMapperRegistry
                    mybatisMapperRegistry.getMapper(type, sqlSession); // MybatisConfiguration.class
                      getMapper(Class type, SqlSession sqlSession) //MybatisMapperRegistry.class

Mybatismappperregistry inherits MapperRegistry

MybatisMapperRegistry#getMapper is as follows:

getMapper(Class type, SqlSession sqlSession) //MybatisMapperRegistry.class
    mapperProxyFactory.newInstance(sqlSession) //MybatisMapperRegistry.class
        newInstance(SqlSession sqlSession) // MybatisMapperProxyFactory.class
            // Mybatismappperproxyfactory.class (all the codes below are here)
            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);
        }
    }
    // Omit other codes
}

5. Call

summary

We usually use @Autowired to inject a Mapper interface and then call the method.

Inject Mapper:

As explained in the above "when injecting: create Mapper proxy class", the proxy containing MapperProxy will be returned during injection.

Call Mapper method:

Will call MapperProxy#invoke

  1. Create an instance to execute the method. (there are DefaultMethodInvoker and PlainMethodInvoker)
    1. Call MapperMethod#execute method

technological process

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

Track the latter

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

It then calls 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;
    }
}

Other web sites

FactoryBean introduction and mybatis spring application
Use of FactoryBean in Spring_ goodluckwj's blog - CSDN blog
Handwritten mybatis spring of spring_ Blog of yangyangping20108 - CSDN blog

Topics: Spring Spring Boot