转载请声明出处:https://juejin.cn/post/6844903550242258958
В двух предыдущих статьях непосредственно анализировался исходный код компонентов в SpringMVC, и многие мелкие партнеры могут немного запутаться. Итак, сегодня я вернусь, чтобы поговорить о базовом контроллере SpringMVC и использовать его в качестве оси для изучения всей системы знаний SpringMVC.
Как SpringMVC используется в проекте?
ранее в "Структура разработки проекта — SSM«В статье уже подробно представлены некоторые файлы конфигурации Spring в проекте SSM. Для приложения Spring важно:
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- <param-value>classpath*:config/applicationContext.xml</param-value> -->
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<!-- 配置一个监听器将请求转发给 Spring框架 -->
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
Завершите инициализацию контейнера Spring и загрузку Bean через ContextLoadListener.Инсайдерское обучение технологии Spring: процесс запуска Spring". Затем, если нам нужно предоставить WEB-функции, нам нужна еще одна, то есть SpringMVC. Конечно, нам также нужна конфигурация для инициализации SpringMVC (процесс инициализации 9 основных компонентов: первые две статьи "Серия исходников SpringMVC: HandlerMapping"и"Серия исходных кодов SpringMVC: AbstractHandlerMapping》 относится к HnadlerMapping, конечно, не только к этим двум, но и к нескольким другим важным подклассам, которые будут постоянно обновляться в будущем):
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springMVC需要加载的配置文件 spring-dao.xml,spring-service.xml,spring-web.xml
Mybatis(如果有) - > spring -> springmvc -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<!-- 默认匹配所有的请求 -->
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
Когда мы настроим вышеуказанное содержимое в web.xml (конечно, мы должны убедиться, что наша конфигурация Spring и файл конфигурации SpringMVC не являются проблемой), запустите веб-контейнер (например, причал), вы можете ввести, например: http:/ /localhost:80/myproject/index.do для доступа к нашему приложению.
Как говорится, мы это знаем, так почему же так, так почему же мы можем получить доступ к нашему SSM-проекту после настройки соответствующих конфигурационных файлов? От отправки такого запроса (http://localhost:80/myproject/index.do) до показа окончательного интерфейса, что Spring делает для нас в этом процессе? (Инициализация контейнера SpringIOC выполняетсяТехнология Inside Spring — обновление контейнера: wac.refresh》В тексте уже примерно сказано, что можно на него ссылаться)
Процесс обработки запросов SpringMVC
Во-первых, давайте воспользуемся следующей картинкой, чтобы понять весь процесс обработки запроса SpringMVC; с 1 по 13 на рисунке в целом описан такой процесс от запроса, отправленного на дисплей интерфейса.
DispatcherServlet
Хорошо, давайте посмотрим непосредственно на определение класса DispatcherServlet:
public class DispatcherServlet extends FrameworkServlet
DispatcherServlet наследуется от FrameworkServlet, и все?
Прежде всего, почему должен быть зеленый отдел? Некоторые студенты, возможно, уже думали, что зеленая часть - это не Spring, а сама Java; Spring успешно имеет JAVA WEB-линию через HttpServletBean, молодой человек (Первоначально Spring был написан на JAVA, ха-ха). Что касается сервлета, вы можете прочитать мою предыдущую статью, в которой кратко представлен этот интерфейс.
Сказав это, поскольку DispatcherServlet в конечном счете является сервлетом, он должен иметь функциональное поведение сервлета.
敲黑板!!!Servlet的生命周期是啥(init->service->destroy : 加载->实例化->服务->销毁)。
На самом деле то, что я хочу сказать здесь, это сервисный метод.Конечно, в DispatcherServlet нет сервисного метода, но есть метод doService! (Трудно цитировать...)
doService — это точка входа DispatcherServlet, давайте взглянем на этот метод:
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) + "]");
}
// 在include的情况下保留请求属性的快照,以便能够在include之后恢复原始属性。
Map<String, Object> attributesSnapshot = null;
//确定给定的请求是否是包含请求,即不是从外部进入的顶级HTTP请求。
//检查是否存在“javax.servlet.include.request_uri”请求属性。 可以检查只包含请求中的任何请求属性。
//(可以看下面关于isIncludeRequest解释)
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));
}
}
}
// 使框架可用于handler和view对象。
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用于保存转发请求的参数的
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);
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
PS:“javax.servlet.include.request_uri”是INCLUDE_REQUEST_URI_ATTRIBUTE常量的值。isIncludeRequest(request)方法的作用我们可以借助一条JSP的指令来理解:
<jsp:incluede page="index.jsp"/>
这条指令是指在一个页面中嵌套了另一个页面,那么我们知道JSP在运行期间是会被编译成相应的Servlet类来运行的,所以在Servlet中也会有类似的功能和调用语法,这就是RequestDispatch.include()方法。 那么在一个被别的servlet使用RequestDispatcher的include方法调用过的servlet中,如果它想知道那个调用它的servlet的上下文信息该怎么办呢,那就可以通过request中的attribute中的如下属性获取:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
В doService это можно увидеть в блоке try ниже:
try {
doDispatch(request, response);
}
doService не обрабатывает его напрямую, второй — передать запрос в doDispatch для конкретной обработки. Конечно, перед вызовом doDispatch doService также выполняет некоторые действия, такие как определение того, является ли запрос включенным, установка некоторых атрибутов запроса и т. д.
Проблема передачи параметров перенаправления, поддерживаемая FlashMap
В doService, в дополнение к четырем параметрам webApplicationContext, localeResolver, themeResolve и themeSource, предоставляемым обработчику и представлению, последние три связаны с FlashMap Код выглядит следующим образом:
//FlashMap用于保存转发请求的参数的
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);
Как упоминалось в комментариях, FlashMap в основном используется для передачи параметров при переадресации Redirect;
Для вышеуказанной проблемы мы можем использовать FlashMap для передачи параметров, нам нужно записать необходимые параметры в OUTPUT_FLASH_MAP_ATTRIBUTE перед перенаправлением, например:
ServletRequestAttributes SRAttributes = (ServletRequestAttributes)(RequestContextHolder.getRequestAttributes());
HttpServletRequest req = SRAttributes.getRequest();
FlashMap flashMap = (FlashMap)(req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE));
flashMap.put("myname","glmapper_2018");
Таким образом, в обработчике после перенаправления spring автоматически установит его на модель. Но если это именно так, не кажется ли очень безвкусным писать приведенный выше фрагмент кода каждый раз, когда вы перенаправляете? Конечно, Spring также предоставляет нам более удобное использование, то есть мы можем использовать переменную типа RedirectAttributes в параметрах нашего метода-обработчика. кусок кода:
@RequestMapping("/detail/{productId}")
public ModelAndView detail(HttpServletRequest request,HttpServletResponse
response,RedirectAttributes attributes, @PathVariable String productId) {
if (StringUtils.isNotBlank(productId)) {
logger.info("[产品详情]:detail = {}",JSONObject.toJSONString(map));
mv.addObject("detail",JSONObject.toJSONString(getDetail(productId)));
mv.addObject("title", "详情");
mv.setViewName("detail.ftl");
}
//如果没有获取到productId
else{
attributes.addFlashAttribute("msg", "产品不存在");
attributes.addFlashAttribute("productName", productName);
attributes.addFlashAttribute("title", "有点问题!");
mv.setViewName("redirect:"/error/fail.htm");
}
return mv;
}
Этот код является абстракцией исходного возврата ошибки бизнес-логики, когда я сделал глобальный модуль обработки ошибок некоторое время назад.Поскольку невозможно единообразно обрабатывать ошибки, невозможно напрямую вернуться к интерфейсу ошибок в конкретном обработчике, поэтому все Обработка ошибок перенаправляется в метод-обработчик error/fail.htm для обработки. Проблема параметров перенаправления была описана выше, поэтому я не буду здесь вдаваться в подробности, просто приведу простой пример и предысторию, как использовать RedirectAttributes.
Принцип RedirectAttributes тоже очень прост, что равносильно существованию сессии, но сессия уничтожается после однократного использования, то есть если редирект выполняется после его получения в методе fail.htm, параметры будут потеряны. Продолжайте использовать RedirectAttributes в файле fail.htm для сохранения параметров и передачи их следующему обработчику.
метод doDispatch
Чтобы не лениться, выше принудительно вставлено объяснение проблемы передачи параметра редиректа в Spring. Вернемся к нашему методу doDispatch.
Роль: обработать фактическую отправку обработчику. Обработчик будет получен путем применения HandlerMappings сервлета по порядку. HandlerAdapter найдет первый HandlerAdapter, который поддерживает класс обработчика, запросив установленные HandlerAdapter сервлета. Все методы HTTP обрабатываются этим методом. HandlerAdapter или сам обработчик должны решить, какие методы являются приемлемыми.
На самом деле основной код в doDispatch состоит всего из 4 строк.Давайте посмотрим:
- Найдите нашего обработчика по запросу
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
- Найдите соответствующий HandlerAdapter в соответствии с обработчиком
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
- HandlerAdapter обрабатывает обработчик
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- Вызовите метод processDispatchResult для обработки результатов, полученных в описанном выше процессе, включая поиск представления и отрисовку вывода пользователю.
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
Возьмем вышеприведенное за ось и посмотрим на весь его исходный код (конкретное значение кода отмечено в коде):
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//当前请求request
HttpServletRequest processedRequest = request;
//处理器链(handler和拦截器)
HandlerExecutionChain mappedHandler = null;
//用户标识multipartRequest(文件上传请求)
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//很熟悉吧,这个就是我们返回给用户的包装视图
ModelAndView mv = null;
//处理请求过程中抛出的异常。这个异常是不包括渲染过程中抛出的异常的
Exception dispatchException = null;
try {
//检查是不是上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 通过当前请求确定相应的handler
mappedHandler = getHandler(processedRequest);
//如果没有找到:就会报异常,这个异常我们在搭建SpringMVC应用时会经常遇到:
//No mapping found for HTTP request with URI XXX in
//DispatcherServlet with name XXX
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 根据handler找到HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//处理GET和Head请求的Last-Modified
//获取请求方法
String method = request.getMethod();
//这个方法是不是GET方法
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;
}
}
//这里就是我们SpringMVC拦截器的preHandle方法的处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用具体的Handler,并且返回我们的mv对象.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果需要异步处理的话就直接返回
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//这个其实就是处理视图(view)为空的情况,会根据request设置默认的view
applyDefaultViewName(processedRequest, mv);
//这里就是我们SpringMVC拦截器的postHandle方法的处理
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);
}
//处理返回结果;(异常处理、页面渲染、拦截器的afterCompletion触发等)
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()) {
// 如果是的话,就替代拦截器的postHandle 和 afterCompletion方法执行
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 删除上传请求的资源
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
В целом, doDispatch делает две вещи:
- обработать запрос
- рендеринг страницы
Блок-схема обработки doDispatch
Вышеприведенное является общим содержанием всего DispatcherServlet.Что касается инициализации контейнера SpringMVC, мы вернемся к изучению после завершения девяти основных компонентов, задействованных в DispatcherServlet. Было две статьи о HandlerMapping о девяти основных компонентах.Поскольку мы планируем разобраться во всей архитектуре SpringMVC, мы представим девять основных компонентов от дизайна интерфейса и подклассов через исходный код.
Серия исходников SpringMVC: HandlerMapping
Серия исходных кодов SpringMVC: AbstractHandlerMapping
大家如果有什么意见或者建议可以在下方评论区留言,也可以给我们发邮件(glmapper_2018@163.com)!欢迎小伙伴与我们一起交流,一起成长。