当前位置: 首页 > news >正文

来个网站/国际免费b站

来个网站,国际免费b站,定制开发 商城网站 最快,做印尼电商独立站的网站文章目录一、doDispatch1.1 getHandler1.1.1 根据请求对象拿到HandlerMethod1.1.2 getHandlerExecutionChaingetHandler总结1.2 getHandlerAdapter1.3 执行prehandle1.4 调用Controller业务方法1.4.1 调用方法和返回值处理1.4.1.1 调用方法:获取参数数组&#xff1a…

文章目录

    • 一、doDispatch
      • 1.1 getHandler
        • 1.1.1 根据请求对象拿到HandlerMethod
        • 1.1.2 getHandlerExecutionChain
      • getHandler总结
      • 1.2 getHandlerAdapter
      • 1.3 执行prehandle
      • 1.4 调用Controller业务方法
        • 1.4.1 调用方法和返回值处理
          • 1.4.1.1 调用方法:
            • 获取参数数组:
            • 解析参数值resolveArgument
          • 1.4.1.2 处理返回值
      • 1.5 中置过滤器
      • 1.6 视图渲染
      • 1.7 后置拦截器
    • 二、SpringMVC中的异常处理
    • 三、SpringMVC流程总结
    • 四、跨域问题的处理
      • 4.1 拿到跨域配置信息
      • 4.2 配置跨域拦截器

前文SpringMVC纯注解配置+原理详解【请求流程预备点】
讲了请求调用的预备知识,关于监听器和DispatcherServlet启动时做的事情,这里直接步入主题,总结在最后。

请求发出后,会进入HttpServlet中的service方法,在这里会根据请求类型调用doGetdoPost等:

FrameworkServlet#doGet:

protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}

最终会调到DispatcherServlet中的doDispatch方法,这里就是调用的核心流程:

一、doDispatch

这里只关注核心的步骤,有代码xx注释标识的部分

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);//代码1:重点看这里// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}//代码2:获取跟HandlerMethod匹配的HandlerAdapter对象// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}//代码:3:前置过滤器,如果为false则直接返回if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//代码4:调用Controller具体方法// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);//代码5:中置过滤器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);}//代码6 视图渲染、异常处理等processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {//代码7:后置过滤器triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

1.1 getHandler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//handlerMappering实例if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {//获取HandlerMethod和过滤器链的包装类HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}

这里handleMappings从哪来的呢?在前文中提到,在启动DispatcherServlet时,会调用onRefresh方法初始化一些组件,handlerMappings也就在这里进行初始化:

protected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}

回到前面,接着遍历handlerMappings调用其getHandler方法:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//代码1:根据请求的uri拿到对应的HandlerMethod对象Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}//代码2:获取HandlerMethod和拦截器链的包装类HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);//查看request请求头中是否有Origin属性if (CorsUtils.isCorsRequest(request)) {CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}

