preface

While learning about the IOC container in Spring Core, you will definitely come into contact with the BeanFactory, the most basic IOC container in Spring. This should be the first class you will encounter when learning Spring source code. Spring also has the FactoryBean class, which is spelled very similar and used very often. Some Spring questions will also ask you what the difference is between the two.

Here’s the conclusion:

  • BeanFactory: The IoC container in Spring, the Factory of all Spring beans
  • FactoryBean: a Bean, not a simple Bean, a FactoryBean that can generate objects or modify object generation. Its implementation is similar to that of the factory and decorator patterns in the design pattern

In the process of learning the source code of Spring and other open source projects, I found that FactoryBean is a class that some frameworks often use when integrating Spring. This paper specifically describes the simplicity, utility and specific application expansion of FactoryBean.

What is FactoryBean

There are two types of beans in Spring, ordinary beans and factory beans, or factoryBeans.

In general, Spring instantiates a bean through reflection using the bean’s class attribute to specify the implementation class. In some cases, the process of instantiating beans is complex, and the traditional way of providing a lot of configuration information in

is limited by the flexibility of configuration, so coding may yield a simple solution. Spring provides a org. Springframework. Beans. Factory. FactoryBean factory class interface, users can customize by implementing the interface logic instantiation of the bean. (See some examples below for more insight)

So, when the process of configuring a

is very complex, and the creation process involves many other beans and complex logic, it is difficult to configure with XML, so consider using FactoryBeans.

The interface definition

package org.springframework.beans.factory; public interface FactoryBean<T> { T getObject() throws Exception; Class<? > getObjectType(); boolean isSingleton(); }Copy the code

Use on some open source frameworks

MyBatis-Spring # SqlSessionFactoryBean

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="* * *" />
        <property name="configLocation" value="* * *"/>
        <property name="mapperLocations" value="* * *"/>
    </bean>
Copy the code
public class SqlSessionFactoryBean
    implements FactoryBean<SqlSessionFactory>, InitializingBean.ApplicationListener<ApplicationEvent> {... }Copy the code

Alibaba’s open source distributed service framework Dubbo # ReferenceBean

<?xml version="1.0" encoding="UTF-8"? >
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="Http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
 
    <! -- Consumer application name, used to calculate dependencies, not matching conditions, not the same as provider -->
    <dubbo:application name="consumer-of-helloworld-app"  />
 
    <! Expose discovery service addresses with multicast broadcast registries -->
    <dubbo:registry address=Multicast: / / 224.5.6.7: "1234" />
 
    <! Create a remote service proxy that can use demoService as a local bean -->
    <dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
</beans>
Copy the code

< dubbo: reference corresponding beans are com. Alibaba. The dubbo. Config. Spring. ReferenceBean class

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean.ApplicationContextAware.InitializingBean.DisposableBean {... }Copy the code

Development practices

involving

  • ProduceLocation Production location
  • Material Material
  • ProductFactoryBean ProductFactoryBean
  • The Product Product
  • Boostrap start class
  • Test-config. XML tests the configuration file

ProduceLocation

@Data
public class ProduceLocation {

    private String locationName;

    private double distanceKm;

    private double pricePerPerKm;
}
Copy the code

Material

@Data
public class Material {

    private String name;

    private double pricePerGram;

    private double weight;
}
Copy the code

Product

@Data
@Builder
public class Product {

    private Material material;

    private ProduceLocation location;

    private double price;
}
Copy the code

ProductFactoryBean

@Setter
@Getter
public class ProductFactoryBean implements FactoryBean<Product> {

    private Material material;

    private ProduceLocation produceLocation;

    @Override
    public Product getObject(a) throws Exception {
        return Product.builder()
                .location(produceLocation)
                .material(material)
                .price(cal(material, produceLocation))
                .build();
    }

