Spring Source Parsing-Singleton Scope (singleton) and Prootype Scope (multiple cases)

Keywords: Spring xml Struts Session

Scopes of Bean s

We are familiar with the scope of Bean s in Spring. In Spring, the most common scopes encountered in normal development are singleton and prototype. But besides singleton and prototype, Spring has other scopes. But these scopes have not been used in development. Spring has complete scopes:

Singleton: Singleton, spring's default creation bean is a singleton, with only one instance in each IOC container;

prototype: multiple instances, when spring creates multiple instances, it instantiates a new instance, but these instances have only and only one bean definition object;

Request: Limits the scope of a single bean definition to the lifetime of a single HTTP request. Each HTTP request has its own bean instance, which is created after a single bean definition. Effective Application Context only in the context of Web-aware Spring;

Session: Limits the scope of a single bean definition to HTTP lifecycle Session. Effective Application Context only in the context of Web-aware Spring.

application: Define a single bean into a lifecycle scope, ServletContext. Effective application Context only in the context of Web-aware Spring.

Websocket: Define a single bean into a lifecycle range of WebSocket. Effective Application Context only in the context of Web-aware Spring.

Of course, in addition to spring definitions, we can also customize scope ourselves. By implementing the interface of org. spring framework. beans. factory. config. Scope, we can customize our scope.

Singleton Scope (singleton)

In spring's IOC container, a single bean has and has only one instance object. That is to say, when you specify the definition of a bean as a single instance, the spring IOC container will only create an instance object of the only bean through a specific bean definition, and these created instances will be cached. In subsequent applications and requests, instance objects in the cache will be returned. Spring's concept of singletons and what we usually see in the design pattern The singleton pattern is different. Usually we see singletons in the design pattern book. Often, a singleton on each class loader can only be instantiated once, that is, there is only one instance object, but spring's singleton is more laterally that there is only one instance object in each container, so it requires the uniqueness of the container to ensure the singleton. How do you specify that a bean is a singleton? Spring provides two ways to achieve this: the first is XML configuration, which defines beans and specifies scope through xml; the second is annotation, which provides annotation @Scope to specify the scope of beans.

  1. xml implementation:
<bean id="testService" class="com.demo.DefaultTestService"/>
<bean id="testService" class="com.demo.DefaultTestService" scope="singleton"/>

scope does not specify that the default is singleton, so both of the above are acceptable.

  1. Annotation implementation
@Scope(value = "singleton")
@Service
public class DefaultTestService {
}

The singletons are created in these two ways. In spring, most of our classes are singletons. In singletons, we should try not to use stateful member variables. The member variables of singletons are shared, which leads to multi-threading problems. Therefore, there are differences between our spring controller and Struts'action implementations. Because spring controller is singleton, class members are seldom used. Variables, and the parameters that Struts interacts with the front end are basically class member variables, because Struts'actions are multiple cases to do so. So when we use spring to create bean s, we should use stateful class member variables carefully. If we want to use stateful class member variables, we must synchronize the operation, otherwise it will cause the problem of multi-threaded sharing variables. Of course, you can also create multiple instances to solve multithreading problems. We create two new service classes, which depend on one dao class at the same time. The dao class has a variable count initialized to 0. Look at the code below:

@Service
public class DefaultTestService {
    @Autowired
    private TestDao testDao;

    public Integer getCount() {
        return testDao.getCount();
    }

    public void IncCount() {
        testDao.setCount(testDao.getCount()+1);
    }
    public TestDao getTestDao() {
        return testDao;
    }

    public void setTestDao(TestDao testDao) {
        this.testDao = testDao;
    }
}

@Service
public class DefaultTestService1 {
    @Autowired
    private TestDao testDao;

    public Integer getCount() {
        return testDao.getCount();
    }

    public TestDao getTestDao() {
        return testDao;
    }

    public void setTestDao(TestDao testDao) {
        this.testDao = testDao;
    }
}
@Component
public class TestDao {

    private Integer count =0;

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }
}

