淺析 Spring IoC - Bean Scopes 和 生命週期

1. Bean scopesjava

    Scope用來聲明 IoC 容器中對象應該處的限定場景或者說該對象的存活空間,即 IoC 容器在對象進入相應的 scope 以前生成並裝配這些對象,在該對象再也不處於 scope 的限定後,容器一般會銷燬這些對象。下面是 Scope的分類:web

Scope Description
singleton

(Default) Scopes a single bean definition to a single object instance per spring

Spring IoC container.bootstrap

prototype Scopes a single bean definition to any number of object instances.
request

Scopes a single bean definition to the lifecycle of a single HTTP request;session

 that is, each HTTP request has its own instance of a bean created off the app

back of a single bean definition. Only valid in the context of a web-aware ide

Spring ApplicationContext.post

session

Scopes a single bean definition to the lifecycle of an HTTP Session.測試

 Only valid in the context of a web-aware Spring ApplicationContext.this

global 

session

Scopes a single bean definition to the lifecycle of a global HTTP Session

Typically only valid when used in a portlet context. Only valid in the context 

of a web-aware Spring ApplicationContext.

application

Scopes a single bean definition to the lifecycle of a ServletContext. Only valid

 in the context of a web-aware Spring ApplicationContext.

    singleton:單一實例,即一個容器中只存在一個這樣的實例,全部對該類型 Bean的依賴都是用這一單一實例。此外,singleton 類型的 Bean 定義,從容器啓動,到它第一次被請求而實例化開始,只要容器不銷燬或者退出,該類型 Bean 的單一實例就會一直存活。

    prototype:容器在接受到該類型對象的請求時,每次都會從新生成一個新的對象給請求方。但容器將對象實例返回給請求方後,就再也不擁有該對象的引用,請求方須要本身負責當前對象後繼生命週期的管理工做。

    request、session、global session、application 只適用於 web程序,一般是和 XmlWebApplicationContext 共同使用。

    request:Spring 容器,即 XmlWebApplicationContext 會爲每一個 HTTP 請求建立一個新的對象,當請求結束後,該對象的生命週期即結束。若是同時又10個 HTTP 請求,容器會分別針對這10個請求建立10個新的對象實例,且實例之間互不干擾。

    session:Spring 容器會爲每一個獨立的 session 建立新的對象實例,比 request scope的 bean存活更長的時間。用戶登陸信息通常是用的最多。

    global session:只有應用在基於porlet的web應用程序中才有意義,他映射到porlet的global範圍的session,若是普通的servlet的web 應用中使用了這個scope,容器會把它做爲普通的session的scope對待。

    application:暫無。

    對於singleton 和 prototype 能夠作個實驗:

<?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.xsd">

	<!-- <import resource="beans2.xml"/> -->

    <bean id="userDAO" class="com.dao.impl.UserDAOImpl" scope="singleton"/>
    
    <bean id="userService" class="com.service.impl.UserServiceImpl" scope="prototype">
        <property name="userDAO" ref="userDAO" />
    </bean>
    
</beans>

    測試 Code:

public class UserServiceTest {

	@Before
	public void setUp() throws Exception {
	}

	@Test
	public void testAddUser() throws Exception {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		
		UserDAOImpl dao1 = (UserDAOImpl) ctx.getBean("userDAO");
		UserDAOImpl dao2 = (UserDAOImpl) ctx.getBean("userDAO");
		
		UserServiceImpl service = (UserServiceImpl) ctx.getBean("userService");
		UserServiceImpl service2 = (UserServiceImpl) ctx.getBean("userService");
		
		System.out.println(dao1 == dao2);
		
		System.out.println(service == service2);
	}
}

    結果: true  false

2. Bean 建立的時機:

    IoC 容器初始化時會預先對非延遲加載的單例對象進行初始化,其餘都是在第一次調用 getBean 時被建立。

    org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons()

