Spring中Junit测试-WebAppConfiguration与WebApplicationContext
目录
Spring中Junit测试-@WebAppConfiguration与WebApplicationContext
先看看
Spring
官方对
@WebAppConfiguration
的注释说明
//@WebAppConfiguration是一个类级注释,始于Spring3.2,用于声明集成测试加载的ApplicationContext应该是WebApplicationContext。
//Spring会以value属性(默认为"src/main/webapp")指定的目录路径来为测试加载WebApplicationContext。要覆盖默认值,请通过value属性指定一个显式资源路径。
//请注意, @WebAppConfiguration必须在单个测试类中或在测试类层次结构中与@ContextConfiguration结合使用。
//从Spring Framework 4.0开始,此注释可用作创建自定义组成的注释的元注释。
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebAppConfiguration {
//指向Web应用程序根目录的资源路径。
//不包括Spring资源前缀的路径(例如,classpath:, file:等等)将被解释为文件系统资源,并且该路径不应以斜杠结尾。
//也可以指向classpath中的资源路径,而不是文件系统。eg.@WebAppConfiguration("classpath:testResources")
//默认以"src/main/webapp"作为文件系统资源。 请注意,这是Web应用程序根目录的标准目录,该目录遵循WAR的标准Maven项目布局。
String value() default "src/main/webapp";
}
看完
Spring
官方的注释说明,大概可以明白:若你在一个测试类上加了
@WebAppConfiguration
注解,则表示告诉
Spring
该集成测试加载的
ApplicationContext
应该是
WebApplicationContext
,那么看看
WebApplicationContext
在
ApplicationContext
的基础上扩展了什么内容:
//该接口为Web应用程序提供配置。在应用程序运行时,它是只读的,但是如果实现支持,则可以重新加载。
//此接口给通用的ApplicationContext接口添加了getServletContext()方法,并定义了一个众所周知的应用程序属性名称,在引导过程中必须将根上下文绑定到该名称。
//像通用应用程序上下文一样,Web应用程序上下文是分层的。 每个应用程序只有一个root context,而应用程序中的每个servlet(包括MVC框架中的调度程序servlet)都有自己的子上下文。
//除了标准的应用程序上下文生命周期功能外,WebApplicationContext的实现类还需要检测ServletContextAware Bean并相应地调用setServletContext方法。
public interface WebApplicationContext extends ApplicationContext {
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
//......
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
//......
//返回此应用程序的标准Servlet API ServletContext。除PortletContext外,它还可用于Portlet应用程序。
ServletContext getServletContext();
}
可以看到
WebApplicationContext
也是一个接口,主要只是定义了
getServletContext()
方法,项目成功启动时
Spring
会将
RootWebApplicationContext
绑定到的该
Context
的
parent
属性,即为它的父容器。
另外,
WebApplicationContext
的实现类还需要检测
ServletContextAware
Bean并相应地调用
setServletContext()
方法。且分析
ServletContextAwareProcessor
及相关联的一系列类源码得知:
Spring会往Web应用程序上下文中注册ServletRequest、ServletResponse、HttpSession等等bean
,我们在代码中通过
@Autowired
注入就可以很方便地获取到它们了。
/**
* 是BeanPostProcessor接口的实现类,将ServletContext传递给实现了ServletContextAware接口的Bean。
* web应用程序上下文(Web application contexts)将自动将这个类注册到其底层bean工厂。 应用程序不直接使用它。
*
* @see org.springframework.web.context.ServletContextAware
* @see org.springframework.web.context.support.XmlWebApplicationContext#postProcessBeanFactory
*/
public class ServletContextAwareProcessor implements BeanPostProcessor {
private ServletContext servletContext;
private ServletConfig servletConfig;
}
public interface ServletContextAware extends Aware {
/**
* Set the {@link ServletContext} that this object runs in.
* <p>Invoked after population of normal bean properties but before an init
* callback like InitializingBean's {@code afterPropertiesSet} or a
* custom init-method. Invoked after ApplicationContextAware's
* {@code setApplicationContext}.
* @param servletContext ServletContext object to be used by this object
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.context.ApplicationContextAware#setApplicationContext
*/
void setServletContext(ServletContext servletContext);
}
public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
implements ConfigurableWebApplicationContext, ThemeSource {
/**
* 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);
//注册web应用的所有域
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
}
public abstract class WebApplicationContextUtils {
/**
* Register web-specific scopes ("request", "session", "globalSession", "application")
* with the given BeanFactory, as used by the WebApplicationContext.
* @param beanFactory the BeanFactory to configure
* @param sc the ServletContext that we're running within
*/
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
if (sc != null) {
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
}
//注册ServletRequest bean!
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent) {
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
}
}
}
public abstract class WebApplicationContextUtils {
/**
* Factory that exposes the current request object on demand.
*/
@SuppressWarnings("serial")
private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
@Override
public ServletRequest getObject() {
return currentRequestAttributes().getRequest();
}
@Override
public String toString() {
return "Current HttpServletRequest";
}
}
}
这里有个关联知识点:
值得注意的是,若测试类上只有
@WebAppConfiguraton
而没有
@ContextConfiguration
。则启动报错:
Caused by: java.lang.IllegalArgumentException: Cannot load an ApplicationContext with a NULL 'contextLoader'. Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContextInternal(CacheAwareContextLoaderDelegate.java:57)
at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:91)
... 26 more