GVKun编程网logo

如何使用Spring MVC处理HTTP选项?(spring mvc处理流程)

15

本篇文章给大家谈谈如何使用SpringMVC处理HTTP选项?,以及springmvc处理流程的知识点,同时本文还将给你拓展(5)SpringMVC-http请求处理路线、HTTP请求处理流程-Spr

本篇文章给大家谈谈如何使用Spring MVC处理HTTP选项?,以及spring mvc处理流程的知识点,同时本文还将给你拓展(5)SpringMVC-http请求处理路线、HTTP请求处理流程-SpringMvc、java – 如何使用Spring MVC和Spring Security为资源处理程序启用HTTP缓存、Java框架-Spring MVC理解004-spring MVC处理请求等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

如何使用Spring MVC处理HTTP选项?(spring mvc处理流程)

如何使用Spring MVC处理HTTP选项?(spring mvc处理流程)

我想使用Spring MVC用我的控制器拦截OPTIONS请求,但是DispatcherServlet捕获了该请求。我该如何处理?

答案1

小编典典
@RequestMapping(value="/youroptions", method=RequestMethod.OPTIONS)public View getOptions() {}

您应该通过将其设置为dispatchOptionsRequest来配置dispatcherServlettrue

(5)SpringMVC-http请求处理路线

(5)SpringMVC-http请求处理路线




核心架构的具体流程步骤如下:

1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;

3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;

6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;

7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

 

用我的话来概括如下:

    由HandlerMapping得到HandlerExecutionChain(其包含Object handler,interceptors),handler就是Controller实例,接着由handler得到HandlerAdapter(ha),最后由ha.handle(..)得到ModelAndView。注意,在执行目标方法之前,会升序调用interceptors,之后又会倒序调用interceptors。那么,HandlerAdapter是个什么东西呢,我在DispatcherServlet.properties里可以看到默认配了3种adapter,其实还有一种adapter没加进配置文件,如果你了解springmvc的发展史和解决mvc的思路,你就会明白为什么有多种实现方式了。最初springmvc采用的是实现Controller接口的方式,javase 1.5开始,springmvc采用了更方便的注解方式来标明Controller类,这两种方式对应的adapter分别是SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter,所以我们可以重点关注AnnotationMethodHandlerAdapter。

下面来好好谈谈DispatcherServlet的部分代码

    我们注意到该类中定义了一些well-known常量,呵呵,众所周知的常量

 

public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";

	public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";

	public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";

	/**
	 * Well-known name for the HandlerMapping object in the bean factory for this namespace.
	 * Only used when "detectAllHandlerMappings" is turned off.
	 * @see #setDetectAllHandlerMappings
	 */
	public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";

	public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";

	public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";

	public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";

	public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";

	public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";

	private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
 我们在配置文件中给相关组件bean命名时,可以保持和只写常量值一致,毕竟别人的命名是相当标准的,不过,这从逻辑上不是必须的,你甚至可以不声明bean的id,因为该类提供了如下变量
/** Detect all HandlerMappings or just expect "handlerMapping" bean? */
	private boolean detectAllHandlerMappings = true;

	/** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */
	private boolean detectAllHandlerAdapters = true;

	/** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */
	private boolean detectAllHandlerExceptionResolvers = true;

	/** Detect all ViewResolvers or just expect "viewResolver" bean? */
	private boolean detectAllViewResolvers = true;

	/** Perform cleanup of request attributes after include request? */
	private boolean cleanupAfterInclude = true;

	/** MultipartResolver used by this servlet */
	private MultipartResolver multipartResolver;

	/** LocaleResolver used by this servlet */
	private LocaleResolver localeResolver;

	/** ThemeResolver used by this servlet */
	private ThemeResolver themeResolver;

	/** List of HandlerMappings used by this servlet */
	private List<HandlerMapping> handlerMappings;

	/** List of HandlerAdapters used by this servlet */
	private List<HandlerAdapter> handlerAdapters;

	/** List of HandlerExceptionResolvers used by this servlet */
	private List<HandlerExceptionResolver> handlerExceptionResolvers;

	/** RequestToViewNameTranslator used by this servlet */
	private RequestToViewNameTranslator viewNameTranslator;

	/** FlashMapManager used by this servlet */
	private FlashMapManager flashMapManager;

	/** List of ViewResolvers used by this servlet */
	private List<ViewResolver> viewResolvers;
