[Spring] the difference between FactoryBean and BeanFactory

Posted by Serberus on Wed, 02 Mar 2022 01:44:21 +0100

Use of FactoryBean

FactoryBean

FactoryBean is an interface. The interface declaration is as follows:

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

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

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

FactoryBean is a kind of factory bean. Unlike ordinary beans, FactoryBean is a bean that can generate beans.

Example of FactoryBean

package com.morris.spring.entity;

import org.springframework.beans.factory.FactoryBean;

public class UserFactoryBean implements FactoryBean<User> {

    @Override
    public User getObject() throws Exception {
        return new User("morris", 18);
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}
package com.morris.spring.demo.annotation;

import com.morris.spring.entity.UserFactoryBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * FactoryBean Use of
 */
public class FactoryBeanDemo {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserFactoryBean.class);
		System.out.println(applicationContext.getBean("&userFactoryBean"));
		System.out.println(applicationContext.getBean("userFactoryBean"));
		System.out.println(applicationContext.getBean("userFactoryBean"));
	}
}

The operation results are as follows:

com.morris.spring.entity.UserFactoryBean@1ec78b9
User(username=morris, age=18)
User(username=morris, age=18)

The following two conclusions can be drawn from the operation results:

  1. Userfactorybean will inject two objects into the Spring manager, one named & userfactorybean, which represents the FactoryBean itself, and the other named userfactorybean, which represents the object returned by the getObject() method of FactoryBean.

  2. The getObject() method of UserFactoryBean will be called only when it is used, that is, it will be instantiated only when it is used, and then added to the Spring container for management, which is a bit similar to the lazy loading of Bean.

From the above phenomenon, it is easy to think that there will be two objects in the singletonObjects container of Spring's first level cache, one is a FactoryBean object named & userFactoryBean, and the other is a Dog object named userFactoryBean. Is this really the case?

Purpose of FactoryBean

Our conventional beans use the reflection of Class to obtain specific instances. If the bean acquisition process is complex, a large number of attribute values need to be configured for conventional xml configuration. At this time, we can use FactoryBean to implement this interface and initialize the bean in its getObject() method.

Usage scenario:

  • MybatisSqlSessionFactoryBean of mybatis

Comparison with BeanFactory

  • BeanFactory is a bean factory, a factory class (Interface), which is responsible for producing and managing beans. It is the lowest interface of ioc container, an ioc container, and an ioc container used by spring to manage and assemble ordinary beans (these beans are called ordinary beans).

  • FactoryBean is a bean. On the basis of IOC container, a simple factory mode and decoration mode are added to the implementation of bean. It is a factory bean that can produce objects and decoration objects. After being managed by spring, the produced objects are determined by getObject() method.

Source code reading

Initialization of FactoryBean

During Spring startup, beanfactoryprocessor will be used to collect the classes to be managed by Spring and encapsulate them into a BeanDefinition. FactoryBean class is no exception. Then it will traverse all beandefinitions for instantiation and initialization.

DefaultListableBeanFactory#preInstantiateSingletons

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

public void preInstantiateSingletons() throws BeansException {
	if (logger.isTraceEnabled()) {
		logger.trace("Pre-instantiating singletons in " + this);
	}

	// Iterate over a copy to allow for init methods which in turn register new bean definitions.
	// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
	List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

	// Trigger initialization of all non-lazy singleton beans...
	// Traverse all BD S and start instantiation
	for (String beanName : beanNames) {
		// Copy the attributes in the parent BD to the child BD
		RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
		/**
		 * It is not abstract, it is singleton, and it can be instantiated only if it is not lazy loading
		 */
		if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
			// Instantiation of factoryBean
			if (isFactoryBean(beanName)) {
				Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
				if (bean instanceof FactoryBean) {
					FactoryBean<?> factory = (FactoryBean<?>) bean;
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(
								(PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
								getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
			}
			else {
				// Instantiation of ordinary bean s
				getBean(beanName);
			}
		}
	}
... ...

If a Bean implements the FactoryBean interface, it will call getBean() just like an ordinary Bean, but will add &.

AbstractBeanFactory#isFactoryBean

Determine whether a bean is factorybean.

org.springframework.beans.factory.support.AbstractBeanFactory#isFactoryBean(java.lang.String)

public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
	String beanName = transformedBeanName(name);
	Object beanInstance = getSingleton(beanName, false);
	if (beanInstance != null) {
		// If there is an instance, judge whether the instance is an instance of FactoryBean
		return (beanInstance instanceof FactoryBean);
	}
	// No singleton instance found -> check bean definition.
	if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
		// No bean definition found in this factory -> delegate to parent.
		return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
	}
	// If there is no instance, judge whether the class(targetType) in BD implements FactoryBean
	return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}

Determine whether the bean implements the FactoryBean interface.

protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
	Boolean result = mbd.isFactoryBean;
	if (result == null) {
		Class<?> beanType = predictBeanType(beanName, mbd, FactoryBean.class);
		result = (beanType != null && FactoryBean.class.isAssignableFrom(beanType));
		mbd.isFactoryBean = result;
	}
	return result;
}