    private double cal(Material material, ProduceLocation produceLocation) {
        return material.getPricePerGram() * material.getWeight()
                + produceLocation.getDistanceKm() * produceLocation.getPricePerPerKm();
    }

    @Override
    publicClass<? > getObjectType() {return Product.class;
    }

    @Override
    public boolean isSingleton(a) {
        return false; }}Copy the code

test-config.xml

<? The XML version = "1.0" encoding = "utf-8"? > <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd "> < bean id =" produceLocation" Class = "base. The ioc. FactoryBeanDemoSet. ProduceLocation" > < property name = "locationName" value = "hangzhou" / > < property Name ="pricePerPerKm" value="151.01"/> <property name="distanceKm" value="3.1"/> </bean> < id="material" Class = "base. The ioc. FactoryBeanDemoSet. Material" > < property name = "name" value = "chocolate" / > < property name = "pricePerGram" value="100"/> <property name="weight" value="50"/> </bean> <bean id="product" class="base.ioc.FactoryBeanDemoSet.ProductFactoryBean"> <property name="material" ref="material"/> <property name="produceLocation" ref="produceLocation"/> </bean> </beans>Copy the code

Boostrap

/ * * *@author Richard_yyf
 * @version1.0 2019/9/21 * /
public class Bootstrap {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
        Product product = (Product) context.getBean("product"); System.out.println(product.toString()); }}Copy the code

The output

Product(Material = material (name= chocolate bean, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName= hangzhou, DistanceKm = 3.1, pricePerPerKm = 151.01), the price = 5468.131)Copy the code

The above configuration can also be done in Java Config mode.

As an example of a simple business, You can also use FactoryBean to encapsulate the API usage of open source tools, such as httpClient creation processes such as timeouts, connection pool sizes, HTTP proxies, and so on.

features

Given a FactoryBean with id= MyBean, getBean(” myBean “) gets the object instance created by the FactoryBean, and getBean(“& MyBean “) gets the actual object of the FactoryBean itself.

Based on the demo above, run the following code

/ * * *@author Richard_yyf
 * @version1.0 2019/9/21 * /
public class Bootstrap {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
        // Product product = (Product) context.getBean("product");
        // System.out.println(product.toString());
        
        FactoryBean<Product> factoryBean = (ProductFactoryBean) context.getBean("&product"); System.out.println(factoryBean.getObject().toString()); }}Copy the code

Output

Product(Material = material (name= chocolate bean, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName= hangzhou, DistanceKm = 3.1, pricePerPerKm = 151.01), the price = 5468.131)Copy the code

Corresponding source

First directly lock the corresponding logical source code,

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

		// Don't let calling code try to dereference the factory if the bean isn't a factory.
        / / BeanFactoryUtils. IsFactoryDereference (name) method to judge whether the name prefixed by &
		if(BeanFactoryUtils.isFactoryDereference(name) && ! (beanInstanceinstanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
		}
        
       // 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.
		if(! (beanInstanceinstanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
			return beanInstance;
		}

		Object object = null;
		if (mbd == null) {
			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);
			}
			booleansynthetic = (mbd ! =null&& mbd.isSynthetic()); object = getObjectFromFactoryBean(factory, beanName, ! synthetic); }return object;
    }
Copy the code
  1. BeanFactoryUtils. IsFactoryDereference (name) determine whether in obtaining FactoryBean references

    	// Is not empty and begins with "&"
    	public static boolean isFactoryDereference(String name) {
    		return(name ! =null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
    	}
    	String FACTORY_BEAN_PREFIX = "&";
    Copy the code
  2. BeanInstance not FactoryBean if came in, but the caller is used to obtain FactoryBean references, throw BeanIsNotAFactoryException anomalies

  3. If the caller is getting a reference to a FactoryBean, and beanInstance is a FactoryBean, return beanInstance directly

  4. If the incoming beanInstance is a plain bean, return beanInstance directly

  5. Create a corresponding bean from the incoming FactoryBean