Application Context 是 BeanFactory 的子接口,并提供了更丰富的功能:自动注入、自动配置、生命周期监听等,本文会对 Spring Application Context 的层次及职责进行解析。
ApplicationContext 接口
ACI 继承逻辑
首先需要对 ApplicationContext 聚合的功能进行拆分。以下是 ApplicationContext 的继承结构:
public interface ApplicationContext extends EnvironmentCapable,
ListableBeanFactory,
HierarchicalBeanFactory,
MessageSource,
ApplicationEventPublisher,
ResourcePatternResolver
可以将这些接口大概分为两类:提供配置及资源(EnvironmentCapable、BeanFactory、ResourceLoader)以及提供功能属性(MessageSource、ApplicationEventPublisher)。
EnvironmentCapable
Interface indicating a component that contains and exposes an Environment reference.
public interface EnvironmentCapable {
Environment getEnvironment();
}
这与之前在《SpringBoot 启动流程解析》章节中讲到的,在 prepare environment 阶段中配置完成,并在 prepare context 阶段配置到 ApplicationContext 中的 Environment 相同。
ListableBeanFactory 和 HierarchicalBeanFactory
我们先看一下两者与 BeanFactory 的关系,ListableBeanFactory 和 HierarchicalBeanFactory 都继承 BeanFactory。
BeanFactory 是访问 Spring Bean Container 的 root 接口,它及它的子接口实现了 Spring 最核心的 Dependency Injection 功能。其有以下功能(只对功能点做一个举例,省略了同名不同参的其他接口):
public interface BeanFactory {
// 获取 Bean Instance
<T> T getBean(Class<T> requiredType);
// 返回一个 Bean Provider,其作用类似于 Optional 的设计模式
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
// 返回 Bean name 对应的 Aliases
String[] getAliases(String name);
// 返回 Bean 对应的 Class 类型
Class<?> getType(String name);
boolean containsBean(String name);
boolean isSingleton(String name);
boolean isPrototype(String name);
boolean isTypeMatch(String name, Class<?> typeToMatch);
}
从定义中可以看到,HierarchicalBeanFactory 继承了 BeanFactory,并附加了对 the parent bean factory 访问的权能,即对 BeanFactory 垂直访问的能力。
public interface HierarchicalBeanFactory extends BeanFactory {
BeanFactory getParentBeanFactory();
// 与 containsBean 的区别主要是忽略 hierarchy 方向的 Bean
boolean containsLocalBean(String name);
}
同 HierarchicalBeanFactory,ListableBeanFactory 也继承了 BeanFactory,并附加了对 all bean instances 访问的权能,即对 BeanFactory 横向访问的能力。
public interface ListableBeanFactory extends BeanFactory {
// 与 containsBean 的区别主要是忽略以非 BeanDefinition 注册的 Singleton Bean
// 以及 hierarchy 方向的 Bean
boolean containsBeanDefinition(String beanName);
int getBeanDefinitionCount();
// allowEagerInit whether stream-based access may initialize lazy-init
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType, boolean allowEagerInit);
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType);
String[] getBeanDefinitionNames();
String[] getBeanNamesForType(@Nullable Class<?> type);
<T> Map<String, T> getBeansOfType(@Nullable Class<T> type);
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType);
}
MessageSource
MessageSource 用于解析消息,并支持消息的参数化和国际化。 Spring 包含两个内置的 MessageSource 实现:ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource。 后者能够重新加载消息定义,而无需重新启动虚拟机。
public interface MessageSource {
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
}
ApplicationEventPublisher
ApplicationEventPublisher 封装了事件发布的接口,实现该接口意味着其拥有发布事件的权能。
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
ResourcePatternResolver
ResourcePatternResolver 继承了 ResourceLoader 接口,并被 PathMatchingResourcePatternResolver 实现。其主要目的是为了加载外部资源进入 Resource,并通过解析被加载入容器。它可以通过 ResourceEditor、ResourceArrayPropertyEditor 实现一些诸如 Listener、Editor 等的扩展功能。
先来看 ResourceLoader,它只提供了根据路径加载 Resource 的方法,以及获取 ClassLoader 的方法。
public interface ResourceLoader {
Resource getResource(String location);
ClassLoader getClassLoader();
}
ResourcePatternResolver 继承 ResourceLoader 接口,实现了根据正则表达式批量加载 Resource 的功能。
/**
* <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
* that is usable outside an {@code ApplicationContext}, also used by
* {@link ResourceArrayPropertyEditor} for populating {@code Resource} array bean
* properties.
*/
public interface ResourcePatternResolver extends ResourceLoader {
Resource[] getResources(String locationPattern);
}
ACI 派生逻辑
Application 接口派生分为两条线,接口派生以及实现类派生。其主要接口有 WebApplication、ConfigurableApplicationContext、WebServerApplicationContext,由这三个类及其组合接口定义了大体意义上的 Web 服务器功能。
WebApplicationContext 赋予了获取 ServletContext 的权能;WebServerApplicationContext 赋予了获取 WebServer 的权能。ConfigurableApplicationContext 赋予了所需组件的配置权能并定义了 AC 执行流程相关接口。ConfigurableWebApplicationContext 和 ConfigurableWebServerApplicationContext 主要是对于 WebApp 和 WebServerApp 进一步的配置。
ApplicationContext 生命周期
在 SpringBoot 启动流程时,将 ApplicationStartup 注入到 ApplicationContext 中,与 SpringApplicationRunListeners 之于 SpringBoot 启动相同,描述了其生命周期阶段。
ApplicationContext 实现
AbstractApplicationContext
AbstractApplicationContext 实现了 ApplicationContext 及 ResourceLoader 接口。具体实现了什么权能在 ApplicationContext 接口部分已经介绍过。
public abstract class AbstractApplicationContext
extends DefaultResourceLoader
implements ConfigurableApplicationContext;
public interface ConfigurableApplicationContext
extends ApplicationContext, Lifecycle, Closeable;
重点关注一下 refresh()
函数,其定义了 ApplicationContext 过程中的重要一步。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
// 委托 Environment 校验必要配置的内容
// 清理 ApplicationListeners 并初始化 EarlyApplicationListeners
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.\
// 注册以下组件
// 1. BeanFactory ClassLoader
// 2. ResourceEditorRegistrar
// 3. ApplicationContextAwareProcessor 主要处理对 Aware interface 的支持
// 4. 禁止通过 Aware 接口注入相关类
// 5. 允许通过 BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext interface 注入
// 6. 提前将 ApplicationListener 注册到 BeanFactory
// 7. 声明 LoadTimeWeaverAwareProcessor 并准备 weave
// 8. 将环境变量和生命周期回调类注册进 BeanFactory:Environment、SystemProperties、SystemEnvironment、ApplicationStartup
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 模板函数
postProcessBeanFactory(beanFactory);
// BeanPostProcess StartupStep 过程
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // 调用执行 BeanFactoryPostProcessor
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory); // 注册 BeanPostProcessor
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
// 模板方法
onRefresh();
// Check for listener beans and register them.
// 注册 ApplicationListener 和 ApplicationEvent
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
// 1. 设置 ConversionService
// 2. 设置 EmbeddedValueResolver
// 3. 设置 LoadTimeWeaverAware
// 4. 缓存 all bean definition metadata
// 5. 初始化全部剩余的 non-lazy-init singletons
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
GenericApplicationContext
GenericApplicationContext 在继承了 AbstractApplicationContext 的同时实现了 BeanDefinitionRegistry 接口,为其附加了管理 BeanDefinition 的权能。
public class GenericApplicationContext
extends AbstractApplicationContext
implements BeanDefinitionRegistry;
具体可以看一下 BeanDefinitionRegistry 定义,详细讲解可以阅读《Spring Bean和BeanFactory层次及职责解析》。
/**
* Interface for registries that hold bean definitions, for example RootBeanDefinition
* and ChildBeanDefinition instances. Typically implemented by BeanFactories that
* internally work with the AbstractBeanDefinition hierarchy.
*
* <p>This is the only interface in Spring's bean factory packages that encapsulates
* <i>registration</i> of bean definitions. The standard BeanFactory interfaces
* only cover access to a <i>fully configured factory instance</i>.
*
* <p>Spring's bean definition readers expect to work on an implementation of this
* interface. Known implementors within the Spring core are DefaultListableBeanFactory
* and GenericApplicationContext.
*/
public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
boolean containsBeanDefinition(String beanName);
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String beanName);
}
GenericWebApplicationContext
/**
* Subclass of {@link GenericApplicationContext}, suitable for web environments.
*
* <p>Implements {@link org.springframework.web.context.ConfigurableWebApplicationContext},
* but is not intended for declarative setup in {@code web.xml}. Instead, it is designed
* for programmatic setup, for example for building nested contexts or for use within
* {@link org.springframework.web.WebApplicationInitializer WebApplicationInitializers}.
*
* <p><b>If you intend to implement a WebApplicationContext that reads bean definitions
* from configuration files, consider deriving from AbstractRefreshableWebApplicationContext,
* reading the bean definitions in an implementation of the {@code loadBeanDefinitions}
* method.</b>
*
* <p>Interprets resource paths as servlet context resources, i.e. as paths beneath
* the web application root. Absolute paths, e.g. for files outside the web app root,
* can be accessed via "file:" URLs, as implemented by AbstractApplicationContext.
*
* <p>In addition to the special beans detected by
* {@link org.springframework.context.support.AbstractApplicationContext},
* this class detects a ThemeSource bean in the context, with the name "themeSource".
*
* @author Juergen Hoeller
* @author Chris Beams
* @since 1.2
*/
public class GenericWebApplicationContext extends GenericApplicationContext
implements ConfigurableWebApplicationContext, ThemeSource {
@Nullable
private ServletContext servletContext;
@Nullable
private ThemeSource themeSource;
/**
* Create a new GenericWebApplicationContext.
* @see #setServletContext
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericWebApplicationContext() {
super();
}
/**
* Create a new GenericWebApplicationContext for the given ServletContext.
* @param servletContext the ServletContext to run in
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericWebApplicationContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory.
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
* @see #setServletContext
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
}
/**
* Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory.
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
* @param servletContext the ServletContext to run in
* @see #registerBeanDefinition
* @see #refresh
*/
public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {
super(beanFactory);
this.servletContext = servletContext;
}
/**
* Set the ServletContext that this WebApplicationContext runs in.
*/
@Override
public void setServletContext(@Nullable ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
@Nullable
public ServletContext getServletContext() {
return this.servletContext;
}
@Override
public String getApplicationName() {
return (this.servletContext != null ? this.servletContext.getContextPath() : "");
}
/**
* Create and return a new {@link StandardServletEnvironment}.
*/
@Override
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
/**
* Register ServletContextAwareProcessor.
* @see ServletContextAwareProcessor
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
if (this.servletContext != null) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
}
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext);
}
/**
* 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, null);
}
}
@Override
@Nullable
public Theme getTheme(String themeName) {
Assert.state(this.themeSource != null, "No ThemeSource available");
return this.themeSource.getTheme(themeName);
}
// ---------------------------------------------------------------------
// Pseudo-implementation of ConfigurableWebApplicationContext
// ---------------------------------------------------------------------
@Override
public void setServletConfig(@Nullable ServletConfig servletConfig) {
// no-op
}
@Override
@Nullable
public ServletConfig getServletConfig() {
throw new UnsupportedOperationException(
"GenericWebApplicationContext does not support getServletConfig()");
}
@Override
public void setNamespace(@Nullable String namespace) {
// no-op
}
@Override
@Nullable
public String getNamespace() {
throw new UnsupportedOperationException(
"GenericWebApplicationContext does not support getNamespace()");
}
@Override
public void setConfigLocation(String configLocation) {
if (StringUtils.hasText(configLocation)) {
throw new UnsupportedOperationException(
"GenericWebApplicationContext does not support setConfigLocation(). " +
"Do you still have an 'contextConfigLocations' init-param set?");
}
}
@Override
public void setConfigLocations(String... configLocations) {
if (!ObjectUtils.isEmpty(configLocations)) {
throw new UnsupportedOperationException(
"GenericWebApplicationContext does not support setConfigLocations(). " +
"Do you still have an 'contextConfigLocations' init-param set?");
}
}
@Override
public String[] getConfigLocations() {
throw new UnsupportedOperationException(
"GenericWebApplicationContext does not support getConfigLocations()");
}
}
ServletWebServerApplicationContext
/**
* A {@link WebApplicationContext} that can be used to bootstrap itself from a contained
* {@link ServletWebServerFactory} bean.
* <p>
* This context will create, initialize and run an {@link WebServer} by searching for a
* single {@link ServletWebServerFactory} bean within the {@link ApplicationContext}
* itself. The {@link ServletWebServerFactory} is free to use standard Spring concepts
* (such as dependency injection, lifecycle callbacks and property placeholder variables).
* <p>
* In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be
* automatically registered with the web server. In the case of a single Servlet bean, the
* '/' mapping will be used. If multiple Servlet beans are found then the lowercase bean
* name will be used as a mapping prefix. Any Servlet named 'dispatcherServlet' will
* always be mapped to '/'. Filter beans will be mapped to all URLs ('/*').
* <p>
* For more advanced configuration, the context can instead define beans that implement
* the {@link ServletContextInitializer} interface (most often
* {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent
* double registration, the use of {@link ServletContextInitializer} beans will disable
* automatic Servlet and Filter bean registration.
* <p>
* Although this context can be used directly, most developers should consider using the
* {@link AnnotationConfigServletWebServerApplicationContext} or
* {@link XmlServletWebServerApplicationContext} variants.
*
* @author Phillip Webb
* @author Dave Syer
* @author Scott Frederick
* @since 2.0.0
* @see AnnotationConfigServletWebServerApplicationContext
* @see XmlServletWebServerApplicationContext
* @see ServletWebServerFactory
*/
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
private static final Log logger = LogFactory.getLog(ServletWebServerApplicationContext.class);
/**
* Constant value for the DispatcherServlet bean name. A Servlet bean with this name
* is deemed to be the "main" servlet and is automatically given a mapping of "/" by
* default. To change the default behavior you can use a
* {@link ServletRegistrationBean} or a different bean name.
*/
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
private volatile WebServer webServer;
private ServletConfig servletConfig;
private String serverNamespace;
/**
* Create a new {@link ServletWebServerApplicationContext}.
*/
public ServletWebServerApplicationContext() {
}
/**
* Create a new {@link ServletWebServerApplicationContext} with the given
* {@code DefaultListableBeanFactory}.
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
*/
public ServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
}
/**
* Register ServletContextAwareProcessor.
* @see ServletContextAwareProcessor
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
registerWebApplicationScopes();
}
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw ex;
}
}
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
@Override
protected void doClose() {
if (isActive()) {
AvailabilityChangeEvent.publish(this, ReadinessState.REFUSING_TRAFFIC);
}
super.doClose();
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
/**
* Returns the {@link ServletWebServerFactory} that should be used to create the
* embedded {@link WebServer}. By default this method searches for a suitable bean in
* the context itself.
* @return a {@link ServletWebServerFactory} (never {@code null})
*/
protected ServletWebServerFactory getWebServerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
+ "ServletWebServerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
+ "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @return the self initializer
* @see #prepareWebApplicationContext(ServletContext)
*/
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
private void registerApplicationScope(ServletContext servletContext) {
ServletContextScope appScope = new ServletContextScope(servletContext);
getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
servletContext.setAttribute(ServletContextScope.class.getName(), appScope);
}
private void registerWebApplicationScopes() {
ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(getBeanFactory());
WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory());
existingScopes.restore();
}
/**
* Returns {@link ServletContextInitializer}s that should be used with the embedded
* web server. By default this method will first attempt to find
* {@link ServletContextInitializer}, {@link Servlet}, {@link Filter} and certain
* {@link EventListener} beans.
* @return the servlet initializer beans
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
/**
* Prepare the {@link WebApplicationContext} with the given fully loaded
* {@link ServletContext}. This method is usually called from
* {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the
* functionality usually provided by a {@link ContextLoaderListener}.
* @param servletContext the operational servlet context
*/
protected void prepareWebApplicationContext(ServletContext servletContext) {
Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (rootContext != null) {
if (rootContext == this) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
return;
}
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
@Override
protected Resource getResourceByPath(String path) {
if (getServletContext() == null) {
return new ClassPathContextResource(path, getClassLoader());
}
return new ServletContextResource(getServletContext(), path);
}
@Override
public String getServerNamespace() {
return this.serverNamespace;
}
@Override
public void setServerNamespace(String serverNamespace) {
this.serverNamespace = serverNamespace;
}
@Override
public void setServletConfig(ServletConfig servletConfig) {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig;
}
/**
* Returns the {@link WebServer} that was created by the context or {@code null} if
* the server has not yet been created.
* @return the embedded web server
*/
@Override
public WebServer getWebServer() {
return this.webServer;
}
/**
* Utility class to store and restore any user defined scopes. This allow scopes to be
* registered in an ApplicationContextInitializer in the same way as they would in a
* classic non-embedded web application context.
*/
public static class ExistingWebApplicationScopes {
private static final Set<String> SCOPES;
static {
Set<String> scopes = new LinkedHashSet<>();
scopes.add(WebApplicationContext.SCOPE_REQUEST);
scopes.add(WebApplicationContext.SCOPE_SESSION);
SCOPES = Collections.unmodifiableSet(scopes);
}
private final ConfigurableListableBeanFactory beanFactory;
private final Map<String, Scope> scopes = new HashMap<>();
public ExistingWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
for (String scopeName : SCOPES) {
Scope scope = beanFactory.getRegisteredScope(scopeName);
if (scope != null) {
this.scopes.put(scopeName, scope);
}
}
}
public void restore() {
this.scopes.forEach((key, value) -> {
if (logger.isInfoEnabled()) {
logger.info("Restoring user defined scope " + key);
}
this.beanFactory.registerScope(key, value);
});
}
}
}
AnnotationConfigServletWebServerApplicationContext
/**
* {@link ServletWebServerApplicationContext} that accepts annotated classes as input - in
* particular {@link org.springframework.context.annotation.Configuration @Configuration}
* -annotated classes, but also plain {@link Component @Component} classes and JSR-330
* compliant classes using {@code javax.inject} annotations. Allows for registering
* classes one by one (specifying class names as config location) as well as for classpath
* scanning (specifying base packages as config location).
* <p>
* Note: In case of multiple {@code @Configuration} classes, later {@code @Bean}
* definitions will override ones defined in earlier loaded files. This can be leveraged
* to deliberately override certain bean definitions via an extra Configuration class.
*
* @author Phillip Webb
* @since 1.0.0
* @see #register(Class...)
* @see #scan(String...)
* @see ServletWebServerApplicationContext
* @see AnnotationConfigServletWebApplicationContext
*/
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
implements AnnotationConfigRegistry {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();
private String[] basePackages;
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext} that needs
* to be populated through {@link #register} calls and then manually
* {@linkplain #refresh refreshed}.
*/
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext} with the
* given {@code DefaultListableBeanFactory}. The context needs to be populated through
* {@link #register} calls and then manually {@linkplain #refresh refreshed}.
* @param beanFactory the DefaultListableBeanFactory instance to use for this context
*/
public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
super(beanFactory);
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext}, deriving
* bean definitions from the given annotated classes and automatically refreshing the
* context.
* @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration}
* classes
*/
public AnnotationConfigServletWebServerApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
/**
* Create a new {@link AnnotationConfigServletWebServerApplicationContext}, scanning
* for bean definitions in the given packages and automatically refreshing the
* context.
* @param basePackages the packages to check for annotated classes
*/
public AnnotationConfigServletWebServerApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
/**
* {@inheritDoc}
* <p>
* Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and
* {@link ClassPathBeanDefinitionScanner} members.
*/
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
super.setEnvironment(environment);
this.reader.setEnvironment(environment);
this.scanner.setEnvironment(environment);
}
/**
* Provide a custom {@link BeanNameGenerator} for use with
* {@link AnnotatedBeanDefinitionReader} and/or
* {@link ClassPathBeanDefinitionScanner}, if any.
* <p>
* Default is
* {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
* <p>
* Any call to this method must occur prior to calls to {@link #register(Class...)}
* and/or {@link #scan(String...)}.
* @param beanNameGenerator the bean name generator
* @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
* @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.reader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
}
/**
* Set the {@link ScopeMetadataResolver} to use for detected bean classes.
* <p>
* The default is an {@link AnnotationScopeMetadataResolver}.
* <p>
* Any call to this method must occur prior to calls to {@link #register(Class...)}
* and/or {@link #scan(String...)}.
* @param scopeMetadataResolver the scope metadata resolver
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.reader.setScopeMetadataResolver(scopeMetadataResolver);
this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
/**
* Register one or more annotated classes to be processed. Note that
* {@link #refresh()} must be called in order for the context to fully process the new
* class.
* <p>
* Calls to {@code #register} are idempotent; adding the same annotated class more
* than once has no additional effect.
* @param annotatedClasses one or more annotated classes, e.g. {@code @Configuration}
* classes
* @see #scan(String...)
* @see #refresh()
*/
@Override
public final void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
this.annotatedClasses.addAll(Arrays.asList(annotatedClasses));
}
/**
* Perform a scan within the specified base packages. Note that {@link #refresh()}
* must be called in order for the context to fully process the new class.
* @param basePackages the packages to check for annotated classes
* @see #register(Class...)
* @see #refresh()
*/
@Override
public final void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.basePackages = basePackages;
}
@Override
protected void prepareRefresh() {
this.scanner.clearCache();
super.prepareRefresh();
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (!this.annotatedClasses.isEmpty()) {
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
}
}
}