SringMVC從入門到源碼,這一篇就夠
SpringMVC簡介
Java行業(yè)的誰人不知SSM框架呢?除非你告訴我剛學(xué)Java,我就相信你不知道SpringMVC。
關(guān)于SringMVC的由來和干嘛用的基本都不用介紹了,基本都知道了。但是有一點(diǎn)可以肯定的是:有很多人只停留在SpringMVC使用層面,對(duì)于SpringMVC的底層原理和源碼卻沒有深入了解過。
這一期我們就來了解「SpringMVC的底層原理和源碼」,在以前的JSP時(shí)代,代碼中前端和后端都混在一起,可能比較老的程序員就寫過下面的代碼,這就是大名鼎鼎的JSP和Servlet時(shí)代。
在一些老的項(xiàng)目中可能就會(huì)出現(xiàn)這樣的代碼,這樣的代碼是不是看起來非常的帶勁,要是讓你維護(hù)這樣的代碼,想死的心都有。
這樣的代碼前端和后端混在一起,相互依賴JSP與Java Bean之間嚴(yán)重耦合,java代碼和Html代碼混在一起,這要求開發(fā)人員既要會(huì)前端也要會(huì)后端,給測試帶來了很多不方便,代碼也不能復(fù)用。
諸如此類的問題,為了解決這樣的問題,首先就是將這些代碼進(jìn)行嚴(yán)格的劃分,前端與后端的代碼分開,逐漸出現(xiàn)代碼的分層架構(gòu),各層職責(zé)分明。
但是,這樣的模型層也還會(huì)有問題,首先每個(gè)模塊就需要一個(gè)Servlet控制器,模塊多的,控制器就會(huì)變得很多,這樣會(huì)導(dǎo)致控制器復(fù)雜。
并且更換視圖技術(shù)麻煩,嚴(yán)重依賴Servlet API。Java Bean結(jié)構(gòu)包含持久化層以及業(yè)務(wù)的處理,數(shù)據(jù)的封裝,這樣就會(huì)導(dǎo)致Java Bean結(jié)構(gòu)臃腫。
按照我們現(xiàn)在代碼的分層,可以把Java Bean又分為「持久層(dao)和服務(wù)層(Service)」 以及我們的 「應(yīng)用控制層(Controller)」。
SpringMVC原理
為了簡化控制層(Servlet),在SpringMVC框架中使用「DispatcherServlet(前端控制器)」 調(diào)度我們自己的「應(yīng)用控制層(Controller)」。
就這樣逐漸的演變,出現(xiàn)了我們現(xiàn)在真正意義上的Web MVC三層架構(gòu),具體的結(jié)構(gòu)圖如下所示:
首先來說明一下SpringMVC幾個(gè)核心的組件:
- DispatcherServlet:前端前端控制器主要負(fù)責(zé)調(diào)度工作,進(jìn)行全局的流程控制。比如:調(diào)度HandlerMapping然后返回執(zhí)行鏈。
 - HandlerMapping:處理器映射器會(huì)返回一個(gè)執(zhí)行鏈,通俗來講也就是執(zhí)行的邏輯順序,執(zhí)行鏈中包含多個(gè)「Interceptor(攔截器)」 和一個(gè)「Handler(處理器)」。
 - HandlerAdapter:處理器適配器里面包含了處理器的調(diào)用,使用適配器的設(shè)計(jì)原則,通過反射調(diào)用我們自己的Controller。
 - Handler:處理器也就是我們的Controller,用戶對(duì)應(yīng)的請(qǐng)求URL請(qǐng)求過來,通過請(qǐng)求與我們Controller的映射規(guī)則(HandlerMapping)相對(duì)應(yīng)起來,這個(gè)就是處理器。
 - ModelAndView:模型和視圖,模式(Model)也就是我們的數(shù)據(jù),通過上面反射調(diào)用Handler(Controller)生成的數(shù)據(jù),以及邏輯視圖(View)。邏輯視圖并不是真正的視圖名,它只是一個(gè)邏輯視圖名,比如:index。
 - View:視圖,這時(shí)候才會(huì)通過上面生成的邏輯視圖名生成對(duì)應(yīng)的物理視圖,返回前端呈現(xiàn)用戶。
 
