Scan the qr code below or search the wechat official account, cainiao Feiyafei, you can follow the wechat official account, read more Spring source code analysis articles

First of all, FactoryBean and BeanFactory have very similar names, but they are two completely different concepts with very different uses. The BeanFactory is a Bean factory that, to some extent, is simply what we call a Spring container. It does the Bean creation, auto-assembly, and storing the singleton beans that have been created. A FactoryBean is a Bean by name, but it’s a special Bean. What’s so special about it? What’s so special about it that we can use it in the development process?

1. The use of FactoryBean

A FactoryBean is special in that it can register two beans to the container, one itself and the Bean represented by the return value of the factoryBean.getobject () method. Get a feel for what a FactoryBean can do with the following code example.

  • CustomerFactoryBean implements the FactoryBean interface. Overrides two methods in the interface. In getObejct(), an instance of UserService is returned. Userservice.class is returned in the getObjectType() method. Then we add the @Component annotation to the CustomerFactoryBean, which means we hand over the CustomerFactoryBean class to Spring to manage.
package com.tiantang.study.component;

import com.tiantang.study.service.UserService;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class CustomerFactoryBean implements FactoryBean<UserService> {
    @Override
    public UserService getObject(a) throws Exception {
        return new UserService();
    }

    @Override
    publicClass<? > getObjectType() {returnUserService.class; }}Copy the code
  • Defines a UserService class that prints a log line in the constructor.
package com.tiantang.study.service;

public class UserService {

    public UserService(a){
        System.out.println("userService construct"); }}Copy the code
  • Defines a configuration class AppConfig that indicates that Spring needs to scan packagescom.tiantang.study.component
package com.tiantang.study.config;

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

@Configuration
@ComponentScan("com.tiantang.study.component")
public class AppConfig {}Copy the code
  • Start the class
public class MainApplication {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("Container started complete");
        UserService userService = applicationContext.getBean(UserService.class);
        System.out.println(userService);
        Object customerFactoryBean = applicationContext.getBean("customerFactoryBean"); System.out.println(customerFactoryBean); }}Copy the code
  • The result of the console print

  • Before we start our source code analysis, we can look at these two questions:
    1. In the AppConfig class we only scancom.tiantang.study.componentThe classes in this package, according to our general understanding, should only have the CustomerFactoryBean class in the Spring container at this time, UserService is not scanned. When we test it, we pass itapplicationContext.getBean(UserService.class)Get the Bean from the container. Why?
    1. We know that by default, when we don’t have a custom naming policy, the Bean’s beanName in the Spring container is the first letter of the class name by default, so in this demo,CustomerFactoryBeanThe beanName of the singleton object of the class in the container is the customerFactoryBean. So we call the method getBean(beanName) to get the Bean by beanName, which in theory should return a singleton object of the CustomerFactoryBean class. However, when we print out the result, we find that the hashCode of this object is exactly the same as the hashCode of the userService object, indicating that the two objects are the same object. Why is this case? Why not an instance object of the CustomerFactoryBean class?
    1. Since you can’t get the customerFactoryBean singleton object from the beanName, how do you get it?

The answer to the above three questions can be solved by one answer: a FactoryBean is a special Bean. Our custom CustomerFactoryBean implements the FactoryBean interface, so when the CustomerFactoryBean is scanned into the Spring container, it actually registers two beans with the container, One is a singleton object of the CustomerFactoryBean class. The other one is the object returned by the getObject() method. In the demo, in the getObject() method we overwrote, we returned an instance of the UserService with new UserService(), So we can get an instance object of UserService from the container. If we want to retrieve the singleton object of the CustomerFactoryBean from the beanName, we need to add an am& in front of the beanName to retrieve the native object from the beanName.

public class MainApplication {