初始化方法中,执行了组件的初始化

 

protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
 我们拿出initHandlerMappings(context)来瞅瞅

 

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				OrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we''ll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet ''" + getServletName() + "'': using default");
			}
		}
	}
 通过该方法我们可以知道,当detectAllHandlerMappings为true时,直接到mvc容器及其父容器中寻找所有类型为HandlerMapping的bean,而detectAllHandlerMappings默认恰为true,因此我们完全可以不声明bean的id。当detectAllHandlerMappings为false时(可通过在web.xml里配置servlet参数来改变默认值),则在mvc容器寻找名为"handlerMapping"的bean。如以上两种情况都没找到bean,将会实例化DispatcherServlet.properties中配置的bean。

    doService方法里有这么一段

 

// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		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));
				}
			}
		}
 这是用于对页面有include的情况做的请求数据快照处理。

下面进入重点doDispatch方法

 

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 {
			// multipart包装处理,我们常用CommonsMultipartResolver
                                processedRequest = checkMultipart(request);
				multipartRequestParsed = processedRequest != request;

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) { // 404
					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.  // 代码省略
				
                                // 调用目标方法前,顺序执行拦截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				try {
					// Actually invoke the handler.
					mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				}
				finally {
					if (asyncManager.isConcurrentHandlingStarted()) {
						return;
					}
				}

				applyDefaultViewName(request, mv);
				// 调用目标方法后逆序执行拦截器
                                mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			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()) {
				// Instead of postHandle and afterCompletion
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				return;
			}
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}

 

HandlerExecutionChain 

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (getInterceptors() != null) {
			for (int i = 0; i < getInterceptors().length; i++) {
				HandlerInterceptor interceptor = getInterceptors()[i];
				if (!interceptor.preHandle(request, response, this.handler)) {
					triggerAfterCompletion(request, response, null);
					return false;
				}
				this.interceptorIndex = i;
			}
		}
		return true;
	}

	/**
	 * Apply postHandle methods of registered interceptors.
	 */
	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
		if (getInterceptors() == null) {
			return;
		}
		for (int i = getInterceptors().length - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = getInterceptors()[i];
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}

	/**
	 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
	 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
	 * has successfully completed and returned true.
	 */
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
			throws Exception {

		if (getInterceptors() == null) {
			return;
		}
		for (int i = this.interceptorIndex; i >= 0; i--) {
			HandlerInterceptor interceptor = getInterceptors()[i];
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
 

HandlerInterceptor

public interface HandlerInterceptor {

	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
	    throws Exception;

	void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception;

	void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception;

}

 

 自定义

public class ExceptionHandler implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        request.setAttribute("exception",ex.getMessage());
        ModelAndView mav = new ModelAndView();
        mav.setViewName("/error");
        return mav;
    }
}
  

 

HTTP请求处理流程-SpringMvc

HTTP请求处理流程-SpringMvc

1、在SpringMVC的http请求处理过程中,包括了前端控制器(DispatcherServlet)、处理映射器(HandlerMapping)、处理适配器(HandlerAdapter)、处理器((Handler)Controller)、视图解析器(ViewReslover)、视图(View)这六大主要对象。他们负责对http请求做处理,具体流程如下图。

第一步:前端控制器dispatcher接受请求

    Client---url--->Dispatcher

第二步:前端控制器去发起handler映射查找请求

    Dispatcher---HttpServletRequest---> HandlerMapping

第三步:处理器映射器查找hanlder并返回HandlerExetuionChain

     Dispatcher <---HandlerExeutionChain---HandlerMapping

第四步:前端控制器发起请求处理器适配器请求执行

  Dispatcher---Handler---> HandlerAdapter