Hello World上面說了那么多,其實(shí)還是要在項(xiàng)目進(jìn)行實(shí)踐中才會(huì)有深刻的體會(huì),下面我們通過實(shí)際一個(gè)案例進(jìn)行上面的深刻的理解。
這里我使用idea搭建SSM項(xiàng)目,還用Eclipse的同學(xué),建議你該換工具了,首先New - Project
然后左邊選擇Maven,右邊勾選Create from archetype,并且選擇webapp模塊:
下面就是填寫一些GroupId以及ArtifactId,這些比較簡單就直接跳過了,不然會(huì)被大佬diss死了,創(chuàng)建完項(xiàng)目后的基本目錄結(jié)構(gòu)如下:
并且在resource目錄下分別創(chuàng)建下面四個(gè)配置文件「applicationContext.xml、jdbc.properties、log4j.properties、spring-mvc.xml」。
applicationContext.xml是Spring的核心配置文件,內(nèi)容如下:
- <?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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 - xsi:schemaLocation="http://www.springframework.org/schema/beans
 - http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
 - http://www.springframework.org/schema/tx
 - http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 - <!-- 加載properties文件 -->
 - <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 - <property name="location" value="classpath:jdbc.properties"/>
 - </bean>
 - <!-- 配置數(shù)據(jù)源 -->
 - <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
 - <property name="driverClassName" value="${driver}"/>
 - <property name="url" value="${url}"/>
 - <property name="username" value="${username}"/>
 - <property name="password" value="${password}"/>
 - </bean>
 - <!-- mybatis和spring完美整合,不需要mybatis的配置映射文件 -->
 - <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 - <property name="dataSource" ref="dataSource"/>
 - <!-- 掃描model包 -->
 - <property name="typeAliasesPackage" value="com.ldc.model"/>
 - <!-- 掃描sql配置文件:mapper需要的xml文件 -->
 - <property name="mapperLocations" value="classpath:mapper/*.xml"/>
 - </bean>
 - <!-- Mapper動(dòng)態(tài)代理開發(fā),掃描dao接口包-->
 - <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 - <!-- 注入sqlSessionFactory -->
 - <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
 - <!-- 給出需要掃描Dao接口包 -->
 - <property name="basePackage" value="com.ldc.dao"/>
 - </bean>
 - <!-- 事務(wù) -->
 - <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 - <property name="dataSource" ref="dataSource"/>
 - </bean>
 - </beans>
 
這個(gè)文件主要配置了數(shù)據(jù)源、數(shù)據(jù)庫的來連接的配置信息,數(shù)據(jù)的詳細(xì)信息就放在jdbc.properties中:
- driver=com.mysql.cj.jdbc.Driver
 - url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
 - username=root
 - password=user
 - initialSize=0
 - maxActive=20
 - maxIdle=20
 - minIdle=1
 - maxWait=60000
 
這里的數(shù)據(jù)庫信息,你們只要修改數(shù)據(jù)庫的用戶名了密碼就行了,其它的作為測試信息基本就不用修改了。
接下來就是日志的配置信息log4j.properties,這里只做簡單的日志配置:
- #日志輸出級(jí)別
 - log4j.rootLogger=debug,stdout,D,E
 - #設(shè)置stdout的日志輸出控制臺(tái)
 - log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 - #輸出日志到控制臺(tái)的方式,默認(rèn)為System.out
 - log4j.appender.stdout.Target = System.out
 - #設(shè)置使用靈活布局
 - log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 - #靈活定義輸出格式
 - log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} -[%p] method:[%c (%rms)] - %m%n
 
配置完日志信息后,接著配置spring-mvc.xml,這個(gè)的SpringMVC框架內(nèi)的信息配置文件:
這和配置信息也很簡單,主要包括「開啟注解驅(qū)動(dòng)、包掃描、視圖解析器的配置」。
配置完SpringMVC后,最后就是配置web.xml,web.xml是前端請(qǐng)求的入口文件:
- <?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-3.0.xsd">
 - <!-- 掃描注解,這樣com.xjt包下的文件都能被掃描 -->
 - <context:component-scan base-package="com.ldc"/>
 - <!-- 開啟SpringMVC注解模式 -->
 - <mvc:annotation-driven/>
 - <!-- 靜態(tài)資源默認(rèn)servlet配置 -->
 - <mvc:default-servlet-handler/>
 - <!-- 配置返回視圖的路徑,以及識(shí)別后綴是jsp文件 -->
 - <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 - <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
 - <property name="prefix" value="/WEB-INF/jsp/"/>
 - <property name="suffix" value=".jsp"/>
 - </bean>
 - </beans>
 
