1 项目启动过程
1.1 ContextLoaderListener(非必须),加载指定文件到上下文
1.1.1 配置Demo
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- contextConfigLocation参数用来指定Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
参数 | 描述 |
contextClass | 实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext(ContextLoader.properties文件指定) |
contextConfigLocation | 表示用于加载Bean的配置文件,作为父上下文,传递给过滤器和DispatcherServlet |
1.1.2 初始化过程
ContextLoaderListener 主要是 contextInitialized 和 contextDestroyed 方法,分别在容器启动和销毁时触发
contextInitialized 方法负责初始化操作,包括
1 加载配置文件到 wac(WebApplicationContext)
wac.refresh();
2 将上下文放在sc(servletContext)里
后续的 filter 和 DispatcherServlet 可以直接使用sc里的上下文获取这里注入的bean
servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);
1.2 DispatchServlet
1.2.1 配置Demo
<servlet>
<servlet-name>dispatch</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatch</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
参数
描述
contextClass
实现WebApplicationContext接口的类,当前的servlet用它来创建上下文。如果这个参数没有指定, 默认使用XmlWebApplicationContext。
contextConfigLocation
传给上下文实例(由contextClass指定)的字符串,用来指定上下文的位置。这个字符串可以被分成多个字符串(使用逗号作为分隔符) 来支持多个上下文(在多上下文的情况下,如果同一个bean被定义两次,后面一个优先)。
namespace
WebApplicationContext命名空间。默认值是[server-name]-servlet
这些参数决定了 选择使用 contextClass 为 XmlWebApplicationContext 来加载上下文,contextConfigLocation 表示 文件名,namespace 表示如果没有配置contextConfigLocation时的默认需要加载的配置文件
protected String[] getDefaultConfigLocations() { return this.getNamespace() != null ? new String[]{"/WEB-INF/" + this.getNamespace() + ".xml"} : new String[]{"/WEB-INF/applicationContext.xml"}; }
1.2.2 初始化过程
(1)调用 HttpServletBean 的 init()
(2)调用 FrameworkServlet 的 initServletBean()
public abstract class FrameworkServlet extends HttpServletBean {
@Override
protected final void initServletBean() throws ServletException {
//省略部分代码
try {
//1、初始化Web上下文
this.webApplicationContext = initWebApplicationContext();
//2、提供给子类初始化的扩展点,无代码
initFrameworkServlet();
}
//省略部分代码
}
}
protected WebApplicationContext initWebApplicationContext() {
// 获取ROOT上下文(ContextLoaderListener加载的)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 使用代码构建DipatcherServlet时,调用DispatcherServlet(WebApplicationContext webApplicationContext)构造方法时会触发
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 查找已经绑定的上下文
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果没有找到相应的上下文,并指定父亲为ContextLoaderListener的ROOT上下文
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
//省略部分代码
}
return wac;
}
(3)进入wac = createWebApplicationContext(rootContext) 指定Root上下文
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置parent 注入
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
// 加载Bean
configureAndRefreshWebApplicationContext(wac);
return wac;
}
(4)调用 configureAndRefreshWebApplicationContext(wac);
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
ServletContext sc = getServletContext();
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
"." + getServletName());
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
}
}
else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()) + "/" + getServletName());
}
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 这里放入了监听器
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
// 加载bean
wac.refresh();
}
(5)调用 wac.refresh() 加载bean
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// 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.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
(6)调用finishRefresh() 触发监听器
(7)触发DispatcherServlet的onRefresh(),加载handlerMapping,handlerAdapter等
注意:如果没有提前注入对应类型的bean(例如HandlerMapping),将会加载默认文件(DispatcherServlet.properties)的bean
例如:在配置文件加入 <mvc:annotation-driven /> 或者 加上@EnableWebMvc注解 ,则会注入 RequestMappingHandlerMapping 等,详细可看 AnnotationDrivenBeanDefinitionParser 类
1.3 DEBUG查看
例如Tomcat,DispatcherServlet继承自StandardWrapperFacade,Debug可以看到相关数据
1.4 ContextLoaderListener 和 DispatcherServlet 初始化的关系
ContextLoaderListener初始化的上下文加载的Bean是对于整个应用程序共享的,不管是使用什么表现层技术,一般如DAO层、Service层Bean
DispatcherServlet初始化的上下文加载的Bean是只对Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,该初始化上下文应该只加载Web相关组件
1.5 ContextLoaderListener其他用法
1.5.1 可用于其他Web框架集成Spring
例如Jersey整合Spring
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.yaoyan.action</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
<param-value>com.yaoyan.filter.RequestFilter</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.yaoyan.filter.ResponseFilter</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jersey-serlvet</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
1.5.2 在过滤器里使用spring上下文的bean
<web-app>
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
@Component
public class MyFilter implements Filter {
@Autowired
UserService userService;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
}
1.6 拦截器冲突失效
如果我们在ContextLoaderListener里加载的文件里指定了拦截器的bean,后面 DispatcherServlet 加载 HandlerMapping时虽然会扫描并注入 所有实现MappedInterceptor接口的拦截器的bean,但是HandlerMapping一般会默认有一些拦截器,此时由于beanName一样,导致ContextLoaderListener里的拦截器不生效
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.mappedInterceptors);
initInterceptors();
}
public static <T> Map<String, T> beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
Assert.notNull(lbf, "ListableBeanFactory must not be null");
Map<String, T> result = new LinkedHashMap(4);
result.putAll(lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit));
if (lbf instanceof HierarchicalBeanFactory) {
HierarchicalBeanFactory hbf = (HierarchicalBeanFactory)lbf;
if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
Map<String, T> parentResult = beansOfTypeIncludingAncestors((ListableBeanFactory)hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit);
Iterator i$ = parentResult.entrySet().iterator();
while(i$.hasNext()) {
Entry<String, T> entry = (Entry)i$.next();
String beanName = (String)entry.getKey();
// 如果beanName一样,是不会注入拦截器的
if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) {
result.put(beanName, entry.getValue());
}
}
}
}
return result;
}
2 处理请求过程
核心流程:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//检查是否是请求是否是multipart(如文件上传),如果是将通过MultipartResolver解析
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 获取HandlerMapping,匹配到URL对应的Controller
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取HandlerAdapter,适配处理器,统一用handler处理请求
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 304 Not Modified缓存支持
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// handlermapping的拦截器的预处理prehandler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 由适配器执行调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 执行handlermapping相关的拦截器的后处理posthandler
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
// 视图在渲染时会把Model传入(view.render(mv.getModelInternal(), request, response);)
// 执行handlermapping相关的拦截器的完成后处理afterComplete
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
1 用户发送请求到DispatcherServlet
2 DispatcherServlet里找出匹配的HandlerMapping,HandlerMapping将会把请求映射为HandlerExecutionChain对象
3 找到HandlerMapping对应的HandlerAdapter(适配器设计模式,因为handlerMapping处理逻辑都不一样,采用adapt统一处理方法名)
4 HandlerAdapter进行处理方法调用,并返回ModelAndView对象
5 如果有View,使用 ViewResolver 解析出View
6 View根据model对view进行渲染
7 dispatcherServer返回响应给用户