第五步:处理器适配器去调用handler执行

HandlerAdapter---HttpServletRequest> Handler(Controller)

第六步:处理器处理后返回ModelAndView给HandlerAdapter

HandlerAdapter <---ModelAndView---Handler(Controller)

第七步:处理器适配器将ModelAndView返回给前端控制器

Dispatcher <---ModelAndView---HandlerAdapter

第八步:前端控制器请求视图解析器解析ModelAndView

Dispatcher---ModelAndView---> ViewReslover

第九步:视图解析器解析视图后返回视图View给前端控制器

Dispatcher <---View---ViewReslover

第十步:前端控制器请求视图要求渲染视图

Dispatcher--->View--->render

第十一步:前端控制器返回响应

Response <---Dispatcher

源码探秘

  第一步接受请求:

我们可以来看看DispatcherServlet的继承结构


其实DispatcherServlet能处理请求是因为HttpServlet类的service方法,而HttpServlet又来自Servlet接口定义的规范。


可以看到抽象类HttpServlet实现了接口Servlet的service方法,根据请求类型不同执行了不同的方法(doGet,doPost)


当请进来后,由HttpServlet的子类FrameworkServlet重写的service方法执行请求,可以看到437行子类调用了父类的service方法,然后在父类执行doGet之类的方法时,由于子类FrameworkServlet重写了父类方法,交由子类执行,所以进到了我的doGet断点里面,它调用了处理请求方法。

接下来我们看看ProcessRequest方法的源码

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
        this.initContextHolders(request, localeContext, requestAttributes);

        try {
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            this.logResult(request, response, (Throwable)failureCause, asyncManager);
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }

    }

 

前面一系列初始化工作我们先不管,看看重要的部分,try里面的doService方法


跟踪进去看了一下,由于它是抽象方法,所以会由子类实现和执行,也就是我们的DispatchServlet类了