在web.xml中主要包含:「默認(rèn)歡迎頁面的配置、字符編碼過濾器的配置、前端控制器、以及指定spring核心配置文件和SpringMVC的配置文件」。
以上就是最基本的配置,其它的配置信息一般是按需配置,這樣配置完后,我們搭建一個(gè)簡單的SSM的項(xiàng)目基本已經(jīng)完成了。
最后的Maven的坐標(biāo)依賴,如下:
- <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 - xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
 - version="3.1">
 - <display-name>mvcDemo</display-name>
 - <!--項(xiàng)目的歡迎頁,項(xiàng)目運(yùn)行起來后訪問的頁面-->
 - <welcome-file-list>
 - <welcome-file>index.jsp</welcome-file>
 - </welcome-file-list>
 - <!-- 注冊ServletContext監(jiān)聽器,創(chuàng)建容器對(duì)象,并且將ApplicationContext對(duì)象放到Application域中 -->
 - <listener>
 - <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 - </listener>
 - <!-- 指定spring核心配置文件 -->
 - <context-param>
 - <param-name>contextConfigLocation</param-name>
 - <param-value>classpath:applicationContext.xml</param-value>
 - </context-param>
 - <!-- 解決亂碼的過濾器 -->
 - <filter>
 - <filter-name>CharacterEncodingFilter</filter-name>
 - <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
 - <init-param>
 - <param-name>encoding</param-name>
 - <param-value>utf-8</param-value>
 - </init-param>
 - <init-param>
 - <param-name>forceEncoding</param-name>
 - <param-value>true</param-value>
 - </init-param>
 - </filter>
 - <filter-mapping>
 - <filter-name>CharacterEncodingFilter</filter-name>
 - <url-pattern>/*</url-pattern>
 - </filter-mapping>
 - <!-- 配置前端控制器 -->
 - <servlet>
 - <servlet-name>springmvc</servlet-name>
 - <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 - <!-- 指定配置文件位置和名稱 如果不設(shè)置,默認(rèn)找/WEB-INF/<servlet-name>-servlet.xml -->
 - <init-param>
 - <param-name>contextConfigLocation</param-name>
 - <param-value>classpath:spring-mvc.xml</param-value>
 - </init-param>
 - <load-on-startup>1</load-on-startup>
 - <async-supported>true</async-supported>
 - </servlet>
 - <servlet-mapping>
 - <servlet-name>springmvc</servlet-name>
 - <url-pattern>/</url-pattern>
 - </servlet-mapping>
 - </web-app>
 
maven做表中主要開發(fā)包含的依賴數(shù)據(jù)庫驅(qū)動(dòng)、日志、mybaties、spring坐標(biāo)、web mvc的坐標(biāo)、以及繼承JSP的坐標(biāo)。
這里前端技術(shù)可以繼承你們自己想要的:Freemarker或者Thymeleaf,只需要引入相關(guān)的Maven坐標(biāo),因?yàn)镴SP已經(jīng)基本被淘汰了,這里只是為了作測試,并不關(guān)心前端用什么技術(shù)。
我們在controller包下創(chuàng)建我們自己的測試類:UserController:
- @Controller
 - @RequestMapping("/user")
 - public class UserController {
 - @Autowired
 - private IUserService userService;
 - @RequestMapping("/getUserById")
 - public ModelAndView selectUser(@PathVariable("id") Long id) throws Exception {
 - ModelAndView mv = new ModelAndView();
 - User user = userService.selectUser(id);
 - mv.addObject("user", user);
 - mv.setViewName("user");
 - return mv;
 - }
 - }
 
這里簡單解釋一下:
- @Controller:標(biāo)名它是一個(gè)控制器,被Spring容器所管理,這個(gè)注解是在@Component后面出的,為了表示代碼的分層,于是就有了@Controller、@Service、@Mapper這三個(gè)注解,他們的作用是一樣的。
 - @RequestMapping:表示接受的請(qǐng)求,還是GetMapping、PostMapping等注解表示請(qǐng)求方法的不同。
 - @Autowired:表示自動(dòng)注入,前提就是被注入的對(duì)象被Spring容器所管理。
 - ModelAndView:這個(gè)前面說過,它裝的就是數(shù)據(jù)和邏輯視圖名。
 
這些還是比較簡單的,通過下面配置Tomcat信息進(jìn)行部署,就可以啟動(dòng)項(xiàng)目進(jìn)行測試了:
DispatcherServlet源碼解析
這個(gè)還是比較簡單的,還不會(huì)可以自行百度,啟動(dòng)項(xiàng)目后我們來測試一下前面,出現(xiàn)下面的界面說明,你搭建SSM項(xiàng)目的基本環(huán)境已經(jīng)成功了:
那么我們的前端請(qǐng)求是怎么一步一步的從「前臺(tái)->后臺(tái)->前臺(tái)」的呢?其實(shí)前面我們已經(jīng)說了SpringMVC的基本原理,在這個(gè)基本原理的基礎(chǔ)上,從源碼的角度,進(jìn)行詳細(xì)的解析:
上面說到SpringMVC的核心調(diào)度器就是DispatcherServlet,負(fù)責(zé)主流程的調(diào)度工作,在DispatcherServlet里面最主要的方法就是doDispatch:
- protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 - HttpServletRequest processedRequest = request;
 - HandlerExecutionChain mappedHandler = null;
 - int interceptorIndex = -1;
 - try {
 - ModelAndView mv;
 - boolean errorView = false;
 - try {
 - //檢查是否是請(qǐng)求是否是multipart(如文件上傳),如果是將通過MultipartResolver解析
 - processedRequest = checkMultipart(request);
 - //步驟2、請(qǐng)求到處理器(頁面控制器)的映射,通過HandlerMapping進(jìn)行映射
 - mappedHandler = getHandler(processedRequest, false);
 - if (mappedHandler == null || mappedHandler.getHandler() == null) {
 - noHandlerFound(processedRequest, response);
 - return;
 - }
 - //步驟3、處理器適配,即將我們的處理器包裝成相應(yīng)的適配器(從而支持多種類型的處理器)
 - HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
 - // 304 Not Modified緩存支持
 - //此處省略具體代碼
 - // 執(zhí)行處理器相關(guān)的攔截器的預(yù)處理(HandlerInterceptor.preHandle)
 - //此處省略具體代碼
 - // 步驟4、由適配器執(zhí)行處理器(調(diào)用處理器相應(yīng)功能處理方法)
 - mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
 - // Do we need view name translation?
 - if (mv != null && !mv.hasView()) {
 - mv.setViewName(getDefaultViewName(request));
 - }
 - // 執(zhí)行處理器相關(guān)的攔截器的后處理(HandlerInterceptor.postHandle)
 - //此處省略具體代碼
 - }
 - catch (ModelAndViewDefiningException ex) {
 - logger.debug("ModelAndViewDefiningException encountered", ex);
 - mv = ex.getModelAndView();
 - }
 - catch (Exception ex) {
 - Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
 - mv = processHandlerException(processedRequest, response, handler, ex);
 - errorView = (mv != null);
 - }
 - //步驟5 步驟6、解析視圖并進(jìn)行視圖的渲染
 - //步驟5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
 - //步驟6 視圖在渲染時(shí)會(huì)把Model傳入(view.render(mv.getModelInternal(), request, response);)
 - if (mv != null && !mv.wasCleared()) {
 - render(mv, processedRequest, response);
 - if (errorView) {
 - WebUtils.clearErrorRequestAttributes(request);
 - }
 - }
 - else {
 - if (logger.isDebugEnabled()) {
 - logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
 - "': assuming HandlerAdapter completed request handling");
 - }
 - }
 - // 執(zhí)行處理器相關(guān)的攔截器的完成后處理(HandlerInterceptor.afterCompletion)
 - //此處省略具體代碼
 - catch (Exception ex) {
 - // Trigger after-completion for thrown exception.
 - triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
 - throw ex;
 - }
 - catch (Error err) {
 - ServletException ex = new NestedServletException("Handler processing failed", err);
 - // Trigger after-completion for thrown exception.
 - triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
 - throw ex;
 - }
 - finally {
 - // Clean up any resources used by a multipart request.
 - if (processedRequest != request) {
 - cleanupMultipart(processedRequest);
 - }
 - }
 - }
 
這個(gè)方法不長,基本就是負(fù)責(zé)其它方法的調(diào)用,從我們上面分析到前端請(qǐng)求第一步到達(dá)SpringMVC后調(diào)用HandlerMapping(處理器映射器)返回執(zhí)行鏈HandlerExecutionChain:
我們debug啟動(dòng)項(xiàng)目,打個(gè)斷點(diǎn)看看,這個(gè)HandlerExecutionChain到底是個(gè)什么東西。
我們可以看到,當(dāng)斷點(diǎn)執(zhí)行到HandlerExecutionChain后,查看HandlerExecutionChain中的handler其實(shí)就是我們自己的請(qǐng)求訪問的Controller,比如上面的我們請(qǐng)求登陸操作,handler里面的信息就是我們自己的LoginController。
同時(shí)包含LoginController的BeanType,前端要請(qǐng)求的方法,以及參數(shù)這個(gè)元數(shù)據(jù)信息,簡單的概括就是:「HandlerExecutionChain里面handler就是我們要請(qǐng)求的Controller以及和一些interceptors信息」。
那么在獲取到這個(gè)HandlerExecutionChain之前肯定是有初始化所有的Spring容器中的Bean以及所有的url與Bean對(duì)應(yīng)的HandlerMapping對(duì)象。
這個(gè)都是在Spring中去完成的,這個(gè)我們后面在做了解,我們再進(jìn)一步的了解HandlerMapping對(duì)象存儲(chǔ)的內(nèi)容,再getHandler方法里面進(jìn)行打斷點(diǎn):
handlerMapping是一個(gè)List對(duì)象,里面主要是這七個(gè)成員信息,我們比較熟悉的就是BeanNameUrlMapping和SimpleUrlHandlerMapping對(duì)象,這些里面可以看出「handlerMapping主要存儲(chǔ)的各種映射規(guī)則」,通過beanName或者url映射到對(duì)應(yīng)的Bean對(duì)象。
繼續(xù)往里面看,可以看到這里有個(gè)applicationContext對(duì)象,這個(gè)也就我們的上下文,里面還有beanFactory,也就是Spring管理的Bean對(duì)象都在這個(gè)工廠里面,包括Spring自己的和我們自己定義Bean信息。
這個(gè)就是HandlerMapping對(duì)象,主要「包含著的Bean映射規(guī)則、Bean詳細(xì)信息?!?/p>
從HandlerMapping->HandlerExecutionChain的過程,用一句通俗易懂的話概括就是:「從茫茫的人海中找到了你(從beanFactory找到了請(qǐng)求對(duì)應(yīng)的Controller以及方法)」。
當(dāng)獲取完我們的執(zhí)行鏈后,接著就是獲取我們的「處理器適配器」(HandlerAdapter),
從getHandlerAdapter的方法中可以看到,根據(jù)返回的handlerMapping對(duì)象中的handler對(duì)象來獲取對(duì)應(yīng)的HandlerAdapter對(duì)象,直接返回。
返回HandlerAdapter對(duì)象后,通過執(zhí)行HandlerAdapter的handle方法獲取ModelAndView對(duì)象,從這個(gè)方法的上面的注釋來看:Actually invoke the handler.。
實(shí)際就是通過「反射」的方式動(dòng)態(tài)的執(zhí)行我們自己的Controller中的方法,也就是前端請(qǐng)求的Controller,因?yàn)閙appedHandler.getHandler()返回的「handler對(duì)象包含著請(qǐng)求Controller的詳細(xì)信息,包括全類名」。
獲取到ModelAndView之后,接著就執(zhí)行我們的攔截器的后置處理方法postHandle。
從他的源碼可以看出,它是獲取到所有的攔截器,然后一個(gè)一個(gè)遍歷,執(zhí)行。
執(zhí)行完所有攔截器的后置處理方法,就是最后䣌視圖的渲染,這里執(zhí)行的是processDispatchResult方法,并把ModelAndView對(duì)象作為參數(shù)傳遞進(jìn)去。
在processDispatchResult方法里面最重要的就是render方法了,執(zhí)行視圖的渲染,最后將渲染的結(jié)果呈現(xiàn)給用戶。
到這里DispatcherServlet主要執(zhí)行邏輯就講完了,其實(shí)主要講的還是SpringMVC的從前端請(qǐng)求->后臺(tái)->前端這樣的一個(gè)過程,限于篇幅,從源碼的角度大概講解這個(gè)的過程是怎么跑起來的。
一篇文章要把SpringMVC的都講清楚是不可能的,SpringMVC所有講下來,都能寫一本書了,后續(xù)的源碼我們繼續(xù)精進(jìn),這篇作為一個(gè)大體脈絡(luò)的了解。
本文轉(zhuǎn)載自微信公眾號(hào)「非科班的科班」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系非科班的科班公眾號(hào)。






































 
 
 











 
 
 
 