示例项目见第四部分
1 原理
1.1 配置文件web.xml
<!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>WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置dispatcher.xml作为mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
对于Servlet容器Tomcat,有两个配置文件比较重要:分别是server.xml和web.xml,server.xml主要是Tomcat的配置文件,不是Servlet规范中的东西。web.xml是Servlet规范中的东西,其每个标签的定义都需要符合Servlet规范。我们来了解一下web.xml中的配置。
在另一个博文https://www.cnblogs.com/zhenjingcool/p/16585255.html中,我们了解到springmvc有两个上下文,分别是根上下文和web上下文。其中根上下文是通过如下代码配置的
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在https://www.cnblogs.com/zhenjingcool/p/16585255.html中我们知道,Tomcat通过ContextLoaderListener实现根上下文的实例化。实例化过程中,根上下文的配置和bean都配置在 <context-param> 中,也就是applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.szj"/>
<mvc:annotation-driven />
<bean></bean>
</beans>
在这个配置文件中,如果需要配置bean,我们使用<bean>标签进行配置。
<mvc:annotation-driven />是告知Spring,我们启用注解驱动。然后Spring会自动为我们注册RequestMappingHandlerMapping和AnnotationMethodHandlerAdapter等几个Bean到工厂中,此时我们可以使用@RequestMapping、@Valid注解来处理请求,也可以使用@ResponseBody来处理返回结果。
<context:component-scan base-package="com.szj"/>。配置组件扫描器。即Spring容器初始化时,扫描base-package包或者子包下面的Java文件,如果扫描到有@controller、@Service、@Repository、@Component等注解的java类,就会将这些bean注册到工厂中。
当然,还有一些其他的配置,可参考:https://blog.youkuaiyun.com/originations/article/details/88843698
下面的配置是前端控制器DispatcherServlet
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置dispatcher.xml作为mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
前端控制器实例化过程中,会创建第二个上下文,这个上下文主要管理如下bean:处理器映射器、处理器适配器、视图解析器等。
其余部分待补充...
2 流程
3 源码分析
3.1 启动
对于springmvc,我们会打包成war包部署到tomcat。这里的启动指的是Tomcat的启动。
我们以第四部分示例项目为例说明,在web.xml文件中我们配置了前端控制器DispatcherServlet,并设置了跟随Tomcat一起启动。
我们通过这个博文https://www.cnblogs.com/zhenjingcool/p/15878453.html我们了解到,在Tomcat启动后,容器加载DispatcherServlet时,首先调用init方法。
GenericServlet的init方法
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
该方法是由Tomcat容器调用的,传入的参数config是容器生成的。
注意:调试的时候会发现这个方法会调用多次。原因是:我们idea启动时,除了当前web应用被部署外,还会默认部署manager应用。而这两个应用中都有多个随Tomcat一起启动的servlet,每个servlet都会调用一次init方法。具体细节参考https://www.cnblogs.com/zhenjingcool/p/16542157.html的1.5小结 (idea部署tomcat的方式分析)
然后执行this.init()调用HttpServletBean的init()方法
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
然后调用initServletBean();该方法在FrameworkServlet中实现
protected final void initServletBean() throws ServletException {
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
}
我们看一下 this.webApplicationContext = initWebApplicationContext();
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext wac = this.webApplicationContext;
onRefresh(wac);
return wac;
}
这里调用了onRefresh()方法,该方法在DispatcherServlet中实现。
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
又调用了initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
我们重点看一下 initHandlerMappings(context); 和 initHandlerAdapters(context); ,分别是初始化处理器映射器和处理器适配器。
initHandlerMappings方法初始化处理器映射器,如果web.xml中配置了处理器映射器,将在这里处理。或者如果applicationContext.xml中配置了 <mvc:annotation-driven /> 将会在根上下文中默认添加RequestMappingHandleMapping和BeanNameUrlHandlerMapping这两个bean,DispatcherServlet实例化的时候会获取父容器中的实现了HandleMapping的实例作为自身的处理器映射器。否则,将使用默认的。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 在ApplicationContext查找所有的HandlerMapping.,如果我们在web.xml中配置了处理器映射器,这里会获取到
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
// 如果web.xml中没有配置处理器映射器,这里添加默认的
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
在该博文末尾提供的实例项目中,我们在web.xml中没有配置处理器映射器,在applicationContext.xml中也没有配置<mvc:annocation-driven/>,所以会执行
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
我们看一下这个方法
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
return strategies;
}
else {
return new LinkedList<>();
}
}
其中 private static final Properties defaultStrategies; 是一个Properties对象的实例,在该实例中为我们提供了如下2个默认处理器映射器
org.springframework.web.servlet.HandlerMapping ->
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
然后会遍历这两个默认处理器映射器,执行如下方法,我们看一下
Object strategy = createDefaultStrategy(context, clazz);
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}
这里会实例化 BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping
我们先看一下实例化 BeanNameUrlHandlerMapping 的过程。
在实例化之前会先执行BeanPostProcessor,其中就包括ApplicationContextAwareProcessor,调用其postProcessBeforeInitialization方法,最终会导致 BeanNameUrlHandlerMapping 其父类的如下方法被调用
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
registerHandler(urls, beanName);
}
}
}
这里获取到applicationContext所有的bean定义,然后遍历这些bean,判断beanname是否以"/"开头,然后添加到列表urls里面返回,这也是为啥web.xml中配置的controller对应的名称必须以"/"开头的原因,如下:
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
如果beanname以"/"开头,则添加到this.handlerMap中
this.handlerMap.put(urlPath, resolvedHandler);
最终,我们得到的这个handlerMapper里面有一个元素

实例化 RequestMappingHandlerMapping 的过程类似,这里不再赘述。
至此处理器映射器初始化完成,处理器映射器里面包含了所有url和controller的对应关系。
我们再回到 DispatcherServlet 的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
我们接着看处理器适配器的初始化过程 initHandlerAdapters(context);
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
// 在ApplicationContext中找到所有的处理器适配器.如果我们在web.xml中配置了处理器适配器,这里会获取到
Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
// 如果web.xml中没有配置处理器适配器,这里创建默认的
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
}
}
在实例项目中,我们没有在web.xml中配置处理器适配器,所以会创建默认的处理器适配器 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
和处理器映射器的逻辑相同,springmvc为我们准备了3个默认处理器适配器
org.springframework.web.servlet.HandlerAdapter ->
org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
最终DispatcherServlet中得到一个this.handlerAdapters列表,包含以上3个处理器适配器。
我们再回到DispatcherServlet的initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
同样的分析过程,我们可以分析视图解析器的初始化过程 initViewResolvers(context); ,这里不再赘述,只说明一点,因为我们在实例项目中的web.xml中配置了视图解析器,所以不会创建默认的视图解析器,会使用我们配置的视图解析器。
3.2 处理请求
以示例代码中的get /hello为例
我们在web.xml中配置了DispatcherServlet并且配置了所有url都通过这个Servlet处理。根据Servlet运行原理https://www.cnblogs.com/zhenjingcool/p/15878453.html我们知道,Tomcat容器接收到请求之后,首先调用的是Servlet的service方法。
HttpServlet
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);
}
}
然后,调用重载方法service(request,response)
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
doGet(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
}//其他method省略
}
根据请求类型,分别调用FrameworkServlet的doGet和doPost方法
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
然后,调用processRequest方法(省略了不重要的代码)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doService(request, response);
}
然后到达DispatcherServlet的doService方法
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
doDispatch(request, response);
}
然后调用doDispatch方法,这个方法才是我们要看的重点
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
ModelAndView mv = null;
// 获取请求对应的处理器(对应的Controller)
mappedHandler = getHandler(processedRequest);
// 获取对应的处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 调用拦截器
mappedHandler.applyPreHandle(processedRequest, response)
// 调用处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 调用拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
}
首先,获取请求对应的处理器
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
这是一个HandlerExecutionChain对象,里面包含了要调用的Controller和拦截器Interceptor。
然后获取处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
在3.1我们知道,我们始化了3个处理器适配器,这里遍历这3个处理器适配器,分别调用supports方法,判断是否支持这个handler。
其中SimpleControllerHandlerAdapter的supports方法如下:
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
我们的/hello对应的MyController满足这个条件,因此,返回的处理器适配器就是SimpleControllerHandlerAdapter。
再回到doDispatch方法
我们已经获取到处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 下一步,调用处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
调用处理器MyController,返回一个ModelAndView对象
public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv=new ModelAndView();
//封装对象,放在ModelAndView中
mv.addObject("msg", "Hello!SpringMVC!~~");
//封装要跳转的视图,放在ModelAndView中。
mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
return mv;
}
}
再回到doDispatch方法,调用processDispatchResult方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (mv != null) {
render(mv, request, response);
}
}
如果mv非空,则调用render方法
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
String viewName = mv.getViewName();
View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
view.render(mv.getModelInternal(), request, response);
}
该方法把Controller获取到的ModelAndView解析到Model(也就是要显示的数据),并且把Model塞入View中(jsp中),最终封装在response中返回。
此处有三个点要注意
1 ModelAndView,包含了view名称,model数据
2 将model取出来(要显示的数据),填充view(这里是jsp)
3 对于现在前后端分离的应用,往往mv=null,不存在render这一步。
4 如果model或者view没有返回,可能此时这一个请求处理已经完成(比如带有@ResponseBody注解的方法,会在Controller中就直接响应response,而不会返回ModelAndView)
最终,返回HttpServlet的service方法
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
执行完该方法,res持有了我们要交给Tomcat容器的响应。把响应交给Tomcat容器,Tomcat容器再交给web服务器,web服务器最后交给网卡,网卡将响应封装成报文发送给客户端网卡,客户端网卡接收数据链路发来的物理帧,经过几层协议的转换,最终得到http报文。我们发送请求时一般使用的是浏览器,这里响应肯定也回到浏览器,浏览器解析http报文渲染页面。(当然如果不是浏览器发起的请求,比如curl发起,获取到http报文后没有渲染页面这一步了,直接展示http报文原文),tcp/ip报文传输过程可以参考https://www.cnblogs.com/zhenjingcool/p/15776825.html
4 示例项目
4.1 项目结构

4.2 web.xml
<!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>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--配置dispatcher.xml作为mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4.3 dispatcher-servlet.xml
XXX-servlet.xml是对XXX这个servlet的配置文件。这里是对web.xml中配置的dispatcher的配置文件。
这里配置了两个bean,分别是视图解析器和我们自定义的MyController
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--添加处理器映射-->
<!-- <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>-->
<!--添加处理器适配-->
<!-- <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>-->
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/><!--设置JSP文件的目录位置-->
<property name="suffix" value=".jsp"/>
</bean>
<bean id="/hello" class="com.szj.controller.MyController" />
</beans>
4.4 applicationContext.xml
这个文件默认是空的,对于容器的一些配置可以在这里配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
4.5 index.jsp
默认首页
<html>
<body>
<h2>Tomcat Server!</h2>
</body>
</html>
4.6 hello.jsp
对应MyController的视图文件。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
4.7 MyController
自定义处理器,实现了Controller接口,该接口有一个handlerRequest方法。
public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//ModelAndView 模型和视图
ModelAndView mv=new ModelAndView();
//封装对象,放在ModelAndView中
mv.addObject("msg", "Hello!SpringMVC!~~");
//封装要跳转的视图,放在ModelAndView中。
mv.setViewName("hello"); //WEB-INF/jsp/hello.jsp
return mv;
}
}
2787

被折叠的 条评论
为什么被折叠?