老规矩,先贴上源码,它是DispatchServlet的doService方法--------------------------------------------------------------------------------------------------------------

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();

            label95:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label95;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        if (this.flashMapManager != null) {
            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 {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

 

所以第一步也就完成了,第一步的任务就是走进这里来。

第二步:前端控制器去发起handler映射查找请求

Dispatcher---HttpServletRequest---> HandlerMapping

上面的源码中主要工作就是给request实例设置一系列参数,要注意的就是doDispatch方法,这里面就是mvc的核心了,前面第一张交互图里面的流程都是在这里实现的。


可以看到,通过HttpRequestServlet作为参数请求handlerMapping

第三步:处理器映射器查找hanlder并返回HandlerExetuionChain

     Dispatcher <---HandlerExeutionChain---HandlerMapping

可以看到上图中返回了mappedHandler变量,就是HandlerExtuceChain类型


可以看到,已经找到并返回了我们的HomeController处理器(Hanlder)

第四步:前端控制器发起请求处理器适配器请求执行

  Dispatcher---Handler---> HandlerAdapter


从508行可以看到,适配器传入handler对象和reaquest等信息,执行handler()方法

第五步:处理器适配器去调用handler执行

HandlerAdapter---HttpServletRequest> Handler(Controller)


从这里可以看到,调用的handlerInternal是个抽象方法,会调用子类的实现方法,子类由RequestMappingHandlerAdapter实现,这个类也是我们经常在xml里面配置的类


通过invokeHandlerMethod方法执行进到controller里面


方法执行后返回我们的index


第六步:处理器处理后返回ModelAndView给HandlerAdapter

HandlerAdapter <---ModelAndView---Handler(Controller)

通过调用invokeHandlerMethod方法返回ModelAndView


第七步:处理器适配器将ModelAndView返回给前端控制器

Dispatcher <---ModelAndView---HandlerAdapter


第八步:前端控制器请求视图解析器解析ModelAndView

Dispatcher---ModelAndView---> ViewReslover


第九步:视图解析器解析视图后返回视图View给前端控制器

Dispatcher <---View---ViewReslover


可以看到,返回的视图,url指向index.jsp页面

第十步:前端控制器请求视图要求渲染视图

Dispatcher--->View--->render

如果View对象不为空,将会调用render方法渲染


如果返回的是json对象,属于接口的,是不会走这里的


此时会找对应的视图解析器去渲染


里面其实也没干啥,就做了个跳转,到jsp页面去绑定数据


第十一步:前端控制器返回响应

Response <---Dispatcher


到这里也就基本上完了。


处理请求完成后做了个重置工作,然后发布一个事件,你可以选择监听这个事件,做相应处理。

再看看response里面


这个就是我们页面上的内容了。

java – 如何使用Spring MVC和Spring Security为资源处理程序启用HTTP缓存

java – 如何使用Spring MVC和Spring Security为资源处理程序启用HTTP缓存

我希望为某些静态资源(如图像)启用HTTP缓存,Spring Security对此进行了限制. (这些资源不是安全性关键,但也不应公开访问).如何避免Spring Security添加禁用缓存的HTTP响应头?

如果我将SetCachePeriod()添加到WebMvcConfigurerAdapter.addResourceHandlers()中的资源处理程序注册中,如下所示:

registry.addResourceHandler("/static/**")
  .addResourceLocations("classpath:/static/").setCachePeriod(3600);

资源仍然返回,并带有以下标题,禁用高速缓存:

Cache-Control: max-age=3600,must-revalidate
Expires: Mon,04 Aug 2014 07:45:36 GMT
Pragma: no-cache

我想避免在项目中引入任何XML配置,目前仅使用Java注释配置.

有没有比扩展Spring资源处理程序更好的解决方案?

解决方法

您可以使用webContentInterceptor资源来允许静态资源缓存.
<mvc:interceptors>
    <mvc:interceptor>
    <mvc:mapping path="/static/*"/>
        <bean id="webContentInterceptor">
            <property name="cacheSeconds" value="31556926"/>
            <property name="useExpiresHeader" value="true"/>
            <property name="useCacheControlHeader" value="true"/>
            <property name="useCacheControlNoStore" value="true"/>
        </bean>
   </mvc:interceptor>
</mvc:interceptors>

使用注释配置缓存拦截器是按照以下方式完成的.
在Web配置类中,您可以为WebContentInterceptor类添加一个bean,并将其添加到拦截器列表中.

@Bean
public WebContentInterceptor webContentInterceptor() {
    WebContentInterceptor interceptor = new WebContentInterceptor();
    interceptor.setCacheSeconds(31556926);
    interceptor.setUseExpiresHeader(true);;
    interceptor.setUseCacheControlHeader(true);
    interceptor.setUseCacheControlNoStore(true);
    return interceptor;
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(webContentInterceptor());
}

参考this site看看它是如何完成的.

Java框架-Spring MVC理解004-spring MVC处理请求

Java框架-Spring MVC理解004-spring MVC处理请求

spring MVC处理请求

 首先,理一下思路:

 

springMVC请求处理的概念图:说明:

1.用户发起请求到前端控制器。

2.前端控制器通过处理器映射器查找hander。

3.处理器映射器返回执行链。 a)hander对象 b)拦截器(集合)

4.前端控制器通处理器适配器包装,执行hander对象。思考:为什么要通过适配器来执行?

5.通过模型hander处理业务逻辑。

6.处理业务完成后,返回ModeAndView对象,其中有视图名称,模型数据。

7.将视图名称和模型数据返回到前端控制器。

8.前端控制器通过视图解释器查找视图对象。

9.视图解释器返回真正的视图。

10.前端控制器通过返回的视图和数据进行渲染。

11.返回渲染完成的视图。

12.将最终的视图返回给用户,产生响应。
//=====================================================================================//

1)当请求到达springmvc前段控制器的时候,会到达DispatcherServlet的doService()方法

2)接着会调用doDispatcher()方法

3)接着会调用getHandler(processedRequest)获取当前的处理器 

4)看getHandler(processedRequest)方法,会返回当前请求的处理器链。当前处理器联封装了负责请求的处理器及其方法