public void preInstantiateSingletons() throws BeansException {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("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<String>(this.beanDefinitionNames);

		// Trigger initialization of all non-lazy singleton beans...
		for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
					boolean isEagerInit;
					if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
						isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
							@Override
							public Boolean run() {
								return ((SmartFactoryBean<?>) factory).isEagerInit();
							}
						}, getAccessControlContext());
					}
					else {
						isEagerInit = (factory instanceof SmartFactoryBean &&
								((SmartFactoryBean<?>) factory).isEagerInit());
					}
					if (isEagerInit) {
						getBean(beanName);
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		for (String beanName : beanNames) {
			Object singletonInstance = getSingleton(beanName);
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged(new PrivilegedAction<Object>() {
						@Override
						public Object run() {
							smartSingleton.afterSingletonsInstantiated();
							return null;
						}
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

3. 延遲初始化 Bean

    指在 IoC 容器啓動時,並不初始化 延遲初始化的 Bean,而是在使用時纔會建立以及初始化 Bean。

    配置方式:在 <bean> 標籤上指定 lazy-init 屬性值爲 "true",如: <bean id="xxx" class="xxx" lazy-init="true" />

    Spring 容器預先初始化 Bean 一般能幫助咱們提早發現配置錯誤,因此通常狀況下不建議開啓lazy-init。除非有某個 Bean 可能須要加載很大資源,並且在整個應用程序生命週期中極可能使用不到,那麼能夠設置爲 延遲初始化。

    延遲初始化的 Bean 一般會在第一次使用時被初始化;或者在 被非延遲初始化 Bean 做爲依賴對象注入時,隨着該 Bean 的初始化而初始化 lazy-init Bean。

4. 自動裝配(Autowire)

    Autowire 指由 spring 容器自動地注入依賴對象。

Mode Explanation
no

(Default) No autowiring. Bean references must be defined via a ref element. 

Changing the default setting is not recommended for larger deployments, 

because specifying collaborators explicitly gives greater control and clarity. 

To some extent, it documents the structure of a system.

byName

Autowiring by property name. Spring looks for a bean with the same name

 as the property that needs to be autowired. For example, if a bean definition 

is set to autowire by name, and it contains a master property (that is, it has a 

setMaster(..) method), Spring looks for a bean definition named master

and uses it to set the property.

byType

Allows a property to be autowired if exactly one bean of the property type

 exists in the container. If more than one exists, a fatal exception is thrown, 

which indicates that you may not use byType autowiring for that bean. 

If there are no matching beans, nothing happens; the property is not set.

constructor

Analogous to byType, but applies to constructor arguments. If there is not

 exactly one bean of the constructor argument type in the container, a fatal error is raised.

    目前 Spring 4.2.2 支持 "no","byName","byType","constructor" 四種自動裝配,默認是 "no" 指不支持自動裝配的。其中Spring 3.0 開始已經不推薦使用以前版本的 "autodetect" 自動裝配,推薦使用 Java 5+支持的(@Autowired)註解方式代替;自動裝配的好處是:減小構造器注入和setter注入配置,減小配置文件。

    配置方式:經過配置<bean>標籤的「autowire」屬性來改變自動裝配方式

<bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byType">
    <!-- <property name="userDAO" ref="userDAO" /> -->
</bean>

    (1)default:表示使用默認的自動裝配,默認的自動裝配須要在<beans>標籤中使用default-autowire屬性指定,其支持「no」、「byName 」、「byType」、「constructor」四種自動裝配。

<?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.xsd"
        default-autowire="byName">

    (2)no:意思是不支持自動裝配,必須明確指定依賴。

    (3)byName:經過設置Bean定義屬性autowire="byName",意思是根據名字進行自動裝配,只能用於setter注入。

<?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.xsd">

	<!-- <import resource="beans2.xml"/> -->

    <bean id="userDAO" class="com.dao.impl.UserDAOImpl" >
    	<property name="daoId" value="1"></property>
    </bean>
    
    <bean id="userDAO2" class="com.dao.impl.UserDAOImpl" >
    	<property name="daoId" value="2"></property>
    </bean>
    
    <bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byName">
        <!-- <property name="userDAO" ref="userDAO" /> -->
    </bean>
    
</beans>
public class UserServiceImpl implements UserService {

	private UserDAO userDAO;
	
	public UserDAO getUserDAO() {
		return userDAO;
	}
	
	public void setUserDAO(UserDAO userDAO) {
		this.userDAO = userDAO;
	}

	//constructor
//	public UserServiceImpl(UserDAO userDAO) {
//		super();
//		this.userDAO = userDAO;
//	}

	@Override
	public void addUser(User user) {
		this.userDAO.saveUser(user);
	}
}

    根據 byName 自動裝配會去找 UserServiceImpl 中的 userDAO 這個name的 Bean,而配置文件中有 userDAO 和 userDAO2 兩個,其中裝配的是 userDAO。若是 userDAO 這個 Bean沒有配置,則會報錯。

    (4)byType:經過設置Bean定義屬性autowire="byType",意思是指根據類型注入,用於setter注入。好比若是指定自動裝配方式爲「byType」,而 setUserDAO() 方法須要注入 UserDAO 類型數據,則Spring容器將查找 UserDAO 類Bean,若是找到一個則注入該Bean,若是找不到將什麼也不注入;若是找到多個Bean將優先注入<bean>標籤「primary」屬性爲 true 的 Bean,不然拋出異常來代表有個多個Bean發現但不知道使用哪一個。

    (5)constructor:經過設置 Bean 定義屬性autowire="constructor",功能和「byType」功能同樣,根據類型注入構造器參數,只是用於構造器注入方式。

5. init-method 和 destroy-method

    (1)init-method="init" :指定初始化方法,在構造器注入和setter注入完畢後執行。

    (2)destroy-method="destroy":指定銷燬方法,只有「singleton」做用域能銷燬;「prototype」做用域的必定不能,由於容器不會監控 prototype Bean的死亡,推薦不要和 prototype 一塊兒使用;其餘做用域不必定能;

<bean id="userService" class="com.service.impl.UserServiceImpl" autowire="byName" 
    init-method="init" destroy-method="destroy">
    <!-- <property name="userDAO" ref="userDAO" /> -->
</bean>
相關文章
相關標籤/搜索