    public static void main(String[] args) {
        CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean"); System.out.println(rawBean); }}Copy the code

2. FactoryBean source code

Now that we know what FactoryBean does and how to use it, let’s look at how FactoryBean works from the source code.

  • In the Spring container startup stage, will be called to refresh () method, the refresh () has invoked the finishBeanFactoryInitialization () method, Will call to the beanFactory. PreInstantiateSingletons () method. So let’s take a look at the source code for this method. (For those unfamiliar with the refresh() method, check out my other two articles: the Container startup process in the Spring source code series, which looks at Bean creation through source code.)
public void preInstantiateSingletons(a) throws BeansException {
	// Get all the beanName from the container
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
	for (String beanName : beanNames) {
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		if(! bd.isAbstract() && bd.isSingleton() && ! bd.isLazyInit()) {// If the bean is a FactoryBean based on its beanName, the bean that implements the FactoryBean interface returns true
			// When beanName is customerFactoryBean, it returns true and goes into the if statement
			if (isFactoryBean(beanName)) {
				// Then use the getBean() method to get or create the singleton
				// Note that there is a FACTORY_BEAN_PREFIX concatenated for beanName
				// FACTORY_BEAN_PREFIX is a constant string, namely: &
				// So in this container startup phase, for customerFactoryBean, it should be: getBean("&customerFactoryBean")
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				Call getObject() of a FactoryBean to instantiate the object returned by getObject() at the start of the container
				if (bean instanceof FactoryBean) {
					finalFactoryBean<? > factory = (FactoryBean<? >) bean;boolean isEagerInit;
					if(System.getSecurityManager() ! =null && factory instanceofSmartFactoryBean) { isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) ((SmartFactoryBean<? >) factory)::isEagerInit, getAccessControlContext()); }else {
						isEagerInit = (factory instanceofSmartFactoryBean && ((SmartFactoryBean<? >) factory).isEagerInit()); }if (isEagerInit) {
						getBean(beanName);
					}
				}
			}
		}
	}
}
Copy the code
  • During the container startup phase, an instance object of the CustomerFactoryBean is created using the getBean() method. If the SmartFactoryBean interface is implemented and the isEagerInit() method returns true, the getObject() method is called during container startup to register the object that getObject() returns. Otherwise, the getObject() method is called back only when the object whose value getObject() returns is first fetched.
  • The doGetBean() method is called in getBean(). The following is the simplified source code for doGetBean(). As you can see from the source code, the getObjectForBeanInstance() method is eventually called.
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
	final String beanName = transformedBeanName(name);
	Object bean;

	Object sharedInstance = getSingleton(beanName);
	if(sharedInstance ! =null && args == null) {
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
	else {
		if (mbd.isSingleton()) {
			
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
		else if (mbd.isPrototype()) {
			bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
		}
		else{ bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); }}return (T) bean;
}
Copy the code
  • The getObjectForBeanInstance() method determines whether the bean is a FactoryBean, and if not, returns the bean directly. If it is a FactoryBean and name begins with an ampersand, it represents the native object from which the FactoryBean was obtained, and will be returned directly. If name does not begin with an ampersand, it means that you want to get the object returned by the getObject() method in FactoryBean. Will try from FactoryBeanRegistrySupport factoryBeanObjectCache this cache map of a class, if the cache exists, it returns, if not, is to call getObjectFromFactoryBean () method. GetObjectForBeanInstance () ¶
