目录

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 ,那么看看 WebApplicationContextApplicationContext 的基础上扩展了什么内容:

https://i-blog.csdnimg.cn/blog_migrate/ec16c9ee84883a2d9519b68c8228b6ee.png

//该接口为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 绑定到的该 Contextparent 属性,即为它的父容器。

另外, 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