1.1.1 根据请求对象拿到HandlerMethod

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {//1.从request对象中获取uri,/user/listString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);this.mappingRegistry.acquireReadLock();try {//2.根据uri从映射关系中找到对应的HandlerMethod对象【这里会根据封装注解的requestingMappingInfo对象中的信息与请求过来的信息进行匹配,也就是RequestingMapping中定义了很多属性,params、headers等等,判断请求和这个方法需要的这些属性匹不匹配,如果都匹配,name就找到了对应的方法】HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);//如果HandlerMethod中的bean是字符串beanName,则通过beanFactory对bean也就是当前Controller类进行实例化,重新放到HandlerMethod中的bean属性中return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);}finally {this.mappingRegistry.releaseReadLock();}}

到这里我们就拿到了具体的处理方法HandlerMethod.

1.1.2 getHandlerExecutionChain

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {//1.把HandlerMethod对象包装到HandlerExecutionChain中,这个对象中有拦截器对象HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));//获取uriString lookupPath = this.urlPathHelper.getLookupPathForRequest(request);//遍历拦截器拦截for (HandlerInterceptor interceptor : this.adaptedInterceptors) {if (interceptor instanceof MappedInterceptor) {MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if 
//如果请求的url和拦截器拦截的路径匹配(mappedInterceptor.matches(lookupPath, this.pathMatcher)) {//就将拦截器添加到执行链中的拦截器集合中chain.addInterceptor(mappedInterceptor.getInterceptor());}}else {chain.addInterceptor(interceptor);}}return chain;}

【了解】这里我们会拿到已经加载好的拦截器,那么这些拦截器在哪里添加的呢?同样,在前面的文章中讲到,在容器启动时,会扫描@EnableMVC注解,其中,引入了一个配置类DelegatingWebMvcConfiguration,在其父类的requestMappingHandlerMapping方法中,就会调用到我们在配置中添加的拦截器,然后添加到interceptors中,最终会被添加到adaptedInterceptors

这里整个getHadler就执行完毕了,获得了handlerMethod和拦截器的包装类HandlerExecutionChain

总结下getHandler做了哪些事,很简单:

getHandler总结

  1. 根据请求的url拿到映射的handlerMethod对象,这里会对各个RequestMappingInfo中的属性和请求中的信息进行匹配,全部属性都匹配的话,对应的handlerMethod就是处理该url的方法对象
  2. 将handlerMethod放到HandlerExecutionChain中,然后遍历启动时已经注册好的拦截器,遍历拦截器,判断当前请求的url是否被其拦截,如果是,则将对应的拦截器添加到HandlerExecutionChain中的拦截器数组中
  3. 返回HandlerExecutionChain

1.2 getHandlerAdapter

接着走getHandlerAdapter,会根据找到的handlerMethod找到合适的handlerAdapter:

这里就是遍历所有的HandlerAdapter,判断当前的handler是否适配当前的adapter,如果找到了某个adapter适配当前的handler,则返回该adapter,如果找不到则抛出异常

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {//基于策略模式,根据handlerMethod对象,找到合适的HandlerAdapter对象if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}}@Overridepublic final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));}

1.3 执行prehandle

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {//遍历拦截器for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];//执行prehandleif (!interceptor.preHandle(request, response, this.handler)) {//如果prehandle返回false,则执行afterCompletion方法triggerAfterCompletion(request, response, null);return false;}//每调用一个拦截器,更新调用的下标this.interceptorIndex = i;}}return true;}

如我们自定义的拦截器:

@Component
public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion");}}

如果prehandle返回false,就会直接在doDispatch中返回,不会调用后面的。

另外这里使用interceptorIndex记录调用的拦截器数组的下标,因此如果某个前置拦截器返回false,这个索引就不会更新,而这个索引之前的就一定是调用完毕的。这样在调用后置过滤器时,就可以从interceptorIndex开始向前调用,保证可以执行所有调用过prehandle的拦截器,进行释放资源。

我们可以在前置拦截器做权限校验,如果没通过就返回false

1.4 调用Controller业务方法

调用方法,说白了就是反射调用,但前提是需要方法的各种参数。Controller方法中,我们常常会用@PathVariable@RequestParam等注解。因此,这是我们重点去了解对方法参数的解析。

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);}

最终会到下面调用Controller中的处理该请求的方法:

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {//获取数据绑定工厂WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);//Model工厂ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);//可调用的方法对象ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {//设置参数解析器invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {//设置返回值解析器invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}//设置参数绑定工厂invocableMethod.setDataBinderFactory(binderFactory);//设置参数名称解析类invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);//这里new了一个ModelAndViewContainer容器ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));//调用有@ModelAttribute注解的方法modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);//异步处理WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.setTaskExecutor(this.taskExecutor);asyncManager.setAsyncWebRequest(asyncWebRequest);asyncManager.registerCallableInterceptors(this.callableInterceptors);asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);if (asyncManager.hasConcurrentResult()) {Object result = asyncManager.getConcurrentResult();mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];asyncManager.clearConcurrentResult();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}//Controller方法调用invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}}

1.4.1 调用方法和返回值处理

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//方法调用逻辑Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {//处理返回值this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}...throw ex;}}
1.4.1.1 调用方法:

OK到这里就找到解析参数和调用方法的位置了

public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {//1.获取参数数组Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}//2.根据参数反射调用return doInvoke(args);}
获取参数数组:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {if (ObjectUtils.isEmpty(getMethodParameters())) {return EMPTY_ARGS;}//将入参封装到MethodParameter,封装了参数类型,参数名称,参数注解等等信息MethodParameter[] parameters = getMethodParameters();Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {//拿到具体的参数MethodParameter parameter = parameters[i];//设置参数名称解析器parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}//使用策略模式,根据参数判断有没有对应参数的处理类,找不到就跑异常。if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {//解析参数值,有26种参数解析类型args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}}//将解析到的参数数组返回return args;}
解析参数值resolveArgument

调用刚刚找到的参数处理类解析当前参数:

	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {//根据当前参数获取对应参数的解析类HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {...抛异常....}//策略模式去调用具体参数解析类return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}

这里有许多参数类型解析器,以@Pathraiable注解的解析器为例:

getArgumentResolver中会调用到PathVariableMethodArgumentResolversupportsParameter判断是否支持当前注解:

这里就拿到当前参数有没有PathVariable注解,没有就返回false。

@Overridepublic boolean supportsParameter(MethodParameter parameter) {if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;}if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);return (pathVariable != null && StringUtils.hasText(pathVariable.value()));}return true;}

接着调用该解析器的resolveArgument进行解析:

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {//拿到@PathVariable注解属性的封装类,内部有name,required和defaultValue三个属性NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);MethodParameter nestedParameter = parameter.nestedIfOptional();//拿到占位符的name/value值Object resolvedName = resolveStringValue(namedValueInfo.name);if (resolvedName == null) {throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");}//根据占位符名拿到请求的uri中对应的具体的值Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);if (arg == null) {//arg为空,如果指定默认值就使用默认值if (namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}//没有指定默认值,且是必须的,就抛出异常else if (namedValueInfo.required && !nestedParameter.isOptional()) {handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);}//处理默认值为nullarg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());}else if ("".equals(arg) && namedValueInfo.defaultValue != null) {arg = resolveStringValue(namedValueInfo.defaultValue);}if (binderFactory != null) {WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}catch (ConversionNotSupportedException ex) {throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}catch (TypeMismatchException ex) {throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),namedValueInfo.name, parameter, ex.getCause());}}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}

这里就是拿到@PathVariable注解属性的封装类,然后根据其value值从request请求中拿到uri中的对应的参数值,如果为空,判断是否有默认值,如果有,则使用默认值,如果没有且是必须的则抛异常,最后将解析到的参数返回。解析完毕后就拿到了参数数组。

拿到参数数组后,就可以调用doInvoke反射调用,

内部调用如下:

return getBridgedMethod()//拿到方法
.invoke(getBean(), args);

这里总体流程很简单,就是拿到处理请求的方法,然后解析方法的参数【使用各种参数解析器解析参数信息,包括使用了注解的参数】,拿到参数然后反射调用即可。

参数调用后,就会拿到返回值,如果我们加@ResponseBody注解就可以拿到json数据,那么在调用完方法后,一定会对返回的数据进行处理。

回到1.4.1,开始对返回值做处理

1.4.1.2 处理返回值
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//根据策略模式找到当前返回值类型的处理类HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}//处理返回值handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}

这里和解析方法参数一样,根据策略模式,基于当前返回值类型找到对应的处理类,然后调用处理类的handleReturnValue处理返回值。

处理类有15种左右,这里以@ResponseBody为例:

@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {//设置ModelAndViewContainer的RequestHandled为true,最后需用到该字段mavContainer.setRequestHandled(true);//从请求中拿到输入流ServletServerHttpRequest inputMessage = createInputMessage(webRequest);//再拿到输出流ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.//只用消息转换器写回数据writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}

而如果是返回一个视图呢?

在其对应的解析器ViewNameMethodReturnValueHandler中可以看到,将返回值,也就是返回的视图名添加到了ModelAndViewContainer中,而该对象是在调用Controller中方法的一开始的地方创建的。

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();//设置视图名到mavContainermavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}}else if (returnValue != null){// should not happenthrow new UnsupportedOperationException("Unexpected return type: " +returnType.getParameterType().getName() + " in method: " + returnType.getMethod());}}

回到1.4,在对方法调用和返回值都处理完毕后,最后会return一个getModelAndView

return getModelAndView(mavContainer, modelFactory, webRequest);
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {modelFactory.updateModel(webRequest, mavContainer);//1.判断是否需要处理,前面讲ResponseBody时,将该字段置为了true,其响应的是json,而不用响应视图,因此直接返回null//而如果需要返回视图,没有对该字段做任何处理,由于该字段默认false,因此会走到下面处理if (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();//构造ModelAndView,封装了视图名,ModelMap以及HttpStatusModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());if (!mavContainer.isViewReference()) {mav.setView((View) mavContainer.getView());}if (model instanceof RedirectAttributes) {Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);if (request != null) {RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}}return mav;}

这里就会根据是否需要返回视图进行处理,如果不需要,则返回null,如果需要,则构造一个ModelAndView将视图名等信息封装进去返回。

到这里整个调用Controller中业务方法的流程就结束了。返回了ModelAndView对象,当前如果是ResponseBody注解的方法这个ModelAndView就会是空的,回到doDispatch方法,后面就开始调用中置过滤器。

1.5 中置过滤器

这里将ModelAndView作为入参传了进来,因此我们可以在中置过滤器中对视图名做一些修改,虽然也没啥用。

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}

同前置一样,但是这里是根据过滤器数组倒叙调用的。

最后就是渲染视图的了。

1.6 视图渲染

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {//处理异常if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {//看这里render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}..................}

调用render渲染:

	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {// We need to resolve the view name.//通过策略模式拿到具体的Viewview = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() +"' in servlet with name '" + getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}//重点在这里view.render(mv.getModelInternal(), request, response);}...}

这部分主要根据视图名,基于策略模式拿到对应的View对象,然后通过View对象的render方法进行渲染:

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,HttpServletResponse response) throws Exception {Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);//编码处理prepareResponse(request, response);//主要看这里renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);}

renderMergedOutputModel会调到InternalResourceView的该方法中:

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {//遍历model将响应的数据放到request中exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// 拿到要跳转的路径String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());rd.include(request, response);}else {//跳转rd.forward(request, response);}}

大体流程就是:

  1. 拿到视图名
  2. 找到处理该视图的View对象
  3. 调用View对象的render方法渲染
  4. 根据跳转路径进行forward跳转

1.7 后置拦截器

这里就是执行实现的各个后置拦截器,做一些收尾工作,如释放资源等。

二、SpringMVC中的异常处理

@ControllerAdvice
public class CommonExceptionHandler {@ExceptionHandler(MyException.class)@ResponseBodypublic ResponseEntity<ExceptionResult> handleException(MyException e){ExceptionEnum em = e.getExceptionEnum();System.out.println(em.getMessage());return  ResponseEntity.status(em.getCode()).body(new ExceptionResult(em));}@ExceptionHandler(AllException.class)@ResponseBodypublic Map<String,Object> handleNovalueException(AllException e){Map<String,Object>map=new HashMap<>();ExceptionEnum em = e.getExceptionEnum();map.put("flag",false);map.put("code",em.getCode());map.put("message",em.getMessage());e.printStackTrace();return  map;}
}

当出现异常时,就会在视图渲染时进行异常处理。

然后就会找到有ControllerAdvice注解的类,按照Spring的一贯作风,肯定会将有ExceptionHandler注解的进行封装,然后就会根据异常类型与ExceptionHandler中指定的异常类型进行匹配,拿到匹配的对应的方法,但中间会先将这个处理类进行实例化,接着就和处理Controller中方法一样,解析对应方法的参数,然后反射调用对应方法。

三、SpringMVC流程总结

  1. 请求首先到达doService,再进入DispatchServletdoDispatch方法

  2. 通过HandlerMapping获取HandlerExecutionChain对象,其中会根据request中的uri从映射关系中找到对应的HandlerMethod,将其和拦截该方法的拦截器一并到HandlerExecutionChain中返回。

  3. 根据HandlerMethod找到对应的适配器HandlerAdapter【HandlerAdapter是适配器模式,遍历所有的适配器判断是否匹配是基于策略模式】

  4. 正向执行拦截器数组的preHandle方法。

  5. 通过适配器调用Controller方法

    • 这里会先解析参数,根据不同的参数类型(不同的注解)调用不同的参数解析器解析获得参数数组
    • 通过反射调用该方法
    • 根据返回值类型调用不同的返回值处理器处理
    • 消息转换器转换一些请求的数据
    • 最终会返回一个ModelAndView对象
  6. 方法执行完毕后拿到的ModelAndView对象,如果我们加了@ResponseBody注解,则该对象为空,在视图解析中就什么都不做。

  7. 逆序执行拦截器的中置过滤器

  8. 解析视图。

    • 如果有异常,则调用异常处理器处理异常【找到ControllerAdvice注解的类中处理方法】

    • 通过视图解析器解析ModelAndView拿到对应的View视图对象,通过视图对象完成转发重定向等操作。

  9. 执行后置拦截器

  10. 返回渲染结果

四、跨域问题的处理

解决:

  1. 在配置类中配置跨域

    @Configuration
    @EnableWebMvc
    public class AppConfig extends WebMvcConfigurationSupport {.........//跨域配置@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/user/**").allowedOrigins("*").allowCredentials(true).allowedMethods("GET", "POST", "DELETE", "PUT","PATCH").maxAge(3600);}//还有其他的一些配置,如转换器等
    }
    
  2. 在方法中使用注解配置

        @CrossOrigin(origins = "*",allowedHeaders = "x-requested-with",allowCredentials = "true",maxAge = 3600,methods = {RequestMethod.GET,RequestMethod.POST,RequestMethod.OPTIONS,RequestMethod.DELETE})@RequestMapping("/list")@ResponseBody public List<User> getUserList() {return userService.list();}
    
  3. 类中开启

    @CrossOrigin
    public class UserController {}
    

相比下,在方法中配置粒度更小些。

接下来看看在MVC流程中是如何处理跨域的:

跨域问题其实是在1.1getHandler中处理的,那里只是注释提了一下,回过头再来看看:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {//代码1:根据请求的uri拿到对应的HandlerMethod对象Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}//代码2:获取HandlerMethod和拦截器链的包装类HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);//代码3:查看request请求头中是否有Origin属性if (CorsUtils.isCorsRequest(request)) {//3.1 拿到自定义的跨域配置CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);//3.2 拿到注解配置的跨域配置CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}

在代码3中就是处理跨域问题,首先判断请求头中是否有Origin属性,如果有,那么就存在跨域问题。

4.1 拿到跨域配置信息

在if中,首先会执行CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);这里拿到我们在配置类中定义的的跨域配置。这部分的配置加载也是在容器启动时进行的,和注册处拦截器的地方是一起的。

接着会拿到注解配置的跨域配置信息,主要看看这里做了什么。

protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;}else {//看这里CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);}}return corsConfig;}

该方法中,就根据handlerMethod,从corsLookup中拿到了跨域配置的对象信息。

public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();return this.corsLookup.get(original != null ? original : handlerMethod);}

也就是说handler和跨域配置对象的映射在这执行已经注册好了,其映射的建立是在容器启动的时候进行的,前文已经讲过,可以去看一下,其中就有这个操作:

				//将CrossOrigin注解属性封装成CorsConfigurationCorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {//建立method和跨域配置对象的映射关系this.corsLookup.put(handlerMethod, corsConfig);}

将方法和类上的跨域注解信息封装到CorsConfiguration中,再将handlerMethod和CorsConfiguration对象放到了一个map中:corsLookup,因此上面就可以直接拿到。

拿到后,再将注解的配置信息和自定义配置的跨域信息合并,最后调用了个getCorsHandlerExecutionChain,看看这个是干啥的:

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, @Nullable CorsConfiguration config) {if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);}else {//将跨域配置对象封装成一个跨域拦截器添加到了chain中chain.addInterceptor(new CorsInterceptor(config));}return chain;}

HandlerExecutionChain中前面说了,里面就封装了所有的拦截器数组,这些拦截器会在后面preHandle、postHandle的调用中被执行。

4.2 配置跨域拦截器

该拦截器类中就继承了HandlerInterceptorAdapter,因此有一个preHnadle方法,该方法就会在1.3中调用。

@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return corsProcessor.processRequest(this.config, request, response);}

调用该方法:

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,HttpServletResponse response) throws IOException {if (!CorsUtils.isCorsRequest(request)) {return true;}ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);if (responseHasCors(serverResponse)) {logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");return true;}ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);if (WebUtils.isSameOrigin(serverRequest)) {logger.trace("Skip: request is from same origin");return true;}boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);if (config == null) {if (preFlightRequest) {rejectRequest(serverResponse);return false;}else {return true;}}return handleInternal(serverRequest, serverResponse, config, preFlightRequest);}

直接看最后:

protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,CorsConfiguration config, boolean preFlightRequest) throws IOException {String requestOrigin = request.getHeaders().getOrigin();String allowOrigin = checkOrigin(config, requestOrigin);//从response中拿到HttpHeaders对象HttpHeaders responseHeaders = response.getHeaders();responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));if (allowOrigin == null) {logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");rejectRequest(response);return false;}HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);List<HttpMethod> allowMethods = checkMethods(config, requestMethod);if (allowMethods == null) {logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");rejectRequest(response);return false;}List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);List<String> allowHeaders = checkHeaders(config, requestHeaders);if (preFlightRequest && allowHeaders == null) {logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");rejectRequest(response);return false;}//向HttpHeaders中设置跨域信息responseHeaders.setAccessControlAllowOrigin(allowOrigin);if (preFlightRequest) {responseHeaders.setAccessControlAllowMethods(allowMethods);}if (preFlightRequest && !allowHeaders.isEmpty()) {responseHeaders.setAccessControlAllowHeaders(allowHeaders);}if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());}if (Boolean.TRUE.equals(config.getAllowCredentials())) {responseHeaders.setAccessControlAllowCredentials(true);}if (preFlightRequest && config.getMaxAge() != null) {responseHeaders.setAccessControlMaxAge(config.getMaxAge());}//刷新responseresponse.flush();return true;}

这部分就是从response对象中拿到HttpHeaders对象,然后将我们设置的跨域配置信息设置到HttpHeaders中,

内部就调用set方法放到headers中

public void setAccessControlAllowHeaders(List<String> allowedHeaders) {set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders));}

key就是那些跨域的配置信息:

在这里插入图片描述

http://www.jmfq.cn/news/4926745.html

相关文章:

  • 购物网站模板代码/企业网站的推广方法有哪些
  • 关于做网站的策划书/线上销售平台有哪些
  • 南京学校网站建设策划/旺道seo软件
  • 服务器可以放几个网站/搜索引擎在线观看
  • 怎么做本地婚姻介绍网站/山西网络营销seo
  • 网页设计图片的代码/seo中文
  • 刷赞网站建设/免费推广软件下载
  • minecraft做图网站/php开源建站系统
  • 优秀高端网站建设公司/网站推广优化公司
  • 广告网站设计怎么样/100大看免费行情的软件
  • 网站备案系统验证码出错的解决方案/网站设计开发网站
  • 域名自助服务平台/小璇seo优化网站
  • 宿迁做网站 宿迁网站建设/中国互联网公司排名
  • 网络公司发生网站建设费分录/湖南关键词优化快速
  • 织梦m网站伪静态/北京网站建设公司案例
  • 中国住建网查询证书/郑州seo
  • 电商网站开发设计方案/整站seo服务
  • wordpress建站 ftp/品牌词优化
  • 湖北省最新疫情情况/优化网站页面
  • 网站开发过程记录/nba最新消息
  • 如何免费建网站/关键词排名优化顾问
  • 社区网站制作/今天最新新闻摘抄
  • 网站设计酷站/百度关键词怎么做
  • asp.net网站开发上/万网域名注册
  • 同一网站相同form id/seo主要做什么
  • 网站建设gzzhixun/国内可访问的海外网站和应用
  • 郑州做网站推广运营商/网络营销推广方式包括
  • 做网站的公司怎么找客户/友情链接软件
  • 苏州现在能正常出入吗/重庆网站优化软件
  • 四川销售应用app/站长工具 seo查询