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
- Mark @ mapperscan ("package path of mapper interface") in the configuration class
- @There is @ import (mappercannerregister. Class) above mappercan. Inject this class into Spring.
- Create MapperScannerConfigurer for Mapper scanner
- Mappercannerregister class implements importbeandefinitionregister, and Spring will automatically call back the registerBeanDefinitions method of this interface.
- Mappercannerregister#registerbeandefinitions method
- Build mappercannerconfigurer and register it into the Spring container.
- Mappercannerregister#registerbeandefinitions method
- Mappercannerregister class implements importbeandefinitionregister, and Spring will automatically call back the registerBeanDefinitions method of this interface.
- Create a scanner and call its scan method.
- Mappercannerconfigurer implements the BeanDefinitionRegistryPostProcessor interface. Spring will automatically call back the postProcessBeanDefinitionRegistry method of this interface.
- postProcessBeanDefinitionRegistry method:
- Create a custom scanner: ClassPathMapperScanner (inherited from Spring's ClassPathBeanDefinitionScanner), and call its scan method, that is, ClassPathMapperScanner#scan
- ClassPathMapperScanner#scan
- This method is not overridden. It is the implementation of calling the parent class (ClassPathBeanDefinitionScanner)
- That is, ClassPathBeanDefinitionScanner#scan
- Call to: ClassPathBeanDefinitionScanner#doScan
- This method is overridden by classpathmappercanner, so it is called to: classpathmappercanner#doscan
- This method is not overridden. It is the implementation of calling the parent class (ClassPathBeanDefinitionScanner)
- postProcessBeanDefinitionRegistry method:
- Mappercannerconfigurer implements the BeanDefinitionRegistryPostProcessor interface. Spring will automatically call back the postProcessBeanDefinitionRegistry method of this interface.
- Scan all Mapper interfaces under the package path you specify and convert them to BeanDefinition
- ClassPathMapperScanner#doScan
- ClassPathMapperScanner#processBeanDefinitions
- Scan all Mapper interfaces under the package path you specify and convert them to BeanDefinition.
- Set the BeanClass of each BeanDefinition to MapperFactoryBean.class (implement the FactoryBean interface)
- 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.
- Pass in the interface represented by the BeanDefinition through the definition.getPropertyValues().add() method.
- After setting all beandefinitions in the above steps, all beandefinitions are registered in BeanFactory, which manages these factorybeans.
- ClassPathMapperScanner#processBeanDefinitions
- ClassPathMapperScanner#doScan
- When injecting, instantiate Mapper's proxy class (when refresh, instantiate the injected object)
- MapperFactoryBean#getObject returns Mapper's proxy class: MapperProxy.class.
- There is an invoke method in MapperProxy.class, which will be used.
Instantiation step
- When using or obtaining these bean s, Spring first obtains the interface type you want to use.
- 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.
- When the return type of FactoryBean matches, Spring will call the getObject method of FactoryBean to create the object.
- During the creation process, the jdk dynamic proxy is made through the previously passed in interface to complete the proxy logic of MyBatis.
- 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
- 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
- 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
- Create an instance to execute the method. (there are DefaultMethodInvoker and PlainMethodInvoker)
- 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