AbstractBeanFactory#doGetBean

The creation process of FactoryBean instance is consistent with that of ordinary Bean.

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {

	// If the beanName is a FactoryBean, the beanName must start with &
	// Transformatedbeanname will remove & from name
	String beanName = transformedBeanName(name);
... ...

protected String transformedBeanName(String name) {
	return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

org.springframework.beans.factory.BeanFactoryUtils#transformedBeanName

public static String transformedBeanName(String name) {
	Assert.notNull(name, "'name' must not be null");
	if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
		return name;
	}
	return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
		do {
			// Remove&
			beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
		}
		while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
		return beanName;
	});
}

From the above source code, it can be found that the initialization process of FactoryBean is basically the same as that of ordinary Bean. Finally, userfactorybean will be cached in the primary cache container singletonObjects in Spring, and the beanName is userfactorybean, Instead of & userfactorybean (if you judge that the Bean is a FactoryBean in the calling process, you will prefix the beanName with &, but remove the & in the beanName in the underlying calling process).

In addition, you can find factorybean The GetObject () method was not called.

Call of getObject() method

The Bean produced by FactoryBean can only be used when calling FactoryBean The GetObject () method will be created, while the FactoryBean The call to GetObject () occurs on FactoryBean When getobject() returns the acquisition of Bean of value type, for example:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserFactoryBean.class);
System.out.println(applicationContext.getBean("&userFactoryBean")); // Get FactoryBean itself
System.out.println(applicationContext.getBean("userFactoryBean")); // Trigger factorybean getObject()
System.out.println(applicationContext.getBean("userFactoryBean")); // Factorybean will only be triggered once getObject()

FactoryBean. The called process of GetObject () method is as follows:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {

	// If the beanName is a FactoryBean, the beanName must start with &
	// Transformatedbeanname will remove & from name
	String beanName = transformedBeanName(name);
	Object bean;

	// Eagerly check singleton cache for manually registered singletons.
	// Query cache
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isTraceEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		// The factoryBean will be specially processed here
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
... ...

When we call ApplicationContext When getBean ("userfactorybean") is executed into the above method, it will first get the userfactorybean in the first level cache according to the userfactorybean, and then execute the getObjectForBeanInstance() method. Here, the factoryBean will be specially processed, and the ordinary bean will return directly.

protected Object getObjectForBeanInstance(
		Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

	// Don't let calling code try to dereference the factory if the bean isn't a factory.
	// Whether name & name has started
	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if (!(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
		}
		if (mbd != null) {
			mbd.isFactoryBean = true;
		}
		return beanInstance;
	}

	// Now we have the bean instance, which may be a normal bean or a FactoryBean.
	// If it's a FactoryBean, we use it to create a bean instance, unless the
	// caller actually wants a reference to the factory.
	// Ordinary bean s return directly
	if (!(beanInstance instanceof FactoryBean)) {
		return beanInstance;
	}

	Object object = null;
	if (mbd != null) {
		mbd.isFactoryBean = true;
	}
	else {
		// Here, the objects generated by the factorybean will be taken from the factoryBeanObjectCache
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		// Return bean instance from factory.
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		// Caches object obtained from FactoryBean if it is a singleton.
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		// getObject() of factorybean object will be called directly
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

Analyze the above code:

  1. At this time, the parameters are name=userFactoryBean, beanName=userFactoryBean, beanInstance=UserFactoryBean
  2. So the first two if will enter.
  3. There is no in factoryBeanObjectCache at present. It will be cached in factoryBeanObjectCache only after the getObject() method is executed once.
  4. Finally, userfactorybean will be called GetObject () method to instantiate the Bean and cache it in factoryBeanObjectCache.

Summary:

  1. A userfactorybean named userfactorybean is stored in the spring L1 cache
  2. A User named userFactoryBean is stored in the factoryBeanObjectCache

Topics: Java Spring Back-end