一、環境搭建
- 創建一個web項目。
- 如果是maven項目,則直接在pom中加入springMvc依賴
<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.1.9.RELEASE</version>
</dependency>
</dependencies>
如果不是就從把這些jar包丟到lib里面
image
二、SpringMVC配置
- 在web中配置servlet,
url-pattern中的/ 和 / * 有區別
<url-pattern> / </url-pattern> 不會匹配到*.jsp,即:*.jsp不會進入spring的 DispatcherServlet類 。
<url-pattern> /* </url-pattern> 會匹配*.jsp,會出現返回jsp視圖時再次進入spring的DispatcherServlet 類,導致找不到對應的controller所以報404錯。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!--SpringMvc開始-->
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--SpringMvc結束-->
</web-app>
- 創建springMvc-servlet.xml文件,放到WEB-INF路徑下
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool.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">
<mvc:annotation-driven />
<context:component-scan base-package="com.tianzeng"/>
<!--通用視圖解析器 -->
<bean id="viewResolverCommon"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
三、測試
創建Controller:
@Controller
public class HelloWorld {
@ResponseBody
@RequestMapping("/helloworld")
public String helloWorld(){
return "Hello world";
}
@RequestMapping("/hello")
public String hello(Model model){
model.addAttribute("msg","hello");
return "/hello";
}
}
創建資源文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
然后把項目丟到tomcat中啟動,訪問/helloworld就能夠看見Hello world了,訪問/hello能夠看到hello
四、源碼分析
springMvc 核心 servlet 結構圖
SpringMVC的servlet的有三個層次:分別是HttpServletBean、FrameworkServlet、DispatcherServlet。
初始化
- HttpServletBean初始化
HttpServletBean直接繼承了java的HttpServlet,創建時自動調用init方法進行初始化
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
// 使用BeanWrapper構造DispatcherServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true); // 設置DispatcherServlet屬性
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// 子類覆蓋此方法,一起給初始化了
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
- FrameworkServlet初始化
FrameworkServlet是HttpServletBean類的子類,所以初始化操作是覆蓋initServletBean
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// initServletBean的主要方法,初始化webApplicationContext
this.webApplicationContext = initWebApplicationContext();
// 這個里面沒有任何實現方法
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(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) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext); // 創建一個WebApplicationContext
}
if (!this.refreshEventReceived) {
onRefresh(wac); // 模板方法,子類DispatcherServlet會覆蓋這個方法進行初始化
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac); // 將新創建的容器設置到ServletContext中去
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
- DispatcherServlet初始化
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//初始化多媒體解析器
initMultipartResolver(context);
//初始化位置解析器
initLocaleResolver(context);
//初始化主題解析器
initThemeResolver(context);
//初始化HandlerMappings
initHandlerMappings(context);
// 初始化HandlerAdapters
initHandlerAdapters(context);
//初始化異常解析器
initHandlerExceptionResolvers(context);
//初始化請求到視圖名轉換器
initRequestToViewNameTranslator(context);
//初始化視圖解析器
initViewResolvers(context);
//初始化FlashMapManager
initFlashMapManager(context);
}
請求處理
- HttpServletBean請求處理
HttpServletBean沒有進行任何請求處理,只是參與了容器的初始化操作 - FrameworkServlet請求處理
service方法是HttpServlet中的方法,servlet容器把所有請求發送到該方法,該方法默認行為是轉發http請求到doXXX方法中,如果重載了該方法,默認操作被覆蓋,不再進行轉發操作
FrameworkServlet重寫了service方法,如果Http請求為PATCH則使用processRequest處理,否則使用父類的service去處理
但是除了doOptions、doTrace這兩個方法FrameworkServlet用了自己的實現,其他的處理最后都使用的是processRequest
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (RequestMethod.PATCH.name().equalsIgnoreCase(request.getMethod())) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
processRequest是FrameworkServlet這個類中最核心的方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
// 上面一大堆都是用于初始化一些亂七八糟的東東,下面的才是處理請求的
try {
// 模板方法,交由子類DispatcherServlet去處理
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
- DispatcherServlet請求處理
DispatcherServlet對于請求分為兩步,第一步是請求處理,第二步是渲染頁面
如果請求進來,會調用DispatcherServlet的doService方法去進行處理
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
// 上面對請求進行了一些處理,doDispatch才是去處理請求的方法
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
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);
// 去找對應的請求處理器
mappedHandler = getHandler(processedRequest);
// 如果找不到對應的請求處理器則直接返回404錯誤
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根據請求處理器,獲取執行操作的請求適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 獲取請求的方式
String method = request.getMethod();
boolean isGet = "GET".equals(method);
// head請求和get一樣,只是head只會取的HTTP header的信息。
if (isGet || "HEAD".equals(method)) {
// lastModified 屬性可返回文檔最后被修改的日期和時間。
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
// checkNotModified邏輯判斷當前lastModfied值和http header的上次緩存值,如果還沒有過期就設置304頭并且返回并結束整個請求流程。否則繼續。
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 實際上執行處理的方法,通過請求訪問對應的處理器,并且返回modelandview對象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果是異步請求直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 設置默認的視圖
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// 處理返回結果。包括異常處理、渲染頁面、發成完成通知出發Interceptor的afterCompletion
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()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 刪除上傳請求的資源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}