(直接看答案可點擊上面 “一句話總結:SpringMVC運行原理” )
什麼是SpringMVC?
SpringMVC是對Servlet的封裝
SpringMVC是對Servlet的封裝
SpringMVC是對Servlet的封裝
在沒有使用SpringMVC框架的時候,如果想寫一個Servlet請求分發器,我們會怎麼寫呢?
//接收除jsp外的所有請求,再進行分發
@WebServlet("/")
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String result = req.getParameter("control");
if (result.equals("m1")) {
method1();
} else if (result.equals("m2")) {
method2(req, resp);
} else if (result.equals("m3")) {
method3();
}
}
private void method1() {
}
private void method2(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("demo.jsp").forward(req, resp);
}
private void method3() {
}
}
以上是我們自定義的DispatcherServlet,但是SpringMVC有它自己的想法,SpringMVC對Servlet的封裝思想,跟上述代碼是類似的。
SpringMVC的重要組件
爲了完成請求分發和處理,SpringMVC設計了以下組件
//前端控制器(分發器);相當於@WebServlet("/")
DispatcherServlet ds
//映射器,解析請求格式,判斷要執行哪個方法
//相當於 if(req.getParameter("control").equals("m1"))
HandlerMapping hm
//適配器,負責調用具體的方法;相當於method1();
HandlerAdapter ha
//視圖解析器,解析結果,準備跳轉到具體的物理視圖
//相當於req.getRequestDispatcher("demo.jsp").forward(req, resp);
ViewResolver vr
SpringMVC 的 DispatcherServlet
當SpringMVC接收到請求時,首先會創建前端控制器DispatcherServlet
這裏可以看到,DispatcherServlet 繼承自 HttpServlet。而我們知道,Servlet首次加載時,調用順序是 init() -> service() -> destroy(),我們一個方法一個方法地看。
- init()
HttpServletBean.class中(留意中文註釋即可)
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 bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//進入
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
FrameworkServlet.class中
protected final void initServletBean() throws ServletException, BeansException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//進入
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (BeansException 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() throws BeansException {
//SpringMVC容器(ConfigurableWebApplicationContext)
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// No fixed context defined for this servlet - create a local one.
//Spring容器
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
//spring容器是springmvc容器的父容器
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
至此,init方法結束,可以看到init()主要是創建了springmvc容器。
- 下面我們來看service()
DispatcherServlet.class中
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug(
"DispatcherServlet with name '" + getServletName() + "' received request for [" + requestUri + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
//把請求參數做成一個HashMap保存
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
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));
}
}
}
// Make framework objects available to handlers and view objects.
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());
try {
//進入
doDispatch(request, response);
}
finally {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
// Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isDebugEnabled()) {
logger.debug("Bound request context to thread: " + request);
}
try {
ModelAndView mv = null;
try {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
//mappedHandler相當於是HandlerMapping映射器;解析並匹配 handler
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Apply preHandle methods of registered interceptors.
//調用攔截器方法(與本主題無關)
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// Actually invoke the handler.
// 適配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//HandlerAdapter調用方法;生成ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// Apply postHandle methods of registered interceptors.
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
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);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
getServletName() + "': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
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);
}
// Reset thread-bound context.
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
// Clear request attributes.
requestAttributes.requestCompleted();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + request);
}
}
}
值得注意的是,HandlerMapping和HandlerAdapter都是在DispatcherServlet類的方法中被使用的。我們再來看看HandlerMapping和HandlerAdapter內部都做了什麼?
選取HandlerMapping的實現類SimpleUrlHandlerMapping和HandlerAdapter的實現類SimpleControllerHandlerAdapter進行考察。
首先在springmvc.xml 配置文件中
<!-- 控制器 -->
<bean id="demo123" class="com.bjsxt.controller.DemoController"></bean>
<bean
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<!-- 解析出來的控制器邏輯名 -->
<entry key="demo" value-ref="demo123"></entry>
</map>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
當程序加載springmvc.xml時,SimpleUrlHandlerMapping.class中
protected void registerHandlers(Map urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
Iterator it = urlMap.keySet().iterator();
while (it.hasNext()) {
String url = (String) it.next();
Object handler = urlMap.get(url);
// Prepend with slash if not already present.
//這裏代表上述key="demo"中無論是否加 /,最終都有/
if (!url.startsWith("/")) {
url = "/" + url;
}
// Remove whitespace from handler bean name.
if (handler instanceof String) {
handler = ((String) handler).trim();
}
//url對應“/demo”;handler對應“demo123”
//進入
registerHandler(url, handler);
}
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
String handlerName = (String) handler;
if (getApplicationContext().isSingleton(handlerName)) {
//這裏拿到了id="demo123"的bean,即對應的控制器
resolvedHandler = getApplicationContext().getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map handler [" + handler + "] to URL path [" + urlPath +
"]: There is already handler [" + resolvedHandler + "] mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isDebugEnabled()) {
logger.debug("Root mapping to handler [" + resolvedHandler + "]");
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isDebugEnabled()) {
logger.debug("Default mapping to handler [" + resolvedHandler + "]");
}
setDefaultHandler(resolvedHandler);
}
else {
// 做一個(解析後的)url路徑和對應控制器方法的映射表
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isDebugEnabled()) {
logger.debug("Mapped URL path [" + urlPath + "] onto handler [" + resolvedHandler + "]");
}
}
}
}
再來看SimpleControllerHandlerAdapter.class
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 調用控制器方法
// 本例中是調用(id="demo123"的)com.bjsxt.controller.DemoController中的方法
return ((Controller) handler).handleRequest(request, response);
}
一句話總結:SpringMVC運行原理
①.當在web.xml中設置DispatcherServlet的爲 / ,用戶發起請求,DispatcherServlet接收。
②.DispatcherServlet調用HandlerMapping解析URL,並找到對應的控制器方法(Handler對象)
③. DispatcherServlet調用HandlerAdapter,HandlerAdapter調用Controller中的方法
④.方法執行完成後會返回一個ModelAndView對象
⑤. ViewResovler對ModelAndView進行視圖解析,返回View
⑥. DispatcherServlet對View(如jsp)進行渲染(即將模型數據填充至視圖中)
⑦. DispatcherServlet響應用戶
(DispatcherServlet - 前端控制器;Controller - 後端控制器)
ps:前端設計模式
圍繞 (web.xml中配置的) DispatcherServlet進行初始化,即DispatcherServlet做中央處理器。
- DispatcherServlet中initStrategies方法
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
}