WebApplicationContext是專門爲web應用準備的,他允許從相對於web根目錄的路勁中裝載配置文件完成初始化工作,從WebApplicationContext中可以獲得ServletContext的引用,整個Web應用上下文對象將作爲屬性放置在ServletContext中,以便web應用可以訪問spring上下文,spring中提供WebApplicationContextUtils的getWebApplicationContext(ServletContext src)方法來獲得WebApplicationContext對象
繼承結構
WebApplicationContext
WebApplicationContext擴展了ApplicationContext的Web應用能力。WebApplicationContext中定義了一個常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,在上下文啓動時,WebApplicationContext以此爲鍵放置在ServletContext屬性列表中。
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
/**
* Name of the ServletContext environment bean in the factory.
* @see javax.servlet.ServletContext
*/
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
/**
* Name of the ServletContext init-params environment bean in the factory.
* <p>Note: Possibly merged with ServletConfig parameters.
* ServletConfig parameters override ServletContext parameters of the same name.
* @see javax.servlet.ServletContext#getInitParameterNames()
* @see javax.servlet.ServletContext#getInitParameter(String)
* @see javax.servlet.ServletConfig#getInitParameterNames()
* @see javax.servlet.ServletConfig#getInitParameter(String)
*/
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
/**
* Name of the ServletContext attributes environment bean in the factory.
* @see javax.servlet.ServletContext#getAttributeNames()
* @see javax.servlet.ServletContext#getAttribute(String)
*/
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
@Nullable
ServletContext getServletContext();
}
ConfigurableWebApplicationContext
ConfigurableWebApplicationContext綜合了ConfigurableApplicationContext的配置能力以及WebApplicationContext的Web應用能力。
它允許通過配置的方式實例化WebApplicationContext,同時設置兩個重要方法:
//爲spring設置web應用上下文,以便兩者整合
setServletContext(ServletContext context)
//設置Spring配置的文件地址
setConfigLocations(String[]locations)
public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext {
/**
* Prefix for ApplicationContext ids that refer to context path and/or servlet name.
*/
String APPLICATION_CONTEXT_ID_PREFIX = WebApplicationContext.class.getName() + ":";
/**
* Name of the ServletConfig environment bean in the factory.
* @see javax.servlet.ServletConfig
*/
String SERVLET_CONFIG_BEAN_NAME = "servletConfig";
void setServletContext(@Nullable ServletContext servletContext);
void setServletConfig(@Nullable ServletConfig servletConfig);
@Nullable
ServletConfig getServletConfig();
void setNamespace(@Nullable String namespace);
@Nullable
String getNamespace();
void setConfigLocation(String configLocation);
void setConfigLocations(String... configLocations);
@Nullable
String[] getConfigLocations();
}
AbstractRefreshableApplicationContext
AbstractRefreshableApplicationContext繼承自AbstractApplicationContext,支持多次進行刷新(多次調用refresh方法),每次刷新時在內部創建一個新的bean工廠的實例。子類僅僅需要實現loadBeanDefinitions方法,該方法在每次刷新時都會調用。
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
//允許Bean定義覆蓋
@Nullable
private Boolean allowBeanDefinitionOverriding;
@Nullable
//允許解決循環引用
private Boolean allowCircularReferences;
/** Bean factory for this context. */
@Nullable
private DefaultListableBeanFactory beanFactory;
/** Synchronization monitor for the internal BeanFactory. */
private final Object beanFactoryMonitor = new Object();
public AbstractRefreshableApplicationContext() {
}
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
}
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
/**
* 重新加載BeanFactory,先銷燬再創建新的
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
@Override
protected void cancelRefresh(BeansException ex) {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory != null) {
this.beanFactory.setSerializationId(null);
}
}
super.cancelRefresh(ex);
}
@Override
protected final void closeBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory != null) {
this.beanFactory.setSerializationId(null);
this.beanFactory = null;
}
}
}
/**
* Determine whether this context currently holds a bean factory,
* i.e. has been refreshed at least once and not been closed yet.
*/
protected final boolean hasBeanFactory() {
synchronized (this.beanFactoryMonitor) {
return (this.beanFactory != null);
}
}
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
return this.beanFactory;
}
}
/**
* Overridden to turn it into a no-op: With AbstractRefreshableApplicationContext,
* {@link #getBeanFactory()} serves a strong assertion for an active context anyway.
*/
@Override
protected void assertBeanFactoryActive() {
}
/**
默認爲DefaultListableBeanFactory
*/
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
/**
* 加載bean定義到給定beanFactory,通常通過 bean definition readers 加載
*/
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
}
AbstractRefreshableConfigApplicationContext
AbstractRefreshableConfigApplicationContext繼承自AbstractRefreshableApplicationContext,添加了對指定的配置文件路徑的公共的處理(ConfigurableApplicationContext接口),可以把他看作基於XML的應用上下文的基類。實現瞭如下的兩個接口:
- BeanNameAware
用於設置上下文的bean的名稱,只有一個方法:void setBeanName(String name)
- InitializingBean
用於上下文一切就緒後,如果還未刷新,那麼就執行刷新操作,只有一個方法:void afterPropertiesSet()
AbstractRefreshableWebApplicationContext
AbstractRefreshableWebApplicationContext繼承自AbstractRefreshableConfigApplicationContext,實現了ConfigurableWebApplicationContext和ThemeSource接口,主要是用於web環境下。在web程序啓動的時候,提供一個configLocations屬性,通過ConfigurableWebApplicationContext接口來進行填充。子類化這個接口是很簡單的,所有你所需要做的事情就是實現loadBeanDefinitions方法,來實現你自己的bean定義的加載邏輯。
public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
implements ConfigurableWebApplicationContext, ThemeSource {
/** Servlet context that this context runs in. */
@Nullable
private ServletContext servletContext;
/** Servlet config that this context runs in, if any. */
@Nullable
private ServletConfig servletConfig;
/** Namespace of this context, or {@code null} if root. */
@Nullable
private String namespace;
/** the ThemeSource for this ApplicationContext. */
@Nullable
private ThemeSource themeSource;
public AbstractRefreshableWebApplicationContext() {
setDisplayName("Root WebApplicationContext");
}
@Override
public void setServletContext(@Nullable ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
@Nullable
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public void setServletConfig(@Nullable ServletConfig servletConfig) {
this.servletConfig = servletConfig;
if (servletConfig != null && this.servletContext == null) {
setServletContext(servletConfig.getServletContext());
}
}
@Override
@Nullable
public ServletConfig getServletConfig() {
return this.servletConfig;
}
@Override
public void setNamespace(@Nullable String namespace) {
this.namespace = namespace;
if (namespace != null) {
setDisplayName("WebApplicationContext for namespace '" + namespace + "'");
}
}
@Override
@Nullable
public String getNamespace() {
return this.namespace;
}
@Override
public String[] getConfigLocations() {
return super.getConfigLocations();
}
@Override
public String getApplicationName() {
return (this.servletContext != null ? this.servletContext.getContextPath() : "");
}
/**
* Create and return a new {@link StandardServletEnvironment}. Subclasses may override
* in order to configure the environment or specialize the environment type returned.
*/
@Override
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
/**
* Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
/**
* This implementation supports file paths beneath the root of the ServletContext.
* @see ServletContextResource
*/
@Override
protected Resource getResourceByPath(String path) {
Assert.state(this.servletContext != null, "No ServletContext available");
return new ServletContextResource(this.servletContext, path);
}
/**
* This implementation supports pattern matching in unexpanded WARs too.
* @see ServletContextResourcePatternResolver
*/
@Override
protected ResourcePatternResolver getResourcePatternResolver() {
return new ServletContextResourcePatternResolver(this);
}
/**
* Initialize the theme capability.
*/
@Override
protected void onRefresh() {
this.themeSource = UiApplicationContextUtils.initThemeSource(this);
}
/**
* {@inheritDoc}
* <p>Replace {@code Servlet}-related property sources.
*/
@Override
protected void initPropertySources() {
ConfigurableEnvironment env = getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
}
}
@Override
@Nullable
public Theme getTheme(String themeName) {
Assert.state(this.themeSource != null, "No ThemeSource available");
return this.themeSource.getTheme(themeName);
}
AnnotationConfigWebApplicationContext
接受註解的類作爲輸入(特殊的@Configuration註解類,一般的@Component註解類,與JSR-330兼容的javax.inject註解)。允許一個一個的注入,同樣也能使用類路徑掃描。對於web環境,基本上是和AnnotationConfigApplicationContext等價的。使用AnnotatedBeanDefinitionReader來對註解的bean進行處理,使用ClassPathBeanDefinitionScanner來對類路徑下的bean進行掃描。
屬性
@Nullable
private BeanNameGenerator beanNameGenerator;
@Nullable
private ScopeMetadataResolver scopeMetadataResolver;
private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
private final Set<String> basePackages = new LinkedHashSet<>();
loadBeanDefinitions
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
//獲取註解bean定義 reader
AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
//獲取classpath bean定義 scanner
ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
//獲取bean name生成器
BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
if (beanNameGenerator != null) {
reader.setBeanNameGenerator(beanNameGenerator);
scanner.setBeanNameGenerator(beanNameGenerator);
beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
}
ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
if (scopeMetadataResolver != null) {
reader.setScopeMetadataResolver(scopeMetadataResolver);
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
//組件類,
if (!this.componentClasses.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Registering component classes: [" +
StringUtils.collectionToCommaDelimitedString(this.componentClasses) + "]");
}
//註冊組件類
reader.register(ClassUtils.toClassArray(this.componentClasses));
}
if (!this.basePackages.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Scanning base packages: [" +
StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
}
//掃描包
scanner.scan(StringUtils.toStringArray(this.basePackages));
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
try {
Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
if (logger.isTraceEnabled()) {
logger.trace("Registering [" + configLocation + "]");
}
//註冊 配置
reader.register(clazz);
}
catch (ClassNotFoundException ex) {
if (logger.isTraceEnabled()) {
logger.trace("Could not load class for config location [" + configLocation +
"] - trying package scan. " + ex);
}
int count = scanner.scan(configLocation);
if (count == 0 && logger.isDebugEnabled()) {
logger.debug("No component classes found for specified class/package [" + configLocation + "]");
}
}
}
}
}
AnnotatedBeanDefinitionReader 參考:Spring BeanDefinition加載
ClassPathBeanDefinitionScanner 參考:Spring BeanDefinition加載
XmlWebApplicationContext
使用XML作爲配置的Web應用。
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** 默認 config location. */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
/**
* 初始化bean definition reader,默認爲空
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}
/**
* Load the bean definitions with the given XmlBeanDefinitionReader.
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
@Override
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
}
XmlBeanDefinitionReader 參考:
ServletContext
webApplicationContext初始化需要ServletContext,也就是說需要web容器前提下才能完成啓動工作 ,可以通過在web.xml中配置自啓動Servlet或Web容器監聽來實現web容器的啓動
Spring提供啓動WebApplicationContext的servlet和Web容器監聽器org.springframework.web.context.ContextLoaderListener
Xml配置方式
!--從類路徑下加載Spring配置文件,classpath特指類路徑下加載-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:smart-context.xml
</param-value>
</context-param>
<!--負責啓動spring容器的監聽器 還可以聲明自啓動的Servlet ContextLoaderServlet-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
@Configuration配置方式
<!--通過指定context參數,讓Spring使用AnnotationConfigWebApplicationContext啓動容器而非XmlWebApplicationContext 默認沒配置時是使用XmlWebApplicationContext-->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!--指定標註了@Configuration的類,多個可以用逗號分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.example.Car,com.example.Boss</param-value>
</context-param>
<!--監聽器將根據上面的配置使用AnnotationConfigWebApplicationContext
根據contextConfigLocation
指定的配置類啓動Spring容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>