We created the above three classes and then modified the variables in dao through different service s. The test code is as follows:

public class DemoTest extends BaseTest {
    protected static final Logger logger = LoggerFactory.getLogger(DemoTest.class);
    @Autowired
    private DefaultTestService defaultTestService;
    @Autowired
    private DefaultTestService1 defaultTestService1;
    @Test
    public void testGet() {
        defaultTestService.IncCount();
        logger.info("Other service Yes count After adding 1, the others service To get the results:"+defaultTestService1.getCount());
        logger.info("defaultTestService:"+defaultTestService.getTestDao());
        logger.info("defaultTestService1:"+defaultTestService1.getTestDao());

    }
}

The results of executing the above code are as follows:

2018-12-17 14:07:07.816 [main] DemoTest - Other service Yes count After adding 1, the others service To get the results: 1
2018-12-17 14:07:07.816 [main] DemoTest - defaultTestService:com.fcbox.pangu.manage.web.TestDao@46678e49
2018-12-17 14:07:07.816 [main] DemoTest - defaultTestService1:com.fcbox.pangu.manage.web.TestDao@46678e49

From the results, we can see that we modify the variable count in dao in one service, and get the new modified value in another service. We can know by printing that the dao injected into service is the same instance, so the variables in the modified instance are global visible, which is why we can not use the original with stateful class members in the singleton. Because. Of course, this example is not a rigorous multi-threaded example, but simply to illustrate that the singleton is the same instance in different call classes. Similarly, if we use member variables in the singleton, the operation of member variables must be synchronized. If you look at the source code of spring, you will find that there are many synchronization operations for member variable access in spring.

Prototype Scope (multiple cases)

As in the example above, multiple beans, when injecting dependencies, are injected from a new instance. The implementation of multiple beans is the same as that of singleton, except that the keyword is changed from singleton to prototype, which defines a multiple beans. Unlike other scope s, Spring does not manage the full life cycle of multiple beans. When a multi-instance bean is instantiated, the instance is handed over to the client, and the container does not record the instance. For the above example, if we set "dao" to several examples, let's look at the printed results. The results are as follows:

2018-12-17 14:14:21.518 [main] DemoTest - Other service Yes count After adding 1, the others service To get the result: 0
2018-12-17 14:14:21.518 [main] DemoTest - defaultTestService:com.fcbox.pangu.manage.web.TestDao@46678e49
2018-12-17 14:14:21.518 [main] DemoTest - defaultTestService1:com.fcbox.pangu.manage.web.TestDao@748e9b20

So, when a bean is set to multiple instances, each time a dependency injection occurs, a new instance is created and injected into the specific target bean. As you can see, the variable modification in TestDao by DefaultTestService will not affect other services, because different service dependency injection TestDao is different instances. It is clear from the result printing that the two instances are actually different.

Dependence on multiple instances of beans in a single bean

When a single bean relies on a multi-instance bean, the multi-instance bean will only be instantiated once when the dependency injection is completed. After the instantiation, it will not change in the next step. That is to say, it will only be instantiated once. If you want to get a multi-instance repeatedly at run time, it can not be executed by this way of dependency injection, because there are many instances. Instantiation happens only once, and no change happens next. In order to get different instances each time the program runs, we will introduce other scope s, which are implemented by proxy.

Introduction to Default Singleton Bean Registry

Default Singleton bean Registry can be simply understood as a registry of singletons. All singletons are cached in this class after they are created. This class has a series of singleton creation, registration and other operations. The methods in this class are used when creating beans if they are singletons. Looking at it, there are many cashe map objects as follows:

Once the singleton has been created, it will be saved in these map s. Look at the specific code:

