做过web项目的小伙伴,对于SpringMVC,Struts2都是在熟悉不过了,再就是我们比较古老的servlet,我们先来复习一下我们的servlet生命周期。
servlet生命周期
上述文字摘自http://c.biancheng.net/view/3989.html
整个过程是比较复杂的,而且我们的参数是通过问号的形式来传递的,比如http://boke?id=1234,id为1234来传递的,如果我们要http://boke/1234这样来传递参数,servlet是做不到的,我们来看一下我们SpringMVC还有哪些优势。
1.基于注解方式的URL映射。比如http://boke/type/{articleType}/id/{articleId}
2.表单参数自动映射,我们不在需要request.getParament得到参数,参数可以通过name属性来自动映射到我们的控制层下。
3.缓存的处理,SprinMVC提供了缓存来提高我们的效率。
4.全局异常处理,通过过滤器也可以实现,只不过SprinMVC的方法会更简单一些。
5.拦截器的实现,通过过滤器也可以实现,只不过SprinMVC的方法会更简单一些。
6.下载处理
我们来对比一下SprinMVC的流程图。
SprinMVC的流程图
下面我们先熟悉一下源码,来个实例,来一个最精简启动SpringMVC。
最精简启动SpringMVC
建立Maven项目就不说了啊,先设置我们的pom文件
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
</dependencies>
再来编写我们的Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>spring mvc</display-name>
<servlet>
<servlet-name>dispatcherServlet</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>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
我们来简单些一个Controller
package com.springmvcbk.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class SpringmvcbkController implements Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/WEB-INF/page/index.jsp");
modelAndView.addObject("name","张三");
return modelAndView;
}
}
写一个index.jsp页面吧。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
good man is love
${name}
</body>
</html>
最后还有我们的spring-mvc.xml
<?xml version="1.0" encoding="GBK"?>
<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">
<bean name="/hello" class="com.springmvcbk.controller.SpringmvcbkController"/>
</beans>
注意自己的路径啊,走起,测试一下。
这样我们最精简的SpringMVC就配置完成了。讲一下这段代码是如何执行的,上面图我们也看到了,请求过来优先去找我们的dispatchServlet,也就是我们Spring-MVC.xml配置文件,通过name属性来找的。找到我们对应的类,我们的继承我们的Controller接口来处理我们的请求,也就是图中的3,4,5步骤。然后再把结果塞回给dispatchServlet。返回页面,走起。
这个是我们表层的理解,后续我们逐渐会深入的,我们再来看另外一种实现方式。
package com.springmvcbk.controller;
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SpringmvcbkController2 implements HttpRequestHandler {
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
httpServletRequest.setAttribute("name","李斯");
httpServletRequest.getRequestDispatcher("/WEB-INF/page/index.jsp").forward(httpServletRequest,httpServletResponse);
}
}
这种方式也是可以的。
整个过程是如何实现的? 1. dispatchServlet 如何找到对应的Control? 2. 如何执行调用Control 当中的业务方法? 在面试中要回答好上述问题,就必须得弄清楚spring mvc 的体系组成。
spring mvc 的体系组成
只是举了几个例子的实现,SpringMVC还有很多的实现方法。我们来看一下内部都有什么核心的组件吧。
HandlerMapping->url与控制器的映谢
HandlerAdapter->控制器执行适配器
ViewResolver->视图仓库
view->具体解析视图
HandlerExceptionResolver->异常捕捕捉器
HandlerInterceptor->拦截器
稍后我们会逐个去说一下这些组件,我们看一下我们的UML类图吧,讲解一下他们之间是如果先后工作调用的。
图没上色,也没写汉字注释,看着有点蒙圈....我来说一下咋回事。HTTPServlet发出请求,我们的DispatcherServlet拿到请求去匹配我们的HandlerMapping,经过HandlerMapping下的HandlerExecutionChain,HandlerInterceptor生成我们的Handl,返回给DispatcherServlet,拿到了Handl,给我们的Handl传递给HandlerAdapter进行处理,得到我们的View再有DispatcherServlet传递给ViewResolver,经由View处理,返回response请求。
我们先来看看我们的Handler是如何生产的。
Handler
这个是SpringMVC自己的继承UML图,最下层的两个是我们常用的,一个是通过name来注入的,一个是通过注解的方式来注入的,他是通过一系列的HandlerInterceptor才生成我们的Handler。
目前主流的三种mapping 如下
1. SimpleUrlHandlerMapping:基于手动配置url与control映谢 2. BeanNameUrlHandlerMapping: 基于ioc name 中已 "/" 开头的Bean时行 注册至映谢. 3. RequestMappingHandlerMapping:基于@RequestMapping注解配置对应映谢
另外两个不说了,太常见不过了。我们来尝试自己配置一个SimpleUrlHandlerMapping
<?xml version="1.0" encoding="GBK"?>
<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">
<bean name="hello2" class="com.springmvcbk.controller.SpringmvcbkController2"/>
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<props>
<prop key="hello.do">hello2</prop>
</props>
</property>
</bean>
</beans>
注意SimpleUrlHandlerMapping是没有/的,而我们的BeanNameUrlHandlerMapping必须加/的。
我们来走一下动态代码,只看取得Handler这段,(初始化的阶段可以自己研究一下)
1 /**
2 * Return the HandlerExecutionChain for this request.
3 * <p>Tries all handler mappings in order.
4 * @param request current HTTP request
5 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
6 */
7 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
8 for (HandlerMapping hm : this.handlerMappings) {
9 if (logger.isTraceEnabled()) {
10 logger.trace(
11 "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
12 }
13 HandlerExecutionChain handler = hm.getHandler(request);
14 if (handler != null) {
15 return handler;
16 }
17 }
18 return null;
19 }
我们找到我们的DispatcherServlet类的getHandler方法上。在源码的1150行,也就是我上图的第7行。打个断点。优先遍历我们handlerMappings集合,找到以后去取我们的handler。
HandlerExecutionChain handler = hm.getHandler(request);方法就是获得我们的Handler方法,这里只是获得了一个HandlerExecutionChain执行链,也就是说我们在找到handler的前后都可能做其它的处理。再来深入一下看getHandler方法。
这时会调用AbstractHandlerMapping类的getHandler方法,然后优先去AbstractUrlHandlerMapping的getHandlerInternal取得handler
1 /**
2 * Look up a handler for the URL path of the given request.
3 * @param request current HTTP request
4 * @return the handler instance, or {@code null} if none found
5 */
6 @Override
7 protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
8 String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);//取得路径
9 Object handler = lookupHandler(lookupPath, request);//拿着路径去LinkedHashMap查找是否存在
10 if (handler == null) {
11 // We need to care for the default handler directly, since we need to
12 // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
13 Object rawHandler = null;
14 if ("/".equals(lookupPath)) {
15 rawHandler = getRootHandler();
16 }
17 if (rawHandler == null) {
18 rawHandler = getDefaultHandler();
19 }
20 if (rawHandler != null) {
21 // Bean name or resolved handler?
22 if (rawHandler instanceof String) {
23 String handlerName = (String) rawHandler;
24 rawHandler = getApplicationContext().getBean(handlerName);
25 }
26 validateHandler(rawHandler, request);
27 handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
28 }
29 }
30 if (handler != null && logger.isDebugEnabled()) {
31 logger.debug("Mapping [" + lookupPath + "] to " + handler);
32 }
33 else if (handler == null && logger.isTraceEnabled()) {
34 logger.trace("No handler mapping found for [" + lookupPath + "]");
35 }
36 return handler;
37 }
得到request的路径,带着路径去我们已经初始化好的LinkedHashMap查看是否存在。
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or {@code null} if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);//拿着路径去LinkedHashMap查找是否存在
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern +"/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
到这里其实我们就可以得到我们的Handler了,但是SpringMVC又经过了buildPathExposingHandler处理,经过HandlerExecutionChain,看一下是否需要做请求前处理,然后得到我们的Handler。得到Handler以后也并没有急着返回,又经过了一次HandlerExecutionChain处理才返回的。
图中我们可以看到去和回来的时候都经过了HandlerExecutionChain处理的。就这样我们的handler就得到了。注意的三种mapping的方式可能有略微差异,但不影响大体流程。
HandlerAdapter
拿到我们的Handler,我们该查我们的HandlerAdapter了,也就是我们的适配器。我们回到我们的DispatchServlet类中
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
还是我们的循环调用,我们的适配器有四种,分别是AbstractHandlerMethodAdapter,HTTPRequestHandlerAdapter,SimpleControllerHandlerAdapter,SimpleServletHandlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());方法开始处理我们的请求,返回ModelAndView。
返回以后,我们交给我们的ViewResolver来处理。
ViewResolver
ContentNegotiatingViewResolver下面还有很多子类,我就不展示了。 选择对应的ViewResolver解析我们的ModelAndView得我到我们的view进行返回。
说到这一个请求的流程就算是大致结束了。我们来看两段核心的代码。
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
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 {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
这个是DispatchServlet类里面doDispatch方法,也就是我们请求来的时候进行解析的方法。
/**
* Render the given ModelAndView.
* <p>This is the last stage in handling a request. It may involve resolving the view by name.
* @param mv the ModelAndView to render
* @param request current HTTP servlet request
* @param response current HTTP servlet response
* @throws ServletException if view is missing or cannot be resolved
* @throws Exception if there's a problem rendering the view
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
// Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}
这个是DispatchServlet类里面render方法,也就是我们处理完成要返回时的方法。大家有兴趣的可以逐行逐步的去走下流程。里面东西也不少的,这里就不一一讲解了。
最近搞了一个个人公众号,会每天更新一篇原创博文,java,python,自然语言处理相关的知识有兴趣的小伙伴可以关注一下。