5)根据请求的处理器获取处理器适配器,通过调用getHandlerAdapter()获取

6)接下来调用handler()方法处理请求 

7)接着进入handler()方法瞧一瞧,来到了类的AbstractHandlerMethodAdapter的handleInternal()方法 

8)最后执行调用 

9)调用结束会返回modelAndView对象 

源码分析

我们这里分两步:

  首先分析HttpServletBean、FrameworkServlet和DispatcherServlet这三个Servlet的处理过程,这样大家可以明白从Servlet容器将请求交给Spring MVC一直到DispatcherServlet具体处理请求之前都做了些什么,最后再重点分析Spring MVC中最核心的处理方法doDispatch的结构。
       一、HttpServletBean
  HttpServletBean主要参与了创建工作,并没有涉及请求的处理。之所以单独将它列出来是为了明确地告诉大家这里没有具体处理请求。
       二、FrameworkServlet
  Servlet的处理过程:首先是从Servlet接口的service方法开始,然后在Http-Servlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost、doPut、doDelete、doOptions和doTrace七个方法,并且做了doHead、doOptions和doTrace的默认实现,其中doHead调用doGet,然后返回只有header没有body的response。
  在FrameworkServlet中重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法(除了doHead的所有处理请求的方法)。在service方法中增加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理;doOptions和doTrace方法可以通过设置dispatchOptionsRequest和dispatchTraceRequest参数决定是自己处理还是交给父类处理(默认都是交给父类处理,doOptions会在父类的处理结果中增加PATCH类型);doGet、doPost、doPut和doDelete都是自己处理。所有需要自己处理的请求都交给了processRequest方法进行统一处理。

下面来看一下service和doGet的代码,别的需要自己处理的方法都和doGet类似。
// org.springframework.web.servlet.FrameworkServlet
protected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  String method = request.getMethod();
  if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
    processRequest(request, response);
  }else {
    super.service(request, response);
  }
}
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  processRequest(request, response);
}

  我们发现这里所做的事情跟HttpServlet里将不同类型的请求路由到不同方法进行处理的思路正好相反,这里又将所有的请求合并到了processRequest方法。当然并不是说Spring MVC中就不对request的类型进行分类,而全部执行相同的操作了,恰恰相反,Spring MVC中对不同类型请求的支持非常好,不过它是通过另外一种方式进行处理的,它将不同类型的请求用不同的Handler进行处理,后面再详细分析。可能有的读者会想,直接覆盖了service不是就可以了吗?HttpServlet是在service方法中将请求路由到不同的方法的,如果在service中不再调用super.service(),而是直接将请求交给processRequest处理不是更简单吗?从现在的结构来看确实如此,不过那么做其实存在着一些问题。比如,我们为了某种特殊需求需要在Post请求处理前对request做一些处理,这时可能会新建一个继承自DispatcherServlet的类,然后覆盖doPost方法,在里面先对request做处理,然后再调用supper.doPost(),但是父类根本就没调用doPost,所以这时候就会出问题了。虽然这个问题的解决方法也很简单,但是按正常的逻辑,调用doPost应该可以完成才合理,而且一般情况下开发者并不需要对Spring MVC内部的结构非常了解,所以Spring MVC的这种做法虽然看起来有点笨拙但是是必要的。