protected void addSingleton(String beanName, Object singletonObject) {
		synchronized (this.singletonObjects) {
			this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
			this.singletonFactories.remove(beanName);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}

Many changes to class member variables in spring source code will be synchronized, so if we have member variables in our singleton, we need to synchronize, otherwise it will cause multi-threading problems. This code actually saves the instance object and the bean name of the singleton bean into the map. Next time if there is a need to inject the bean, it will go to cache to get it first. If it can't get it, it will go to the cache to create a new singleton. When the new one is finished, it will also be put into the cache. The specific process of creating instantiation of singleton is described below.

Instance process of singletons and multiple instances

First of all, both singleton and multi-instance instances are created through a unified bean creation process. Looking at their creation process in detail, all beans will be created to call the getBean method, as follows:

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

doGetBean will create a bean and continue to see its implementation. This function is quite long and not explained one by one. It only explains the main process. In this case, first of all, it will go to the cache to get the singleton. The code is as follows.

Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		if (logger.isDebugEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	} else{
		//Create single case
	}

First, we will go to spring container to find the singleton by name. If we get the singleton instance object, we will return it directly. If we can't get it, we will go to the else branch. That is to say, before creating the singleton, we will first use all the dependent beans in the target beans (not the @Autowired annotated beans, but xml to use the depend-on tag, or parameter values). The referenced beans) are all created before the target beans are created. Some of the codes are as follows:

//Get the definition of the target bean
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

// Instantiate dependent bean s
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
	for (String dep : dependsOn) {
		if (isDependent(beanName, dep)) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
		}
		registerDependentBean(dep, beanName);
		getBean(dep);
	}
}

As you can see from the source code, dependent beans are created before the target beans. After creating the dependent beans, he starts to create the target beans. First, he decides whether the definition of the beans is singleton or not. If so, he first goes to the cache to find whether the singleton has been created. If a direct return is created, otherwise the singleton is created and returned. If not, if not, he creates multiple instances, some of the code for creating the singleton is as follows:

if (mbd.isSingleton()) {
	sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
		@Override
		public Object getObject() throws BeansException {
			try {
				//
				return createBean(beanName, mbd, args);
			}
			catch (BeansException ex) {
				// Explicitly remove instance from singleton cache: It might have been put there
				// eagerly by the creation process, to allow for circular reference resolution.
				// Also remove any beans that received a temporary reference to the bean.
				destroySingleton(beanName);
				throw ex;
			}
		}
	});
	bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

If there are multiple instances, it will go into the process of creating multiple instances. Some codes are as follows:

else if (mbd.isPrototype()) {
	// It's a prototype -> create a new instance.
	Object prototypeInstance = null;
	try {
		beforePrototypeCreation(beanName);
		prototypeInstance = createBean(beanName, mbd, args);
	}
	finally {
		afterPrototypeCreation(beanName);
	}
	bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

The difference between the creation of singletons and multiple instances is that after the creation of singletons, spring records the status of his instances and saves them in the cache. The next creation will take precedence over the cached instances, while the creation of multiple instances will directly create the instantiated object and will not record his instances. From the above code, you can see that a code always appears, whether it is from the cached or created or created multiple instances, the created instance object is not returned directly, but is returned as a result after execution through this method. This code bean = getObjectForBeanInstance (prototype Instance, name, beanName, mbd); What role does it play? As we all know, spring has a class called bean factory, which implements the interface FactoryBean. Its instance is not a real bean. It is a factory class created by a specific class. So the factory bean can not be returned directly after instantiation. The concrete instantiated object of the bean factory is realized by the method getObject in the interface FactoryBean, so for example. If the factory class needs special treatment, this is exactly what this code does. Let's look at its implementation.

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.
	if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof 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 (!(beanInstance instanceof 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);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}

From the code, we can see that if it is a normal bean, it will return the instance object directly, and if it is a factory bean, it will execute to get the instantiated object in the factory.

summary

Whether it's single or multi-instance creation, it's just a step in spring initialization beans. Spring is from the definition of preparing beans to instantiating beans according to these definitions, and injecting various attributes into bean dependencies. Through in-depth analysis of spring, an important concept of spring, IOC container, has been generally understood. Whether it's the loading of the application context, the creation of bean definition s, or dependency injection and the creation of multiple instances, these are some of the important concepts and processes in Spring's IOC container.

Posted by Bennettman on Fri, 03 May 2019 06:10:38 -0700