文章目录
- 1. SpringMVC自动配置概览
- 2. 简单功能分析
- 2.1 静态资源访问
- 2.1.1 静态资源目录
- 2.1.2 静态资源访问前缀
- 2.1.3 webjar
- 2.2 欢迎页支持
- 2.3 自定义 Favicon
- 2.4 静态资源配置原理
- 2.4.1 配置类只有一个有参构造器
- 2.4.2 资源处理的默认规则
- 2.4.3 欢迎页的处理规则
- 2.4.4 favicon
- 3. 请求参数处理
- 3.1 请求映射
- 3.1.1 rest使用与原理
- 3.1.2 请求映射原理
- 3.2 普通参数与基本注解
- 3.2.1 注解
- 3.2.2 Servlet API
- 3.2.3 复杂参数
- 2.2.4 自定义对象参数
- 3.3 POJO封装过程
- 3.4 参数处理原理
- 3.4.1 HandlerAdapter
- 3.4.2 执行目标方法
- 3.4.3 参数解析器-HandlerMethodArgumentResolver
- 3.4.4 返回值处理器
- 3.4.5 如何确定目标方法每一个参数的值
- 3.4.6 目标方法执行完成
- 3.4.7 处理派发结果
- 4. 数据响应与内容协商
- 4.1 响应JSON
- 4.1.1 jackson.jar+@ResponseBody
- 4.1.2 SpringMVC到底支持哪些返回值
- 4.1.3 HTTPMessageConverter原理
- 4.2 内容协商
- 4.2.1 引入xml依赖
- 4.2.2 postman分别测试返回json和xml
- 4.2.3 开启浏览器参数方式内容协商功能
- 4.2.4 内容协商原理
- 4.2.5 自定义 MessageConverter
- 5. 视图解析与模板引擎
- 5.1 视图解析
- 5.1.1 视图解析原理流程
- 5.2 模板引擎-Thymeleaf(略)
- 5.3 thymeleaf使用(略)
- 5.4 构建后台管理系统(略)
- 6. 拦截器
- 6.1 HandlerInterceptor
- 6.2 配置拦截器
- 6.3 拦截器原理
- 7. 文件上传
- 7.1 页面表单
- 7.2 文件上传代码
- 7.3 自动配置原理
- 8. 异常处理
- 8.1 错误处理
- 8.1.1 默认规则
- 8.1.2 定制错误处理逻辑
- 8.1.3 异常处理自动配置原理
- 8.1.4 异常处理步骤流程
- 9. Web原生组件注入(Servlet、Filter、Listener)
- 9.1 使用Servlet API
- 9.2. 使用RegistrationBean
- 10. 嵌入式Servlet容器
- 10.1 切换嵌入式Servlet容器
- 10.2 定制Servlet容器
- 11. 定制化原理
- 11.1 定制化的常见方式
- 11.2 原理分析套路
- 11.2 原理分析套路
1. SpringMVC自动配置概览
2. 简单功能分析
2.1 静态资源访问
2.1.1 静态资源目录
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**。
请求进来,先去找Controller看能不能处理。
不能处理的所有请求又都交给静态资源处理器。
静态资源也找不到则响应404页面
改变默认的静态资源路径
spring:mvc:static-path-pattern: /res/**resources:static-locations: [classpath:/haha/]
2.1.2 静态资源访问前缀
默认无前缀
spring:mvc:static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
2.1.3 webjar
自动映射 /webjars/**
https://www.webjars.org/
<dependency><groupId>org.webjars</groupId><artifactId>jquery</artifactId><version>3.5.1</version></dependency>
访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径
2.2 欢迎页支持
- 静态资源路径下 index.html
- 可以配置静态资源路径
- 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效resources:static-locations: [classpath:/haha/]
2.3 自定义 Favicon
favicon.ico 放在静态资源目录下即可。
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
2.4 静态资源配置原理
- SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
- SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@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 {}
- 给容器中配了什么。
@Configuration(proxyBeanMethods = false)@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })@Order(0)public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
- 配置文件的相关属性和xxx进行了绑定。WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources
2.4.1 配置类只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....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;}
2.4.2 资源处理的默认规则
@Overridepublic 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();//webjars的规则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));}}
spring:
# mvc:
# static-path-pattern: /res/**resources:add-mappings: false 禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };/*** Locations of static resources. Defaults to classpath:[/META-INF/resources/,* /resources/, /static/, /public/].*/private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
2.4.3 欢迎页的处理规则
HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。 @Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());return welcomePageHandlerMapping;}WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {//要用欢迎页功能,必须是/**logger.info("Adding welcome page: " + welcomePage.get());setRootViewName("forward:index.html");}else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {// 调用Controller /indexlogger.info("Adding welcome page template: index");setRootViewName("index");}}
2.4.4 favicon
3. 请求参数处理
3.1 请求映射
3.1.1 rest使用与原理
@RequestMapping(value = "/user",method = RequestMethod.GET)public String getUser(){return "GET-张三";}@RequestMapping(value = "/user",method = RequestMethod.POST)public String saveUser(){return "POST-张三";}@RequestMapping(value = "/user",method = RequestMethod.PUT)public String putUser(){return "PUT-张三";}@RequestMapping(value = "/user",method = RequestMethod.DELETE)public String deleteUser(){return "DELETE-张三";}@Bean@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}//自定义filter@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();methodFilter.setMethodParam("_m");return methodFilter;}
spring:mvc:hiddenmethod:filter:enabled: true #开启页面表单的Rest功能
3.1.2 请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet -> 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 {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 找到当前请求使用哪个Handler(Controller的方法)处理mappedHandler = getHandler(processedRequest);//HandlerMapping:处理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则
所有的请求映射都在HandlerMapping中。
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
3.2 普通参数与基本注解
3.2.1 注解
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@RestController
public class ParameterTestController {// car/2/owner/zhangsan@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("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);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;}//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd//2、SpringBoot默认是禁用了矩阵变量的功能// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。// removeSemicolonContent(移除分号内容)支持矩阵变量的//3、矩阵变量必须有url路径变量才能被解析@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;}// /boss/1;age=20/2;age=10@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;}}
3.2.2 Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
@Overridepublic 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);}
3.2.3 复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();
Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map mavContainer.getModel(); 获取到值的
2.2.4 自定义对象参数
可以自动类型转换与格式化,可以级联封装
/*** 姓名: <input name="userName"/> <br/>* 年龄: <input name="age"/> <br/>* 生日: <input name="birth"/> <br/>* 宠物姓名:<input name="pet.name"/><br/>* 宠物年龄:<input name="pet.age"/>*/
@Data
public class Person {private String userName;private Integer age;private Date birth;private Pet pet;}@Data
public class Pet {private String name;private String age;}result
3.3 POJO封装过程
- ServletModelAttributeMethodProcessor
3.4 参数处理原理
- HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
- 适配器执行目标方法并确定方法参数的每一个值
3.4.1 HandlerAdapter
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
3.4.2 执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3.4.3 参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么; SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
- 当前解析器是否支持解析这种参数
- 支持就调用 resolveArgument
3.4.4 返回值处理器
3.4.5 如何确定目标方法每一个参数的值
============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) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;}
5.1、挨个判断所有参数解析器那个支持解析这个参数
@Nullableprivate 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;}
5.2、解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
5.3、自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持 是否为简单类型。
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));}
@Override@Nullablepublic 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 {// Create attribute instancetry {attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {// No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();}bindingResult = ex.getBindingResult();}}if (bindingResult == null) {// Bean property binding and validation;// skipped in case of binding failure on construction.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());}}// Value type adaptation, also covering java.util.Optionalif (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}// Add resolved attribute and BindingResult at the end of the modelMap<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
@FunctionalInterface
public interface Converter<S, T>
未来我们可以给WebDataBinder里面放自己的Converter; private static final class StringToNumber implements Converter<String, T>
自定义 Converter
//1、WebMvcConfigurer定制化SpringMVC的功能@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();// 不移除;后面的内容。矩阵变量功能就可以生效urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Pet>() {@Overridepublic Pet convert(String source) {// 啊猫,3if(!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;}});}};}
3.4.6 目标方法执行完成
将所有的数据都放在 ModelAndViewContainer
;包含要去的页面地址View。还包含Model数据。
3.4.7 处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// Determine the path for the request dispatcher.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());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}}
暴露模型作为请求域属性
// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有数据遍历挨个放在请求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}
4. 数据响应与内容协商
4.1 响应JSON
4.1.1 jackson.jar+@ResponseBody
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
web场景自动引入了json场景<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency>
给前端自动返回json数据;
1、返回值解析器
try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}
@Overridepublic 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);}
RequestResponseBodyMethodProcessor
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {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);}
2、返回值解析器原理
- 注意:如果使用
@ResponseBody
会直接在响应体中写入数据,不会再封装为一个ModelAndView
对象返回(不会进行视图渲染)。其原理是利用MessageConver
的实现类执行完毕后,会设置ModelAndViewContainer
中的isRequestHandled()
方法判断是否已经处理了请求。
4.1.2 SpringMVC到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
4.1.3 HTTPMessageConverter原理
1、MessageConverter规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。 例子:Person对象转为JSON。或者 JSON转为Person
2、默认的MessageConverter
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
4.2 内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
4.2.1 引入xml依赖
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
4.2.2 postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
4.2.3 开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:contentnegotiation:favor-parameter: true #开启请求参数内容协商模式
发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json即可。
4.2.4 内容协商原理
1、 判断当前响应头中是否已经有确定的媒体类型。MediaType
2、 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
+ contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
+
+ HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
3、 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
5、客户端需要【application/xml】。服务端能力【10种、json、xml】
6、进行内容协商的最佳匹配媒体类型
7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
导入了jackson处理xml的包,xml的converter就会自动进来
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()));}
4.2.5 自定义 MessageConverter
实现多协议数据兼容。json、xml、x-guigu
@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}}}
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】
5. 视图解析与模板引擎
视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
5.1 视图解析
5.1.1 视图解析原理流程
自定义视图解析器+自定义视图; 大厂学院
5.2 模板引擎-Thymeleaf(略)
5.3 thymeleaf使用(略)
5.4 构建后台管理系统(略)
6. 拦截器
6.1 HandlerInterceptor
/*** 登录检查* 1、配置好拦截器要拦截哪些请求* 2、把这些配置放在容器中*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法执行之前* @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String requestURI = request.getRequestURI();log.info("preHandle拦截的请求路径是{}",requestURI);//登录检查逻辑HttpSession session = request.getSession();Object loginUser = session.getAttribute("loginUser");if(loginUser != null){//放行return true;}//拦截住。未登录。跳转到登录页request.setAttribute("msg","请先登录");
// re.sendRedirect("/");request.getRequestDispatcher("/").forward(request,response);return false;}/*** 目标方法执行完成以后* @param request* @param response* @param handler* @param modelAndView* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("postHandle执行{}",modelAndView);}/*** 页面渲染以后* @param request* @param response* @param handler* @param ex* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion执行异常{}",ex);}
}
6.2 配置拦截器
/*** 1、编写一个拦截器实现HandlerInterceptor接口* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**") //所有请求都被拦截包括静态资源.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求}
}
6.3 拦截器原理
- 根据当前请求,找到 HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
- 先来顺序执行 所有拦截器的 preHandle 方法
- 如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
- 如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
- **如果任何一个拦截器返回false。直接跳出不执行目标方法 **
- **所有拦截器都返回True。执行目标方法 **
- **倒序执行所有拦截器的postHandle方法。 **
- 前面的步骤有任何异常都会直接倒序触发 afterCompletion
- 页面成功渲染完成以后,也会倒序触发 afterCompletion
7. 文件上传
7.1 页面表单
<form method="post" action="/upload" enctype="multipart/form-data"><input type="file" name="file"><br><input type="submit" value="提交">
</form>
7.2 文件上传代码
/*** MultipartFile 自动封装上传过来的文件* @param email* @param username* @param headerImg* @param photos* @return*/@PostMapping("/upload")public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos) throws IOException {log.info("上传的信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);if(!headerImg.isEmpty()){//保存到文件服务器,OSS服务器String originalFilename = headerImg.getOriginalFilename();headerImg.transferTo(new File("H:\\cache\\"+originalFilename));}if(photos.length > 0){for (MultipartFile photo : photos) {if(!photo.isEmpty()){String originalFilename = photo.getOriginalFilename();photo.transferTo(new File("H:\\cache\\"+originalFilename));}}}return "main";}
7.3 自动配置原理
- 文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties
- 自动配置好了 StandardServletMultipartResolver 【文件上传解析器
- 原理步骤
- 请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
- 参数解析器来解析请求中的文件内容封装成MultipartFile
- 将request中文件信息封装为一个Map; MultiValueMap<String, MultipartFile> FileCopyUtils。实现文件流的拷贝
@PostMapping("/upload")public String upload(@RequestParam("email") String email,@RequestParam("username") String username,@RequestPart("headerImg") MultipartFile headerImg,@RequestPart("photos") MultipartFile[] photos)
8. 异常处理
8.1 错误处理
8.1.1 默认规则
- 默认情况下,Spring Boot提供/error处理所有错误的映射
- 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
- 要对其进行自定义,添加View解析为error
- 要完全替换默认行为,可以实现
ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。 - error/下的4xx,5xx页面会被自动解析;
8.1.2 定制错误处理逻辑
-
自定义错误页
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
-
@ControllerAdvice+@ExceptionHandler 处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
-
@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
-
Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
- response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
-
自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
-
ErrorViewResolver 实现自定义处理异常;
- response.sendError 。error请求就会转给controller
- 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
- basicErrorController 要去的页面地址是 ErrorViewResolver ;
springMVC 统一异常处理 返回JSON数据
引用自:https://blog.csdn.net/qq_38403662/article/details/91418281
需求
在后台开发中,难免会存在一些异常,如果我们在controller中一个一个的去try catch处理,会很繁琐,并且不好维护;如果在web.xml配置错误页面,会导致返回一个试图给前台,对于前后端分离的不太友好,前台无法解析,这明显不是我们想要的,我们需要的是返回串JSON的错误码给前台;
@ControllerAdvice
从spring3.2开始,增加了新注解@ControllerAdvice,控制器增强的注解。其原理是使用AOP对Controller控制器进行增强(前置增强、后置增强、环绕增强,AOP原理请自行查阅);这样我们就可以自行对控制器的方法进行调用前(前置增强)和调用后(后置增强)的处理。
如果要返回JSON格式,只需要创建一个ExceptionHandler的class 用@ControllerAdvice注解,里面通过@ExceptionHandler来注解了的方法处理数据,处理中将Exception转换为Json格式返回即可。
代码
package com.XXX.XXXimport com.XXXX.common.GenericResponse;
import com.XXXX.common.ServiceError;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;/*** 招生统一异常处理*/
@ControllerAdvice
public class XXXXExceptionHandler {private final Logger logger = LoggerFactory.getLogger(XXXXExceptionHandler.class);/*** 运行时异常* @param runtimeException* @return */@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic GenericResponse runtimeExceptionHandler(RuntimeException runtimeException) {logger.error(runtimeException.getMessage());return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"系统运行异常");}/*** 空指针异常* @param ex* @return*/@ExceptionHandler(NullPointerException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)@ResponseBodypublic GenericResponse nullPointerExceptionHandler(NullPointerException ex) {logger.error(ex.getMessage());return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"系统空指针异常");}/*----- REQUEST ERROR -----*///400错误@ExceptionHandler({HttpMessageNotReadableException.class})@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic GenericResponse requestNotReadable(HttpMessageNotReadableException ex){logger.error(ex.getMessage());return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"参数格式错误(缺少分隔符或结束标签)");}//400错误@ExceptionHandler({TypeMismatchException.class})@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic GenericResponse requestTypeMismatch(TypeMismatchException ex){logger.error(ex.getMessage());return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"参数类型不匹配");}//400错误@ExceptionHandler({MissingServletRequestParameterException.class})@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic GenericResponse requestMissingServletRequest(MissingServletRequestParameterException ex){logger.error(ex.getMessage());return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"缺少请求参数");}//405错误@ExceptionHandler({HttpRequestMethodNotSupportedException.class})@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)@ResponseBodypublic GenericResponse request405(){return GenericResponse.response(ServiceError.UN_KNOW_ERROR,"不支持该请求方式");}}
说明
- 需要注意的是该类一定要存放到能够被spring扫描到的地方
- 其中GenericResponse 及 ServiceError是自定义的响应结果,可修改为自己定义的(因为各自的项目的响应结果都不一样,这边就不贴出来了)
- @ExceptionHandler表示需要处理的异常类型
- @ResponseStatus表示修改返回的http状态
- @ResponseBody表示返回JSON、因为GenericResponse为一个实体对象,所以最终将对象
转为JSON再返回给前台
效果图
后台controller抛出异常,无需try catch
8.1.3 异常处理自动配置原理
- ErrorMvcAutoConfiguration 自动配置异常处理规则
- 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
- public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
- DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
- 容器中的组件:类型:BasicErrorController --> id:
basicErrorController(json+白页 适配响应)- 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
- 容器中有组件 View->id是error;(响应默认错误页)
- 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
- 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
- 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
- error/404、5xx.html
如果想要返回页面;就会找error视图 【StaticView】。(默认是一个白页)
8.1.4 异常处理步骤流程
-
执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
-
进入视图解析流程(页面渲染?) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
-
mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
-
遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
-
系统默认的 异常解析器
-
-
DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null;
-
默认没有任何系统默认的异常解析器能处理异常,所以异常会被抛出
-
如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理。
-
解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
-
默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
-
模板引擎最终响应这个页面 error/500.html
-
9. Web原生组件注入(Servlet、Filter、Listener)
9.1 使用Servlet API
@ServletComponentScan(basePackages = “com.atguigu.admin”) :指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring的拦截器?
@WebFilter(urlPatterns={“/css/“,”/images/”})
@WebListener
推荐可以这种方式;
扩展:DispatchServlet 如何注册进来
+ 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
+ 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。
+ 默认映射的是 / 路径。
Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/
B: /my/1
9.2. 使用RegistrationBean
ServletRegistrationBean
, FilterRegistrationBean
, and ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {@Beanpublic ServletRegistrationBean myServlet(){MyServlet myServlet = new MyServlet();return new ServletRegistrationBean(myServlet,"/my","/my02");}@Beanpublic FilterRegistrationBean myFilter(){MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));return filterRegistrationBean;}@Beanpublic ServletListenerRegistrationBean myListener(){MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();return new ServletListenerRegistrationBean(mySwervletContextListener);}
}
10. 嵌入式Servlet容器
10.1 切换嵌入式Servlet容器
- 默认支持的webServer
Tomcat
,Jetty
,orUndertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
- 切换服务器
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency>
- 原理
- SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
- web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
- ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)
- SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
- 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
- ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
- ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
- TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
- 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
10.2 定制Servlet容器
- 实现 WebServerFactoryCustomizer
- 把配置文件的值和ServletWebServerFactory 进行绑定
- 修改配置文件 server.xxx
- 直接自定义 ConfigurableServletWebServerFactory
xxxxxCustomizer:定制化器,可以改变xxxx的默认规则
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {@Overridepublic void customize(ConfigurableServletWebServerFactory server) {server.setPort(9000);}}
11. 定制化原理
11.1 定制化的常见方式
- 修改配置文件;
- xxxxxCustomizer;
- 编写自定义的配置类 xxxConfiguration;
- @Bean替换、增加容器中默认组件;视图解析器
- Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;
- @Bean给容器中再扩展一些组件
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
- @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
- 原理
- WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
- 一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
- DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
- 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
- public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- @EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
- 原理
- … …
11.2 原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
mponent;
@Component
public class CustomizationBean implements WebServerFactoryCustomizer {
@Override
public void customize(ConfigurableServletWebServerFactory server) {server.setPort(9000);
}
}
# 11. 定制化原理
## 11.1 定制化的常见方式
+ 修改配置文件;
+ xxxxxCustomizer;
+ 编写自定义的配置类 xxxConfiguration;
+ @Bean替换、增加容器中默认组件;视图解析器
+ Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;
+ @Bean给容器中再扩展一些组件
```java
@Configuration
public class AdminWebConfig implements WebMvcConfigurer
- @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能
- 原理
- WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
- 一旦使用 @EnableWebMvc 、。会 @Import(DelegatingWebMvcConfiguration.class)
- DelegatingWebMvcConfiguration 的 作用,只保证SpringMVC最基本的使用
- 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
- 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
- public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
- WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
- @EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。
- 原理
- … …
11.2 原理分析套路
场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项