protected Object getObjectForBeanInstance( Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if(! (beanInstanceinstanceof FactoryBean)) {
			throw newBeanIsNotAFactoryException(beanName, beanInstance.getClass()); }}// If the bean is not a factoryBean, the bean is returned directly
	// Or the bean is a factoryBean but the name begins with the & special symbol, which means that the original object of the factoryBean is to be obtained.
	// For example, if name = &customerFactoryBean, a Bean of type customerFactoryBean will be returned
	if(! (beanInstanceinstanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}
	// If it's a FactoryBean, it gets it from cache first. If the cache doesn't exist, the FactoryBean's getObject() method is called.
	Object object = null;
	if (mbd == null) {
		// Fetch from the cache. When did you put it in the cache? The first time the getObject() method is called, the return value is put into the cache.
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) { FactoryBean<? > factory = (FactoryBean<? >) beanInstance;if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		booleansynthetic = (mbd ! =null && mbd.isSynthetic());
		// In the getObjectFromFactoryBean() method the getObject() method is eventually calledobject = getObjectFromFactoryBean(factory, beanName, ! synthetic); }return object;
}
Copy the code
  • GetObjectFromFactoryBean () method, mainly by calling doGetObjectFromFactoryBean get bean () method, then the bean processing, finally into the cache. In addition, singleton beans are distinguished from non-singleton beans. Singleton beans are put into the cache after creation, while non-singleton beans are not put into the cache, but are recreated each time.
protected Object getObjectFromFactoryBean(FactoryBean<? > factory, String beanName,boolean shouldPostProcess) {
	// If the BeanFactory isSingleton() method returns true, getObject() is a singleton
	if (factory.isSingleton() && containsSingleton(beanName)) {
		synchronized (getSingletonMutex()) {
			// Check whether the cache exists again. (Dual detection mechanism, similar to writing thread-safe code)
			Object object = this.factoryBeanObjectCache.get(beanName);
			if (object == null) {
				/ / in doGetObjectFromFactoryBean () is the true calling getObject () method
				object = doGetObjectFromFactoryBean(factory, beanName);
				Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
				if(alreadyThere ! =null) {
					object = alreadyThere;
				}
				else {
					// The following is the post-processing, which is no different from the post-processing of normal beans
					if (shouldPostProcess) {
						if (isSingletonCurrentlyInCreation(beanName)) {
							return object;
						}
						beforeSingletonCreation(beanName);
						try {
							object = postProcessObjectFromFactoryBean(object, beanName);
						}
						catch (Throwable ex) {
							throw new BeanCreationException(beanName,
									"Post-processing of FactoryBean's singleton object failed", ex);
						}
						finally{ afterSingletonCreation(beanName); }}// Put it in the cache
					if (containsSingleton(beanName)) {
						this.factoryBeanObjectCache.put(beanName, object); }}}returnobject; }}/ / the singleton
	else {
		Object object = doGetObjectFromFactoryBean(factory, beanName);
		if (shouldPostProcess) {
			try {
				object = postProcessObjectFromFactoryBean(object, beanName);
			}
			catch (Throwable ex) {
				throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); }}returnobject; }}Copy the code
  • DoGetObjectFromFactoryBean () method of the logic is simple, direct call the FactoryBean getObject () method. Part of the source code is as follows
private Object doGetObjectFromFactoryBean(finalFactoryBean<? > factory,final String beanName)
			throws BeanCreationException {

	Object object;
	if(System.getSecurityManager() ! =null) {
		AccessControlContext acc = getAccessControlContext();
		try {
			object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
		}
		catch (PrivilegedActionException pae) {
			throwpae.getException(); }}else {
		// Call getObject()
		object = factory.getObject();
	}
	return object;
}
Copy the code
  • Spring code is written so well that almost every method has high reusability, which leads to a deep hierarchy of methods within methods. Therefore, the process of FactoryBean creation is summarized in a flow chart.

  • After reading this section of the source code analysis, this time can understand the demo print results.

3. The application scenario of FactoryBean — Spring-MyBatis plugin principle

Now that you know how FactoryBeans work, what scenarios have you seen factoryBeans used in your daily work? If not noticed, the author here with Spring integration of Mybatis principle to example it.

  • We can start by recalling what we need to do when using Mybatis alone. Add dependencies, configure the data source, create the SqlSessionFactory, and the environment is set up. (If your memory is vague, please refer to the official document: mybatis.org/mybatis-3/g…) .
  • When we integrate Mybatis into Spring, we also add Mybatis dependencies, but we also need to add an additional JAR package:mybatis-springThen configure the data source. One final configuration is required, and if you are using XML, the following configuration is required:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
Copy the code
  • If you are not using JavaConfig, you need to do the following:
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    return sqlSessionFactoryBean;
}
Copy the code
  • We found that both XML and JavaConfig registered an SqlSessionFactoryBean to the container. We know from the class name that this is a FactoryBean. When we use Mybatis alone, we need to create an SqlSessionFactory, whereas when we integrate Mybatis with Spring, we need an SqlSessionFactoryBean, so we can guess, The SqlSessionFactoryBean registers an SqlSessionFactory with the Spring container by virtue of the FactoryBean’s specificity. If you look at the source code for SqlSessionFactoryBean, you can see that it implements the FactoryBean interface and overwrites the getObejct method to register an SqlSessionFactory with the container via the getObject() method.
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean.ApplicationListener<ApplicationEvent> {
	/ /... Omit other code
	
	public SqlSessionFactory getObject(a) throws Exception {
	if (this.sqlSessionFactory == null) {
	  afterPropertiesSet();
	}

	return this.sqlSessionFactory; }}Copy the code
  • SqlSessionFactory is a property of the SqlSessionFactoryBean, which is assigned by calling the afterPropertiesSet() method. (Because SqlSessionFactoryBean implements the InitializingBean interface, the afterPropertiesSet() method can be called back when Spring initializes the Bean.)
