方法一:在初始化時保存ApplicationContext對象
方法二:通過Spring提供的工具類獲取ApplicationContext對象
方法三:繼承自抽象類ApplicationObjectSupport
方法四:繼承自抽象類WebApplicationObjectSupport
方法五:實現接口ApplicationContextAware
常用的5種獲取spring 中bean的方式總結:
方法一:在初始化時保存ApplicationContext對象
ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml"); ac.getBean("beanId");
說明:這種方式適用於采用Spring框架的獨立應用程序,需要程序通過配置文件手工初始化Spring的情況。
方法二:通過Spring提供的工具類獲取ApplicationContext對象
import org.springframework.web.context.support.WebApplicationContextUtils; ApplicationContext ac1 = WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext sc); ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc); ac1.getBean("beanId"); ac2.getBean("beanId");
說明:
這種方式適合於采用Spring框架的B/S系統,通過ServletContext對象獲取ApplicationContext對象,然后在通過它獲取需要的類實例。
上面兩個工具方式的區別是,前者在獲取失敗時拋出異常,后者返回null。
方法三:繼承自抽象類ApplicationObjectSupport
說明:抽象類ApplicationObjectSupport提供getApplicationContext()方法,可以方便的獲取到ApplicationContext。
Spring初始化時,會通過該抽象類的setApplicationContext(ApplicationContext context)方法將ApplicationContext 對象注入。
方法四:繼承自抽象類WebApplicationObjectSupport
說明:類似上面方法,調用getWebApplicationContext()獲取WebApplicationContext
方法五:實現接口ApplicationContextAware
說明:實現該接口的setApplicationContext(ApplicationContext context)方法,並保存ApplicationContext 對象。
Spring初始化時,會通過該方法將ApplicationContext對象注入。
雖然,spring提供了后三種方法可以實現在普通的類中繼承或實現相應的類或接口來獲取spring 的ApplicationContext對象,但是在使用是一定要注意實現了這些類或接口的普通java類一定要在Spring 的配置文件application-context.xml文件中進行配置。否則獲取的ApplicationContext對象將為null。
如下是我實現了ApplicationContextAware接口的例子
package com.dxz.spring.ioc; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class SpringConfigTool implements ApplicationContextAware {// extends // ApplicationObjectSupport{ private static ApplicationContext context = null; private static SpringConfigTool stools = null; public synchronized static SpringConfigTool init() { if (stools == null) { stools = new SpringConfigTool(); } return stools; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { context = applicationContext; } public synchronized static Object getBean(String beanName) { return context.getBean(beanName); } }
其次在applicationContext.xml文件進行配置:
<bean id="SpringConfigTool" class="com.dxz.spring.ioc.SpringConfigTool"/>
最后提供一種不依賴於servlet,不需要注入的方式
注意一點,在服務器啟動時,Spring容器初始化時,不能通過以下方法獲取Spring 容器,如需細節可以觀看源碼org.springframework.web.context.ContextLoader
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->1 import org.springframework.web.context.ContextLoader; 2 import org.springframework.web.context.WebApplicationContext; 3 4 WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext(); 5 wac.getBean(beanID);
Spring注入非單例bean以及scope的作用范圍
一、 問題描述
在大部分情況下,容器中的bean都是singleton類型的。
如果一個singleton bean要引用另外一個singleton bean,或者一個非singleton bean要引用另外一個非singleton bean時,通常情況下將一個bean定義為另一個bean的property值就可以了。不過對於具有不同生命周期的bean來說這樣做就會有問題了,比如在調用一個singleton類型bean A的某個方法時,需要引用另一個非singleton(prototype)類型的bean B,對於bean A來說,容器只會創建一次,這樣就沒法在需要的時候每次讓容器為bean A提供一個新的的bean B實例。
二、 解決方案
對於上面的問題Spring提供了三種解決方案:
- 放棄控制反轉。
通過實現ApplicationContextAware接口讓bean A能夠感知bean 容器,並且在需要的時候通過使用getBean("B")方式向容器請求一個新的bean B實例。
- Lookup方法注入。
Lookup方法注入利用了容器的覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實例。
- 自定義方法的替代方案。
該注入能使用bean的另一個方法實現去替換自定義的方法。
三、 實現案例
3.1 放棄IOC
接口類:
- package learn.frame.spring.scope.dropioc;
- public interface Command {
- public Object execute();
- }
實現類:
- package learn.frame.spring.scope.dropioc;
- public class AsyncCommand implements Command {
- @Override
- public Object execute() {
- return this;
- }
- }
業務類:
ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,實現了ApplicationContextAware接口的對象會擁有 一個ApplicationContext的引用,這樣我們就可以已編程的方式操作ApplicationContext。看下面的例子。
- public class CommandManager implements ApplicationContextAware {
- //用於保存ApplicationContext的引用,set方式注入
- private ApplicationContext applicationContext;
- //模擬業務處理的方法
- public Object process() {
- Command command = createCommand();
- return command.execute();
- }
- //獲取一個命令
- private Command createCommand() {
- return (Command) this.applicationContext.getBean("asyncCommand"); //
- }
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;//獲得該ApplicationContext引用
- }
- }
配置文件:beans-dropioc.xml
單例Bean commandManager的process()方法需要引用一個prototype(非單例)的bean,所以在調用process的時候先通過 createCommand方法從容器中取得一個Command,然后在執行業務計算。
scope="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-3.0.xsd">
- <!-- 通過scope="prototype"界定該bean是多例的 -->
- <bean id="asyncCommand" class="learn.frame.spring.scope.dropioc.AsyncCommand"
- scope="prototype"></bean>
- <bean id="commandManager" class="learn.frame.spring.scope.dropioc.CommandManager">
- </bean>
- </beans>
測試類:
- package org.shupeng.learn.frame.spring.scope;
- import java.util.ArrayList;
- import org.junit.Before;
- import org.junit.Test;
- import learn.frame.spring.scope.dropioc.CommandManager;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class TestCommandManagerDropIOC {
- private ApplicationContext context;
- @Before
- public void setUp() throws Exception {
- context = new ClassPathXmlApplicationContext("beans-dropioc.xml");
- }
- @Test
- public void testProcess() {
- CommandManager manager = (CommandManager) context.getBean("commandManager",
- CommandManager.class);
- System.out.println("第一執行process,Command的地址是:" + manager.process());
- System.out.println("第二執行process,Command的地址是:" + manager.process());
- }
- }
Test結果:
- 第一執行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@187c55c
- 第二執行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@ae3364
通過控制台輸出看到兩次的輸出借中的Command的地址是不一樣的,因為我們為asyncCommand配置了scope="prototype"屬性,這種方式就是使得每次從容器中取得的bean實例都不一樣。
業務代碼和Spring Framework產生了耦合。
3.2 Look方法注入
這種方式Spring已經為我們做了很大一部分工作,要做的就是bean配置和業務類。
新的業務:
- package learn.frame.spring.scope.lookup;
- import learn.frame.spring.scope.dropioc.Command;
- public abstract class CommandManager {
- //模擬業務處理的方法
- public Object process() {
- Command command = createCommand();
- return command.execute();
- }
- //獲取一個命令
- protected abstract Command createCommand();
- }
配置文件:beans-lookup.xml
- <?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-3.0.xsd">
- <!-- 通過scope="prototype"界定該bean是多例的 -->
- <bean id="asyncCommand" class="learn.frame.spring.scope.dropioc.AsyncCommand"
- scope="prototype"></bean>
- <bean id="commandManager" class="learn.frame.spring.scope.lookup.CommandManager">
- <lookup-method name="createCommand" bean="asyncCommand"/>
- </bean>
- </beans>
變化部分:
- 修改CommandManager類為abstract的,修改createCommand方法也為abstract的。
- 去掉ApplicationContextAware的實現及相關set方法和applicationContext變量定義
- 修改bean配置文件,在commandManager Bean中增加<lookup-method name="createCommand" bean="asyncCommand"/>。
測試類:
- package learn.frame.spring.scope;
- import org.junit.Before;
- import org.junit.Test;
- import learn.frame.spring.scope.lookup.CommandManager;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class TestCommandManagerLookup {
- private ApplicationContext context;
- @Before
- public void setUp() throws Exception {
- context = new ClassPathXmlApplicationContext("beans-lookup.xml");
- }
- @Test
- public void testProcess() {
- CommandManager manager = (CommandManager) context.getBean("commandManager",
- CommandManager.class);
- System.out.println("第一執行process,Command的地址是:" + manager.process());
- System.out.println("第二執行process,Command的地址是:" + manager.process());
- }
- }
測試結果:
- 第一執行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@5bb966
- 第二執行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@1e903d5
控制台打印出的兩個Command的地址不一樣,說明實現了。
- <public|protected> [abstract] <return-type> theMethodName(no-arguments)
- 被注入方法不一定是抽象的,如果被注入方法是抽象的,動態生成的子類(這里就是動態生成的CommandManager的子類)會實現該方法。否則,動態生成的子類會覆蓋類里的具體方法。
- 為了讓這個動態子類得以正常工作,需要把CGLIB的jar文件放在classpath里,這就是我們引用cglib包的原因。
- Spring容器要子類化的類(CommandManager)不能是final的,要覆蓋的方法(createCommand)也不能是final的。
Lookup方法注入干凈整潔,易於擴展,更符合Ioc規則,所以盡量采用這種方式。
四、 原理分析(bean的scope屬性范圍)
scope用來聲明IOC容器中的對象應該處的限定場景或者說該對象的存活空間,即在IOC容器在對象進入相應的scope之前,生成並裝配這些對象,在該對象不再處於這些scope的限定之后,容器通常會銷毀這些對象。
Spring容器最初提供了兩種bean的scope類型:singleton和prototype,但發布2.0之后,又引入了另外三種scope類型,即request,session和global session類型。不過這三種類型有所限制,只能在web應用中使用,也就是說,只有在支持web應用的ApplicationContext中使用這三個scope才是合理的。
可以使用bean的singleton或scope屬性來指定相應對象的scope,其中,scope屬性只能在XSD格式的文檔生命中使用,類似於如下代碼所演示的形式:
- DTD:
- <bean id ="mockObject1" class="..." singleton="false" />
- XSD:
- <bean id ="mockObject1" class="..." scope="prototype" />
注意:這里的singleton和設計模式里面的單例模式不一樣,標記為singleton的bean是由容器來保證這種類型的bean在同一個容器內只存在一個共享實例,而單例模式則是保證在同一個Classloader中只存在一個這種類型的實例。
4.1. singleton
singleton類型的bean定義,在一個容器中只存在一個實例,所有對該類型bean的依賴都引用這一單一實例。singleton類型的bean定義,從容器啟動,到他第一次被請求而實例化開始,只要容器不銷毀或退出,該類型的bean的單一實例就會一直存活。
通常情況下,如果你不指定bean的scope,singleton便是容器默認的scope,所以,下面三種配置,形式實際上達成的是同樣的效果:
- DTD or XSD:
- <bean id ="mockObject1" class="..." />
- DTD:
- <bean id ="mockObject1" class="..." singleton="true" />
- XSD:
- <bean id ="mockObject1" class="..." scope="singleton" />
4.2 prototype
scope為prototype的bean,容器在接受到該類型的對象的請求的時候,會每次都重新生成一個新的對象給請求方。
雖然這種類型的對象的實例化以及屬性設置等工作都是由容器負責的,但是只要准備完畢,並且對象實例返回給請求方之后,容器就不在擁有當前對象的引用,請求方需要自己負責當前對象后繼生命周期的管理工作,包括該對象的銷毀。也就是說,容器每次返回請求方該對象的一個新的實例之后,就由這個對象“自生自滅”了。
可以用以下方式定義prototype類型的bean:
- DTD:
- <bean id ="mockObject1" class="..." singleton="false" />
- XSD:
- <bean id ="mockObject1" class="..." scope="prototype" />
4.3 request ,session和global session
這三個類型是spring2.0之后新增的,他們不像singleton和prototype那么通用,因為他們只適用於web程序,通常是和XmlWebApplicationContext共同使用。
request:
- <bean id ="requestPrecessor" class="...RequestPrecessor" scope="request" />
Spring容器,即XmlWebApplicationContext 會為每個HTTP請求創建一個全新的RequestPrecessor對象,當請求結束后,該對象的生命周期即告結束。當同時有10個HTTP請求進來的時候,容器會分別針對這10個請求創建10個全新的RequestPrecessor實例,且他們相互之間互不干擾,從不是很嚴格的意義上說,request可以看做prototype的一種特例,除了場景更加具體之外,語意上差不多。
session:
對於web應用來說,放到session中最普遍的就是用戶的登錄信息,對於這種放到session中的信息,我們我們可以使用如下形式的制定scope為session:
- <bean id ="userPreferences" class="...UserPreferences" scope="session" />
Spring容器會為每個獨立的session創建屬於自己的全新的UserPreferences實例,他比request scope的bean會存活更長的時間,其他的方面真是沒什么區別。
global session:
- <bean id ="userPreferences" class="...UserPreferences" scope="globalsession" />
global session只有應用在基於porlet的web應用程序中才有意義,他映射到porlet的global范圍的session,如果普通的servlet的web 應用中使用了這個scope,容器會把它作為普通的session的scope對待。
(我只是聽說過porlet這個詞,好像是和servlet類似的一種java web技術,大家以后遇到的時候可以搜一下!)
五、 新的擴展(注解方式)
自Spring3.x開始,增加了@Async這樣一個注解,Spring 文檔里是這樣說的:
- The @Async annotation can be provided on a method so that invocation of that method will occur asynchronously. </br>
- In other words, the caller will return immediately upon invocation and the actual execution of the method will </br>
- occur in a task that has been submitted to a Spring TaskExecutor.
就是說讓方法異步執行。