web开发
关键 spring到处都是这种设计模式,设置多个不同的处理器,然后通过遍历循环找到支持当前类型的处理器,如果存在效率问题,则直接将当前条件对应的处理器缓存下来。这也是spring底层提供的大量扩展点。使用到的地方包括:
映射处理器*MappingHandler(找到不同的controller处理请求)
参数解析器*ParamResolver
返回值处理器*ResultProcessor
返回值消息转换器*MessageConvertor
视图解析器ViewResolver
错误异常处理HandlerExceptionResolvers:异常解析器,捕获并处理异常。
错误视图解析器DefaultErrorViewReslver:如果没有捕获处理异常则自动跳转到错误视图/error
特别疑惑这种通过循环遍历的方式查找对应的处理器的方法,是不是存在效率问题呢?
1 静态资源访问 静态资源访问
默认的路径,类路径下/static /public /resources META_INFO/resources。使用当前项目的根路径/
改变默认的静态路远路径:spring.resources.static-locations=classpath:/haha
默认是没有访问前缀,通过配置添加静态资源访问前缀,spring.mvc.static-path-pattern=/static/**
webjars支持,将前端依赖以jar的形式导入到项目中,在jar包的资源路径下有对应的前端资源,其路径变为/webjars/** 添加依赖中指定的地址。(前后端分离一般不会使用这个方法)
静态资源访问的原理:
静态资源映射的地址是/**
如果同时存在动态资源和静态资源,则首先访问动态资源
欢迎页和图表
欢迎页的默认名称 index.html 放到静态资源路径下,访问根路径会自动跳转
图表的的默认名称 favorite.ico 放到静态资源路径下可以自动加载
自动加载原理
在使用springboot框架进行开发的时候可以使用这个设计模式,实现Bean配置和属性配置。——SpringMvcAutoConfiguration
首先定义一个场景 自动配置列,在某些条件下打开、配置自动加载的顺序
通过EnableConfiguration指定绑定的 属性配置文件
在内部初始化一系列相关的Bean容器,用来支持该场景的操作。
与Web相关的自动配置类WebMVCAutoConfiguration。
定义了加载的条件,WebServlet场景,且没有开启全部接管模式。
定义了配置加载的顺序,在某些Configuration之后进行加载。
1 2 3 4 5 6 7 @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class }) @ConditionalOnMissingBean(WebMvcConfigurationSupport.class) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class }) public class WebMvcAutoConfiguration {
静态内部配置类WebMvcAutoConfigurationAdapter给容器中配置了什么,静态内部类绑定了属性配置文件。加载了两个properties文件。
1 2 3 4 5 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
加载了相关的属性文件。通过静态内部类的方式降低了类的嵌套深度,定义了多个属性。
1 2 3 4 5 @ConfigurationProperties(prefix = "spring.mvc") public class WebMvcProperties {@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) public class ResourceProperties {
内部自动注入创建了该Bean。
resourceProperties spring.resources 相关的配置文件
mvcProperties spring.mvc 相关的属性配置
beanFactory IOC工厂
其中ObjectProvider用于通过编程的方式实现不唯一的类型注入。也可以直接注入一个List
messageConvertersProvider 找到所有的MessageConvertor
resourceHandlerRegistrationCustomizerProvider 用户自定义的资源处理器
dispatcherServletPath 定义servlet的context的路径
servletRegistrations 给应用添加java web原生的servlet、listener、filter
自动装配规则:如果一个SpringBoot配置类只有一个默认的有参构造器,则该构造器的所有参数都会从容器中进行自动装配。相当于添加了@Autowire
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public WebMvcAutoConfigurationAdapter (ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this .resourceProperties = resourceProperties; this .mvcProperties = mvcProperties; this .beanFactory = beanFactory; this .messageConvertersProvider = messageConvertersProvider; this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this .dispatcherServletPath = dispatcherServletPath; this .servletRegistrations = servletRegistrations; }
在该类中创建了Web场景下所需要的各种组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @Override public void configureMessageConverters (List<HttpMessageConverter<?>> converters) {} @Override public void configureAsyncSupport (AsyncSupportConfigurer configurer) {} @Override @SuppressWarnings("deprecation") public void configurePathMatch (PathMatchConfigurer configurer) {} private boolean singleDispatcherServlet () {} @Override @SuppressWarnings("deprecation") public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {} @Bean @ConditionalOnMissingBean public InternalResourceViewResolver defaultViewResolver () {} @Bean @ConditionalOnBean(View.class) @ConditionalOnMissingBean public BeanNameViewResolver beanNameViewResolver () {} @Bean @ConditionalOnBean(ViewResolver.class) @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class) public ContentNegotiatingViewResolver viewResolver (BeanFactory beanFactory) {} @Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver () {} @Override public MessageCodesResolver getMessageCodesResolver () {} @Override public void addFormatters (FormatterRegistry registry) {} @Override public void addResourceHandlers (ResourceHandlerRegistry registry) {} private Integer getSeconds (Duration cachePeriod) {} private void customizeResourceHandlerRegistration (ResourceHandlerRegistration{ } @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @ConditionalOnMissingFilterBean(RequestContextFilter.class) public static RequestContextFilter requestContextFilter() {}
静态资源加载的所有规则。spring所有的加载项,都可以通过这种代码调试的方法,找到对应的开关或配置 。
addmaping 标识是否开启静态资源。
配置/webjars/**,添加了所有classpath:/webjars/路径下的静态资源
配置了/**,添加了所有classpath:/static/ public/下的资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); return ; } Duration cachePeriod = this .resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this .resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**" )) { customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**" ) .addResourceLocations("classpath:/META-INF/resources/webjars/" ) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this .mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern) .addResourceLocations(getResourceLocations(this .resourceProperties.getStaticLocations())) .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)); } }
2 请求映射处理 请求映射过程
@xxxMapping:@RequestMapptin @GetMapping @PostMapping @DeleteMapping @PutMapping
支持rest请求的配置。只有HTML表单需要进行如下操作,如果是xhr则可以直接发送Delete和Pub请求
表单中添加_method隐藏参数,指定POST请求真正的请求方式
开启页面表单Rest支持 spring.mvc.hiddenmethod.filter=true
1 2 3 4 5 6 @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter () { return new OrderedHiddenHttpMethodFilter (); }
请求映射原理
dispatch方法是请求映射的核心类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 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); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 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 ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } 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()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
请求映射的原理 List<Map<uri,Handler>> HandlerMappingList;多个不同的HandlerMapping,按照优先级进行匹配。
系统初始化了五个HandlerMapping其中RequestMappingHandlerMapping扫描Requestmapping注解进行请求映射。WelcomePageHandlerMapping 定义了欢迎页的映射。
如果需要自定义的映射请求处理,可以自定义的HandlerMapping进行扩展。
RequestMappingHandlerMapping,保存了RequestMapping注解保存的uri和方法的映射。mappingRegistry保存了映射信息。
请求处理的过程(类层侧结构) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @startuml package javax/servlet{ interface Servlet{ init() getServletConfig() getServletInfo() destroy() service() } class GenericServlet implements Servlet{ doGet() } note right: 实现了默认的空方法 package http{ class HttpServlet{ doGet doPost doPut doDelete doHead doTrace } note right : http类型的servlet\n\ 将servlet具体位http的post、get等方法 GenericServlet <|-- HttpServlet } } namespace org/springframwork/web/servlet{ class HttpServletBean{ } note right: 配置了环境信息 HttpServlet <|-- HttpServletBean class FrameworkServlet extends HttpServletBean{ doGet--> processRequest doPost --> processRequest doPut --> processRequest doDelete --> processRequest processRequest -->doService } class DispatcherServlet extends FrameworkServlet{ doService -->doDispatcher doDispatcher } } @enduml
3 请求参数处理 注解请求参数
测试springmvc常用的参数注解 @PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 @RestController public class ParameterTestController { @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String name, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam("age") Integer age, @RequestParam("inters") List<String> inters, @RequestParam Map<String,String> params, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie) { Map<String,Object> map = new HashMap <>(); map.put("age" ,age); map.put("inters" ,inters); map.put("params" ,params); map.put("_ga" ,_ga); System.out.println(cookie.getName()+"===>" +cookie.getValue()); return map; } @PostMapping("/save") public Map postMethod (@RequestBody String content) { Map<String,Object> map = new HashMap <>(); map.put("content" ,content); return map; } @GetMapping("/cars/{path}") public Map carsSell (@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand, @PathVariable("path") String path) { Map<String,Object> map = new HashMap <>(); map.put("low" ,low); map.put("brand" ,brand); map.put("path" ,path); return map; } @GetMapping("/boss/{bossId}/{empId}") public Map boss (@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge) { Map<String,Object> map = new HashMap <>(); map.put("bossAge" ,bossAge); map.put("empAge" ,empAge); return map; } }
HandlerMappingAdapter用来对Handler的处理过程进行封装。其中RequestHandlerMapping就是用来封装@RequestMapping的映射的,HandlerFunctionAdapter是用来支持函数映射的。
调用adapter的handler方法进行真正的处理逻辑。最终还是调用RequestMappingHandler的handler方法进行处理
在上述方法中,调用参数解析器进行进行参数解析。可以看到springboot支持的所有的参数解析方法。
同时方法中也可以看到方法支持的返回值处理器。 spring中很多地方都遵循这种设计模式(策略模式),定义一个共同的接口、给接口定义多种不同的实现、建立条件和实现之间的映射关系,从而可以面向不同的场景和进行灵活的扩展。
最终通过反射调用具体的方法。在真正的调用之前首先要调用参数解析方法解析参数的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 --> handler --> invocableMethod.invokeAndHandle(webRequest, mavContainer); --> InvocableHandlerMethod.java:Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); @Nullable public Object invokeForRequest (NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } return doInvoke(args); } protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } 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 { args[i] = this .resolvers.resolveArgument(parameter, mavContainer, request, this .dataBinderFactory); } catch (Exception ex) { if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
解析过程如图所示,遍历所有的参数解析器,找到支持的解析器,为了加快运行病针对这个参数建立缓存。(为什么不在一开始就建立mapping映射呢,而是要在运行中建立映射?一方面为了增加启动速度,避免在启动的时候加载大量无用的数据进来,另一方面有一些映射的建立是动态的过程,需要调用目标模块是否支持的方法,必须在调用后才才能确定映射到具体哪一个解析器。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Nullable private HandlerMethodArgumentResolver getArgumentResolver (MethodParameter parameter) { HandlerMethodArgumentResolver result = this .argumentResolverCache.get(parameter); if (result == null ) { for (HandlerMethodArgumentResolver resolver : this .argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this .argumentResolverCache.put(parameter, result); break ; } } } return result; }
传入ServletAPI
支持的request类型。WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || Principal.class.isAssignableFrom(paramType) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); }
与注解同理,都是通过参数解析器,解析不同的请求。
复杂参数
支持的复杂参数如下:Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder。
其中Map/Model都是可以给request域中放数据,与request.setAttribute()类似。可以通过request.getAttribute()从请求域中获取;
map/model 都是调用相同的方法从底层获取到值的,mavContainer.getModel。而且两者获取到的是同一个对象。
当方法处理完成后,所有的数据都会放单modelAndViewContainer中。当返回值是一个字符串的时候,方法不会直接返回给前端,而是一个view的地址!!!并且携带了数据model,完成后续渲染工作。
自定义对象
通过ServletModelAttributeMethodProcessor参数解析器处理自定义了类型的参数。
设计了大量的转换器,将对类型进行转换。主要是为了封装转换器中的 src 和 dsr方法,封装成一个重载的方法,然后方便获取不同类型的转换器。
当前解析器是否支持解析这种参数
支持就调用 resolveArgument
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 ============InvocableHandlerMethod========================== protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } 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 { args[i] = this .resolvers.resolveArgument(parameter, mavContainer, request, this .dataBinderFactory); } catch (Exception ex) { if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; } @Nullable private HandlerMethodArgumentResolver getArgumentResolver (MethodParameter parameter) { HandlerMethodArgumentResolver result = this .argumentResolverCache.get(parameter); if (result == null ) { for (HandlerMethodArgumentResolver resolver : this .argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this .argumentResolverCache.put(parameter, result); break ; } } } return result; }
1 2 3 4 5 6 7 8 9 10 11 12 13 public static boolean isSimpleValueType (Class<?> type) { return (Void.class != type && void .class != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @Override @Nullable public final Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null , "ModelAttributeMethodProcessor requires ModelAndViewContainer" ); Assert.state(binderFactory != null , "ModelAttributeMethodProcessor requires WebDataBinderFactory" ); String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null ) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null ; BindingResult bindingResult = null ; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { throw ex; } if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null ) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null ) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException (binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
自定义数据转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Pet>() { @Override public Pet convert (String source) { if (!StringUtils.isEmpty(source)){ Pet pet = new Pet (); String[] split = source.split("," ); pet.setName(split[0 ]); pet.setAge(Integer.parseInt(split[1 ])); return pet; } return null ; } }); } }; }
4 响应数据处理 响应数据
引入json开发的前端依赖,就会自动将返回值使用jackson工具处理封装。
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> </dependency>
返回值处理器判断是否支持该返回类型。
上述返回值处理器支持的返回值类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ModelAndView Model View ResponseEntity ResponseBodyEmitter StreamingResponseBody HttpEntity HttpHeaders Callable DeferredResult ListenableFuture CompletionStage WebAsyncTask 有 @ModelAttribute 且为对象类型的 @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
ResponseBodyProcessor对返回值进行处理,转换为JSON
浏览器与服务器协商,服务器根据自身提供的类型能力,选择合适的类型。
SpringMVC查找所有的MessageConvertor,找到能够处理数据的转换器。
1 2 3 4 5 6 7 8 9 10 0 - 只支持Byte类型的1 - String2 - String3 - Resource4 - ResourceRegion5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class6 - MultiValueMap7 - true 8 - true 9 - 支持注解方式xml处理的。
jackson2组件能够吧任何对象转换为json
可以开启基于请求参数的内容协商。
spring.contentnegotiation.favor-param=true
?format=json
自定义数据转换器
需要自定义MessageConvertor。在SpringMVCConfigur中进行spring-mvc的自定义。使用mvc默认的扩展点
1 2 3 4 5 6 7 8 9 10 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { } } }
学习一种根据类自动加载MessageConvertor的方法。java的灵活之处,也是java莫名其妙不知道在哪里给你添加一段代码的烦人的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 WebMvcConfigurationSupport jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper" , classLoader); if (jackson2XmlPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); if (this .applicationContext != null ) { builder.applicationContext(this .applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter (builder.build())); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter ()); }
自定义内容协商器
使用mvc默认的扩展点。
5 视图解析与模板引擎 服务端模板渲染
相关的场景启动器
1 2 3 spring-boot-starter-freemarker spring-boot-starter-groovy-templates spring-boot-starter-thymeleaf
thymeleaf自动配置类.
自动配置好了thymeleaf的模板解析器SpringResourceTemplateResolver
自动配置了spring的模板引擎。SpringTemplateEngine
自动配置了Spring视图解析器ThymeleafViewResolverConfiguration
1 2 3 4 5 @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) public class ThymeleafAutoConfiguration {
thymeleaf 说明
文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,…. 变量不能有空格
字符串拼接: +
变量替换: |The name is ${name}|
运算符: + , - , * , / , %
运算符: and , or一元运算: ! , not
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
If-then: (if) ? (then) If-then-else: (if) ? (then) : (else) Default: (value) ?: (defaultvalue)
表单写法
1 2 3 4 5 6 <form action="subscribe.html" th:attr="action=@{/subscribe}" > <fieldset> <input type="text" name="email" /> <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}" /> </fieldset> </form>
链接写法
1 2 3 <img src="/note_image/images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /> <input type="submit" value="Subscribe!" th:value="#{subscribe.submit}" /> <form action="subscribe.html" th:action="@{/subscribe}" >
循环写法
1 2 3 4 5 <tr th:each="prod : ${prods}" > <td th:text="${prod.name}" >Onions</td> <td th:text="${prod.price}" >2.41 </td> <td th:text="${prod.inStock}? #{true} : #{false}" >yes</td> </tr>
条件写法
1 2 3 4 5 6 7 8 9 <a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if ="${not #lists.isEmpty(prod.comments)}" >view</a> <div th:switch ="${user.role}" > <p th:case ="'admin'" >User is an administrator</p> <p th:case ="#{roles.manager}" >User is a manager</p> <p th:case ="*" >User is some other thing</p> </div>
优先级
视图解析原理 带上 model、request、response调用render方法。
resolverName方法会遍历所有的视图解析器。找到支持的视图解析器。
返回值“redirect:view”会得到RedirectView视图解析器。
返回值“forward:”会在后端进行重定向,而不是前端浏览器。
返回值 result方法。
通过视图解析器能够得到对应的视图对象。
调用视图对象的render方法进行渲染,得到最终结果。
调用sendRedirect方法,servlet的原生的redirect方法。使得前端