下面就来看processRequest方法,processRequest是FrameworkServlet类在处理请求中最核心的方法。
// org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  long startTime = System.currentTimeMillis();
  Throwable failureCause = null;
  // 获取LocaleContextHolder中原来保存的LocaleContext
  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  // 获取当前请求的LocaleContext
  LocaleContext localeContext = buildLocaleContext(request);
  // 获取RequestContextHolder中原来保存的RequestAttributes
  RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();

  //获取当前请求的ServletRequestAttributes
  ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
  //将当前请求的LocaleContext和ServletRequestAttributes设置到LocaleContextHolder和RequestContextHolder
  initContextHolders(request, localeContext, requestAttributes);

  try {
  //实际处理请求入口
  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 {
  //恢复原来的LocaleContext和ServletRequestAttributes到LocaleContextHolder和RequestContextHolder中
  resetContextHolders(request, previousLocaleContext, previousAttributes);
  if (requestAttributes != null) {
  requestAttributes.requestCompleted();
}
  //省略了log代码
  //发布ServletRequestHandledEvent消息
  publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

  processRequest方法中的核心语句是doService(request,response),这是一个模板方法,在DispatcherServlet中具体实现。在doService前后还做了一些事情(也就是大家熟悉的装饰模式):首先获取了LocaleContextHolder和RequestContextHolder中原来保存的LocaleContext和RequestAttributes并设置到previousLocaleContext和previousAttributes临时属性,然后调用buildLocaleContext和buildRequestAttributes方法获取到当前请求的LocaleContext和RequestAttributes,并通过initContextHolders方法将它们设置到LocaleContextHolder和Request-ContextHolder中(处理完请求后再恢复到原来的值),接着使用request拿到异步处理管理器并设置了拦截器,做完这些后执行了doService方法,执行完后,最后(finally中)通过resetContextHolders方法将原来的previousLocaleContext和previousAttributes恢复到Locale-ContextHolder和RequestContextHolder中,并调用publishRequestHandledEvent方法发布了一个ServletRequestHandledEvent类型的消息。

  这里涉及了异步请求相关的内容,除了异步请求和调用doService方法具体处理请求,processRequest自己主要做了两件事情:

  ①对LocaleContext和RequestAttributes的设置及恢复;

  ②处理完后发布了Servlet-RequestHandledEvent消息。


  首先来看一下LocaleContext和RequestAttributes。LocaleContext里面存放着Locale(也就是本地化信息,如zh-cn等),RequestAttributes是spring的一个接口,通过它可以get/set/removeAttribute,根据scope参数判断操作request还是session。这里具体使用的是ServletRe-questAttributes类,在ServletRequestAttributes里面还封装了request、response和session,而且都提供了get方法,可以直接获取。下面来看一下ServletRequestAttributes里setAttribute的代码(get/remove都大同小异)。

// org.springframework.web.context.request.ServletRequestAttributes
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}else {
HttpSession session = getSession(true);
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}

  设置属性时可以通过scope判断是对request还是session进行设置,具体的设置方法非常简单,就是直接对request和session操作,sessionAttributesToUpdate属性后面讲到Session-AttributesHandler的时候再介绍,这里可以先不考虑它。需要注意的是isRequestActive方法,当调用了ServletRequestAttributes的requestCompleted方法后requestActive就会变为false,执行之前是true。这个很容易理解,request执行完了,当然也就不能再对它进行操作了!你可能已经注意到,在刚才的finally块中已调用requestAttributes的requestCompleted方法。
  现在大家对LocaleContext和RequestAttributes已经有了大概的了解,前者可以获取Locale,后者用于管理request和session的属性。不过可能还是有种没有理解透的感觉,因为还不知道它到底怎么用。不要着急,我们接下来看LocaleContextHolder和Request-ContextHolder,把这两个理解了也就全部明白了!
先来看LocaleContextHolder,这是一个abstract类,不过里面的方法都是static的,可以直接调用,而且没有父类也没有子类!也就是说我们不能对它实例化,只能调用其定义的static方法。这种abstract的使用方式也值得我们学习。在LocaleContextHolder中定义了两个static的属性。

// org.springframework.context.i18n.LocaleContextHolder
private static final ThreadLocal<LocaleContext> localeContextHolder =
new NamedThreadLocal<LocaleContext>("Locale context");

private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
new NamedInheritableThreadLocal<LocaleContext>("Locale context");

 

这两个属性都是ThreadLocal<LocaleContext>类型的,LocaleContext前面已经介绍了,ThreadLocal大家应该也不陌生,很多地方都用了它。

LocaleContextHolder类里面封装了两个属性localeContextHolder和inheritableLocale-ContextHolder,它们都是LocaleContext,其中第二个可以被子线程继承。Locale-ContextHolder还提供了get/set方法,可以获取和设置LocaleContext,另外还提供了get/setLocale方法,可以直接操作Locale,当然都是static的。这个使用起来非常方便!比如,在程序中需要用到Locale到时候,首先想到的可能是request.getLocale(),这是最直接的方法。不过有时候在service层需要用到Locale的时候,再用这种方法就不方便了,因为正常来说service层是没有request的,这时可能就需要在controller层将Locale拿出来,然后再传进去了!当然这也没什么,传一下就好了,但最重要的是怎么传呢?服务层的代码可能已经通过测试了,如果想将Locale传进去可能就需要改接口,而修改接口可能会引起很多问题!而有了LocaleContextHolder就方便多了,只需要在server层直接调用一下LocaleContextHolder.getLocale()就可以了,它是静态方法,可以直接调用!当然,在Spring MVC中Locale的值并不总是request.getLocale()获取到的值,而是采用了非常灵活的机制,在后面的LocaleResolver中再详细讲解。
  RequestContextHolder也是一样的道理,里面封装了RequestAttributes,可以get/set/removeAttribute,而且因为实际封装的是ServletRequestAttributes,所以还可以getRequest、getResponse、getSession!这样就可以在任何地方都能方便地获取这些对象了!另外,因为里面封装的其实是对象的引用,所以即使在doService方法里面设置的Attribute,使用RequestContextHolder也一样可以获取到。
  在方法最后的finally中调用resetContextHolders方法将原来的LocaleContext和Request-Attributes又恢复了。这是因为在Sevlet外面可能还有别的操作,如Filter(Spring-MVC自己的HandlerInterceptor是在doService内部的)等,为了不影响那些操作,所以需要进行恢复。
最后就是publishRequestHandledEvent(request,response,startTime,failureCause)发布消息了。在publishRequestHandledEvent内部发布了一个

ServletRequestHandledEvent消息,代码如下:
// org.springframework.web.servlet.FrameworkServlet
private void publishRequestHandledEvent(
HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause) {
// publishEvents可以在配置Servlet时设置,默认为true
if (this.publishEvents) {
// 无论请求是否执行成功都会发布消息
long processingTime = System.currentTimeMillis() - startTime;
int statusCode = (responseGetStatusAvailable ? response.getStatus() : -1);
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause, statusCode));
}
}

 

  当publishEvents设置为true时,请求处理结束后就会发出这个消息,无论请求处理成功与否都会发布。publishEvents可以在web.xml文件中配置Spring MVC的Servlet时配置,默认为true。我们可以通过监听这个事件来做一些事情,如记录日志。
