写在最前
一边读源码,一边写理解。连续写了一周,终于大概搞定了。
之前想一次性就搞定的,后来发现自己想的太简单了。所以准备之后再写写HandlerMapping、handlerAdapter以及ViewResolver。
写的过程有些痛苦,写完收获很大,推荐将SpringMVC作为源码阅读的起点。
一、认识SpringMVC
-
MVC模式是什么?
MVC模式是一种架构模式,用于将代码在数据、逻辑、界面层级分离。
M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)
V-View 视图(做界面的展示 jsp,html……)
C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)
SpringMVC是什么?
SpringMVC是一个MVC模式的WEB框架。
更确切的说,SpringMVC是Spring框架的一个模块,类似IOC,AOP这样的模块,所以经常会听到SpringMVC可以和Spring无缝集成,其实Springmvc就是Spring的子模块,所以根本不需要进行整合。为什么要使用SpringMVC?
至于为什么要使用SpringMVC,上面已经说了SpringMVC就是Spring的子模块,所以这个问题一部分可以转换成为什么要使用Spring。除去为什么要使用Spring这个老问题,那么就是SpringMVC不存在和Spring整合的问题,与Struts2相比安全性高,上手快速。
二、SpringMVC的使用
-
我们要使用SpringMVC,要先将DispatcherServlet注册到容器中。那么为什么要这么做呢?
从下面类关系图上可以看到,DispatcherServlet继承了HttpServlet,所以DispatcherServlet本质是一个Servlet。想要使用servlet,就需要将Servlet注册到Servlet容器(如Tomcat)中。
- Servlet注册到Servlet容器
- 比较常用的做法是在web.xml中配置<servelt>,拦截所有请求到DispatcherServlet去处理。
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
- 有些项目中,是没有web.xml的。是因为在Servlet 3.0以后,可以通过实现WebApplicationInitializer接口的方式将DispatcherServlet注册到容器中。
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
@Configuration
@ComponentScan("controller")
public class AppConfig {
}
- 使用SpringMVC,除了将DispatcherServlet注册到容器,还需要实现Controller控制器。
- 实现Controller接口,实现handleRequest方法。
import org.springframework.web.servlet.mvc.Controller;
@Component("/test2")
public class BeanNameController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws Exception {
System.out.println("this is a BeanNameController");
return null;
}
}
- 使用@Controller注解,配合@RequestMapping注解实现Controller
@Controller
public class RequesetMappingController {
@RequestMapping("test1")
public String testRequesetMappingController(){
return "this is a RequesetMappingController";
}
}
三、SpringMVC的流程
首先来看SpringMVC流程图,对SpringMVC流程有个大概的理解**
1.1 用户发送一个http请求,被前端控制器接收(DispatcherServlet)。
1.2 前端控制器对请求做一个分发与封装,通过处理器映射器(HandleMapping)查找url对应的handler并将handler返回给前端控制器。这时候已经找到了handler,但是并没有执行。因为handler中只保存了Controller的类或者方法路径。
1.3 前端控制器将包含Controller路径的Handler传递给处理器适配器(HandlerAdapter),由处理器适配器执行具体的业务后,将结果ModelAndView返回给前端控制器。
1.4 前端控制器得到ModelAndView,通过视图名称查找对应的视图,将视图渲染。
1.5 响应用户请求。SpringMVC为什么要这么设计,而不是直接由url找到对应的业务代码?
所以先简单的解释一下,实现Controller控制器的方式有两种,可以通过注解,也可以通过实现接口。通过@Controller注解的方式,每一个类中可以存在多个映射的方法。通过实现接口的方式,每一个类中只存在一个映射的方法。因为两种方式迥异,为了查找方便,SpringMVC在执行初始化时,便加载所有Controller,将url和含有Controller对象或方法路径的Handler分别放入两个不同HandlerMapping中,以key和value的形式存储,保证了url和Handler的一一对应关系。当接受到url请求时,只需要遍历handlerMappings下的两个HandleMapping,就可以找到对应的handler。
handlerMappings,它的作用就是包含上面提到的两个不同的HandlerMapping,分别是BeanNameUrlHandlerMapping和RequestMappingHandlerMapping,值得注意的是,这两个HandlerMapping是读取配置文件,通过反射加载的两个对象。从框架层面上讲,这样设计的好处是,当框架业务扩展,增添其他方式注册Controller,也可以直接在配置文件中添加handlerMappings(handlerAdapter同理),而不需要改变原有框架代码。
四. 解读源码
DispatcherServlet的本质是servlet。所以就必然存在 init()、doGet()、doPost()或者service()方法。
- 初始化init()
1.1 HttpServletBean.init()
DispatcherServlet 的 init() 方法在其父类 HttpServletBean 中实现的,它覆盖了 GenericServlet 的 init() 方法,主要作用是加载 web.xml 中 DispatcherServlet 的 配置,并调用子类的初始化。
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
...
}
}
// 让子类实现的方法,这种在父类定义在子类实现的方式叫做模版方法模式
this.initServletBean();
}
ServletConfigPropertyValues :是静态内部类,使用 ServletConfig 获取 web.xml 中配置的参数
1.2 FrameworkServlet.initServletBean()
在 HttpServletBean 的 init() 方法中调用了 initServletBean() 这个方法,它是在 FrameworkServlet 类中实现的,主要作用是建立 WebApplicationContext 容器(有时也称上下文),加载 SpringMVC 配置文件中定义的 Bean 到该容器中,最后将该容器添加到 ServletContext。
@Override
protected final void initServletBean() throws ServletException {
try {
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
} catch (RuntimeException | ServletException var5) {}
}
SpringMVC 容器的父容器是Spring容器,也就是说,SpringMVC可以使用Spring容器中的Bean,但是Spring不可以调用SpringMVC 容器中的Bean。
protected WebApplicationContext initWebApplicationContext() {
// 获取 ContextLoaderListener 初始化并注册在 ServletContext 中的根容器,即 Spring 的容器
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 将 Spring 的容器设为 SpringMVC 容器的父容器
cwac.setParent(rootContext);
}
//初始化SpringMVC容器
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 如果 WebApplicationContext 为空,则进行查找,能找到说明上下文已经在别处初始化。
wac = this.findWebApplicationContext();
}
if (wac == null) {
// 如果 WebApplicationContext 仍为空,则以 Spring 的容器为父上下文建立一个新的。
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// 模版方法,由 DispatcherServlet 实现
this.onRefresh(wac);
}
if (this.publishContext) {
// 发布这个 WebApplicationContext 容器到 ServletContext 中
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
}
return wac;
}
1.3 DispatcherServlet.onRefresh() 方法
建立好 WebApplicationContext(上下文) 后,通过 onRefresh(ApplicationContext context) 方法回调,进入 DispatcherServlet 类中。onRefresh() 方法中调用this.initStrategies(),提供 SpringMVC 的初始化。HandlerMappings,HandlerAdapters,ViewResolvers都是此时初始化的。
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
1.4 DispatcherServlet.initHandlerMappings()
这个方法从SpringMVC 的容器及 Spring 的容器中查找所有的 HandlerMapping 实例,并把它们放入到 handlerMappings 这个 list 中。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// 按一定顺序放置 HandlerMapping 对象
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
} else {
try {
HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException var3) {
;
}
}
if (this.handlerMappings == null) {
this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);
}
}
DispatcherServlet.initHandlerAdapters()、DispatcherServlet.initHandlerAdapters()相似
- 处理请求
2.1 doGet()、doPost()
HttpServlet 提供了 doGet()、doPost() 等方法,DispatcherServlet 中这些方法是在其父类 FrameworkServlet 中实现的,代码如下:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
2.2 processRequest() 方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// 返回与当前线程相关联的 LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 根据请求构建 LocaleContext,公开请求的语言环境为当前语言环境
LocaleContext localeContext = buildLocaleContext(request);
// 返回当前绑定到线程的 RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 根据请求构建ServletRequestAttributes
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//WebAsyncManager:用来管理异步请求的处理。什么时候要用到异步处理呢?就是业务逻辑复杂(或者其他原因),为了避免请求线程阻塞,需要委托给另一个线程的时候。
// 获取当前请求的 WebAsyncManager,如果没有找到则创建
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// 使 LocaleContext 和 requestAttributes 关联
initContextHolders(request, localeContext, requestAttributes);
try {
// 由 DispatcherServlet 实现
doService(request, response);
} catch (ServletException ex) {
} catch (IOException ex) {
} catch (Throwable ex) {
} finally {
// 重置 LocaleContext 和 requestAttributes,解除关联
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}// 发布 ServletRequestHandlerEvent 事件
publishRequestHandledEvent(request, startTime, failureCause);
}
}
2.3 核心部分 doDispatch()
DispatcherServlet 的 doService() 方法主要是设置一些 request 属性,并调用 doDispatch() 方法进行请求分发处理,doDispatch() 方法的主要过程是通过 HandlerMapping 获取 Handler,再找到用于执行它的 HandlerAdapter,执行 Handler 后得到 ModelAndView ,ModelAndView 是连接“业务逻辑层”与“视图展示层”的桥梁,接下来就要通过 ModelAndView 获得 View,再通过它的 Model 对 View 进行渲染。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request; //processedRequest:加工过的请求
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; //是否为上传上传
// 获取当前请求的WebAsyncManager,如果没找到则创建并与请求关联
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查是否有 Multipart,有则将请求转换为 Multipart 请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 遍历所有的 HandlerMapping 找到与请求对应的 Handler,并将其与一堆拦截器封装到 HandlerExecution 对象中。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 遍历所有的 HandlerAdapter,找到可以处理该 Handler 的 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理 last-modified 请求头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 遍历拦截器,执行它们的 preHandle() 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// 执行实际的处理程序
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
// 遍历拦截器,执行它们的 postHandle() 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 处理执行结果,是一个 ModelAndView 或 Exception,然后进行渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
} catch (Error err) {
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// 遍历拦截器,执行它们的 afterCompletion() 方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
2.4 执行实际的处理程序调用了handle方法
当Controller是实现接口的方式时,这时候可以直接执行handleRequest()方法。是使用注解的方式时,这个时候就通过反射调用相应Controller中的方法。
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller)handler).handleRequest(request, response);
}
五、反射简单概括
为什么要使用反射,除了降低解耦性以外,其实没有其他方式去调用到其他Controller中的方法的。这里没有对象可以注入,也没有办法去直接调用其他Controller,所以这里只能使用反射的方式。
通过反射得到形参和实参后,在request.getParameterValues()中查找paremeter为参数名的valus[],进行赋值。
有一点需要注意的是,参数名字的获取。在SpringMVC框架中,使用的技术是ASM javaasisst反编译字节码
在JDK1.8之前,直接拿参数名字是拿不到的,使用反射拿到的参数名,得到的是‘arg0’,’arg1’...
JDK1.8开始,允许拿到参数名称,但也需要一些配置,这里不做过多解释
六、SpringMVC中使用的设计模式
模板方法模式
框架中使用了很多的模板方法模式,比如DispatcherServlet本质是Servlet,具有init(),service(),destroy()等方法,tomcat运行时,会加载web.xml将中的servlet,servlet调用init()方法。init()中有很多工作需要准备,如初始化web容器,导入配置,初始化HandlerMappings、HandlerAdapters等等。
这些工作不是全部都在DispatcherServlet中完成,有一部分工作其是在其父类FrameworkServlet中完成的。当DispatcherServlet初始化,会调用HttpServletBean中的init(),然后执行FrameworkServlet中的initServletBean()完成初始化。迭代器模式
集合对象获取迭代器,通过迭代器本身的遍历方法获取数据,而不用关心遍历的具体实现方式。
Iterator var2 = this.handlerMappings.iterator();
适配器模式
Controller的实现方法有多种,如果直接将url和Controller进行映射,那么我们就要区Map中Controller对象,然后分别对Controller进行处理。另外一方面,当需要扩展Controller的实现方式,就需要对原有代码更改,违反开闭原则。
使用适配器模式,将多种Controller用Handler进行适配(不要和装饰者模式搞混,装饰者模式是对原有功能进行增强),使不同种类的对象可以在一起工作。关于设计模式的补充
框架中应用了很多设计模式,很多模式模式一眼看上去并不太和书上将设计模式的例子完全吻合。但仔细琢磨,思想却是一样的。也证明了设计模式是一种思想吧。
七、写在最后
在MVC模式中,Model层实体类有很多称呼,如:BO/PO/POJO/VO/entity 等等。
这些称呼的出现和设计理念有关,比如把一个对象用于写入数据库,那么就可以称之PO(persistent object);用作处理业务,就可以称之为BO(business object),如果想把一个业务对象写入数据库,根据设计理念,就要先把BO转换PO。
在Spring框架中也一样,不是所有的设计都是最合适的,也有一些设计是因为追寻当时的潮流理念而使用,阅读时要注意。