public void afterPropertiesSet(a) throws Exception {
    // The buildSqlSessionFactory() method is initialized according to mybatis configuration.
	this.sqlSessionFactory = buildSqlSessionFactory();
}
Copy the code
  • There is one other place where FactoryBeans are also leveraged in the Spring and MyBatis integration. We usually scan our Mapper files with MapperScan annotations during development. MapperScan annotations, added the Import (MapperScannerRegistrar. Class), (about the Import annotations, can see the author’s another article: mp.weixin.qq.com/s/y_2Z9m0ge…).
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
Copy the code
  • Through the registerBeanDefinitions() method of MappererRegistrar, the ScannerRegistrar will scan the Mapper and resolve it into a BeanDefinition. The beanClass property is set to mapPerFactoryBean.class instead of the class we defined for Mapper. This means that for every Mapper interface we define, loaded into Spring, we end up with a MapperFactoryBean.
  • Let’s take a look at what the MapperFactoryBean class does. Below is the partial source code for the MapperFactoryBean class. From the source code, we found that it implements the FactoryBean interface, overriding three methods in the interface. In the getObject() method, by callinggetSqlSession().getMapper(this.mapperInterface)An object is returned. This line of code ends up calling newInstance() to the MapperProxyFactory, creating a proxy object for each Mapper.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
	@Override
	public T getObject(a) throws Exception {
		return getSqlSession().getMapper(this.mapperInterface);
	}

	@Override
	public Class<T> getObjectType(a) {
		return this.mapperInterface;
	}

	@Override
	public boolean isSingleton(a) {
        // Returns true to make the Mapper interface a singleton
		return true; }}Copy the code
  • MapperProxyFactory class source. The final step is to call the JDK’s dynamic proxy to create a dynamic proxy for our Mapper. (MyBatis framework is the DAO layer implemented through dynamic proxy)
public class MapperProxyFactory<T> {

  protected T newInstance(MapperProxy<T> mapperProxy) {
  	// JDK dynamic proxy
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    returnnewInstance(mapperProxy); }}Copy the code
  • So each Mapper interface we write will correspond to a MapperFactoryBean, and each MapperFactoryBean’s getObject() method will eventually use JDK dynamic proxies to create an object, so each Mapper interface will correspond to a proxy object. This enables the integration of Spring and MyBatis.

4. Mybatis – spring – the boot – starter principle

The principle of integrating MyBatis in SpringBoot is the same, although the MyBatis -spring-boot-starter dependency is used, but the final integration principle is exactly the same as spring. The main feature of SpringBoot is automatic configuration, and the principle of integration with other frameworks is little changed from Spring.

  • Mybatis -spring-boot-autoconfigure jar package mybatis-spring-boot-autoconfigure jar package mybatis-spring-boot-autoconfigure jar package mybatis-spring-boot-autoconfigure jar package The automatic configuration of MyBatis is realized by the MybatisAutoConfiguration class in this JAR package. An SqlSessionBean is registered to the container using the getObject() method of the SqlSessionFactoryBean.
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
	@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        // Omit some code
        returnfactory.getObject(); }}Copy the code

5. To summarize

  • This article focuses on the functions and usage scenarios of FactoryBeans. First, the FactoryBean is demonstrated by demo, and then the principle of FactoryBean is analyzed with Spring source code.
  • Then through the source code analysis FactoryBean in Spring and MyBatis integration process plays an important role. The first is to provide an SqlSessionFactory and the second is to create JDK dynamic proxy objects for each Mapper.
  • Finally, through a small source code analysis of SpringBoot integration Mybatis principle. In fact, Spring and MyBatis integration principle is the same, so the focus is still Spring source understanding.

6. I guess you like it

  • See the Bean creation process from the source code
  • The Spring source code series container startup process
  • In the Spring! The most! The most! Important afterprocessor! No one!!
  • @ Import and @ EnableXXX
  • Write a Redis/Spring integration plug-in by hand
  • Why should dynamic proxies in the JDK be implemented based on interfaces and not inheritance?

You can scan the qr code below to follow the wechat official account, Cainiao Feiyafei, read more Spring source code together.