下面就写一个记录日志的监听器。

@Component
public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent> {
final static Logger logger = LoggerFactory.getLogger("RequestProcessLog");
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
logger.info(event.getDescription());
}
}

  我们可以看到,只要简单地继承ApplicationListener,并且把自己要做的事情写到onApplicationEvent里面就行了。很简单吧!当然要把它注册到spring容器里才能起作用,如果开启了注释,只要在类上面标注@Component就可以了。
  首先是在service方法里添加了对PATCH的处理,并将所有需要自己处理的请求都集中到了processRequest方法进行统一处理,这和HttpServlet里面根据request的类型将请求分配到各个不同的方法进行处理的过程正好相反。然后就是processRequest方法,在processRequest里面主要的处理逻辑交给了doService,这是一个模板方法,在子类具体实现,另外就是对使用当前request获取到的LocaleContext和RequestAttributes进行了保存,以及处理完之后的恢复,在最后发布了ServletRequestandledEvent事件。

  DispatcherServlet在004中说明......

我们今天的关于如何使用Spring MVC处理HTTP选项?spring mvc处理流程的分享已经告一段落,感谢您的关注,如果您想了解更多关于(5)SpringMVC-http请求处理路线、HTTP请求处理流程-SpringMvc、java – 如何使用Spring MVC和Spring Security为资源处理程序启用HTTP缓存、Java框架-Spring MVC理解004-spring MVC处理请求的相关信息,请在本站查询。

本文标签: