1. 程式人生 > >Web專案啟動初始化監聽器如何獲取spring bean

Web專案啟動初始化監聽器如何獲取spring bean

我們在專案開發中可能會遇到這樣的需求,在專案啟動的時候我們通過web.xml配置載入一個監聽器,然後在監聽器中初始化我們專案中所需要的資料,那我們分析一下實現

1: 首先建立監聽器,實現javax.servlet.ServletContextListener

public class InitDataListener implements ServletContextListener{
	
	//商品管理service
	private ProductService productService;

	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		//1:獲取業務邏輯類查詢商品資訊
		
	}

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//初始化 productService
		
	}

}

1.2:在web.xml配置監聽器,不配置監聽器是不起作用的

  <!-- 配置初始化資料的監聽器 -->
    <listener>
  	<listener-class>cn.it.shop.listener.InitDataListener</listener-class>
  </listener>
*注意事項:cn.it.shop.listener.InitDataListener 一定要註冊在org.springframework.web.context.ContextLoaderListener的後面,因為方案2,3,是在spring中獲取容器的,所以要先執行ContextLoaderListener,在執行我們自己的InitDataListener 

2

:我們要初始化資料,必須要獲取到 productService 查詢資料,那我們如何在這裡獲取spring注入的bean呢?

2.1:解決方案一:

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//解決方案1:以測試的思路解決此問題
			//1.1:建立Spring容器
		ApplicationContext context = new 
					ClassPathXmlApplicationContext("applicationContext-*.xml");
			//1.2:獲取bean
		productService = (ProductService) context.getBean("productService");
			//1.3:輸出測試
		System.out.println("===bean獲取成功===="+productService);
	}

輸出日誌如下:

-----sendAction-----
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.AccountServiceImpl@285896a2
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Account>
獲取到class泛型 ===class cn.it.shop.model.Account
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.CategoryServiceImpl@1cfd5850
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Category>
獲取到class泛型 ===class cn.it.shop.model.Category
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.ProductServiceImpl@14da0969
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Product>
獲取到class泛型 ===class cn.it.shop.model.Product

-----sendAction-----
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.AccountServiceImpl@774426c4
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Account>
獲取到class泛型 ===class cn.it.shop.model.Account
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.CategoryServiceImpl@41269505
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Category>
獲取到class泛型 ===class cn.it.shop.model.Category
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.ProductServiceImpl@4c019cad
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Product>
獲取到class泛型 ===class cn.it.shop.model.Product

===bean獲取成功====cn.it.shop.service.impl.ProductServiceImpl@4c019cad

我們從日誌的輸出可以看出問題,bean輸出了兩次,並且可以看出每一個都開闢了新的地址,由此可以得出,bean被建立了多次,雖然如此,但是我們的問題解決了沒有?解決了,我們看藍色字型部分,輸出===bean獲取成功====cn.it.shop.service.impl.ProductServiceImpl@4c019cad,這是我們監聽中輸出的吧,那這樣我們的問題解決了,但是這樣並不推薦使用,不可取,因為會把Spring配置檔案載入兩次 

2.2 解決方案2:

	@Override
	public void contextInitialized(ServletContextEvent sce) {
		//解決方案2:直接到ServletContext中獲取Spring配置檔案
//2.1:獲取Spring容器
ApplicationContext context = (ApplicationContext) sce.getServletContext()
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//2.2:獲取bean
productService = (ProductService) context.getBean("productService");
//2.3:輸出測試
System.out.println("===bean獲取成功===="+productService);
	}
日誌輸出:
-----sendAction-----
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.AccountServiceImpl@12436c64
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Account>
獲取到class泛型 ===class cn.it.shop.model.Account
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.CategoryServiceImpl@1fe8c96d
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Category>
獲取到class泛型 ===class cn.it.shop.model.Category
this代表當前物件呼叫建構函式物件cn.it.shop.service.impl.ProductServiceImpl@626b0fe0
獲取當前this物件的父類資訊class cn.it.shop.service.impl.BaseServiceImpl
獲取當前this物件的父類資訊(包括泛型資訊)cn.it.shop.service.impl.BaseServiceImpl<cn.it.shop.model.Product>
獲取到class泛型 ===class cn.it.shop.model.Product

===bean獲取成功====cn.it.shop.service.impl.ProductServiceImpl@626b0fe0

這次我們可以清楚的看出,Spring配置檔案只加載了一次,我們是直接獲取已經載入好的配置檔案,拿到我們所需要的bean,那你可能還在想要寫這麼多程式碼,實際上這只是介紹一下流程,終極解決方案在解決方案3

2.3終極解決方案3:

@Override
	public void contextInitialized(ServletContextEvent sce) {
		//解決方案3:通過工具類載入即可
		WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
		productService = (ProductService) webApplicationContext.getBean("productService");
		System.out.println("===bean獲取成功===="+productService);
	}

此解決方案為最終解決方案,這裡就不檢視日誌了,肯定是對的,為什麼我這麼肯定,想知道原理的童鞋可以往下看

2.4WebApplicationContextUtils獲取Spring容器的原理其實就是方案2,我們來看一下getWebApplicationContext這個方法

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
		return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	}
可以看到什麼,細心的同學肯定已經看到了,是不是這個key呀 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 和我們方案2呼叫的key一樣吧,我們再往下看這個方法getWebApplicationContext 
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
		Assert.notNull(sc, "ServletContext must not be null");
		Object attr = sc.getAttribute(attrName);
		if (attr == null) {
			return null;
		}
		if (attr instanceof RuntimeException) {
			throw (RuntimeException) attr;
		}
		if (attr instanceof Error) {
			throw (Error) attr;
		}
		if (attr instanceof Exception) {
			throw new IllegalStateException((Exception) attr);
		}
		if (!(attr instanceof WebApplicationContext)) {
			throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
		}
		return (WebApplicationContext) attr;
	}

看到這裡應該就很清晰了吧,他做的事情和我們方案2做的事情一樣,只不過他做了很多驗證,最後返回WebApplicationContext,那有的同學可能說我們上邊返回的是ApplicationContext,其實WebApplicationContext就是ApplicationContext的子級
public interface WebApplicationContext extends ApplicationContext {
//...
}
OK分析完畢 end