03-SpringBoot核心功能之web开发

目录

提前预知

  • 课程笔记来源于雷神的SpringBoot2教程
  • 参考文档地址:参考文档
  • 要学会查看官方文档!!!!!!!!!

01、SpringMVC自动配置概览

  • 参考文档:4.7:Developing Web Applications
  • 参考文档:4.7.1: The Spring Web MVC Framework.Spring MVC Auto-configuration
  • 文档原文:
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (covered later in this
document)).
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • Automatic registration of MessageCodesResolver (covered later in this document).
  • Static index.html support.
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

If you want to keep those Spring Boot MVC customizations and make more MVC customizations
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

If  you  want  to  provide  custom  instances  of  RequestMappingHandlerMapping,
RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot
MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide
custom instances of those components.

If you want to take complete control of Spring MVC, you can add your own @Configuration
annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated
DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc
  • 文档翻译:
SpringBoot为SpringMVC提供了自动配置功能,可以很好地与大多数应用程序配合使用。自动配置在Spring默认设置的基础上添加了以下功能:
 
  •包括内  容协商视图解析器 和 BeanName视图解析器
  •支持提供静态资源,包括对WebJars的支持
  •自动注册 Converter,GenericConverter,Formatter
  •支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  •自动注册 MessageCodesResolver (国际化用)
  •静态index.html 页支持
  •自定义 Favicon
  •自动使用 ConfigurableWebBindingInitializer,(ataBinder负责将请求数据绑定到JavaBean上)

不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则

声明 WebMvcRegistrations 改变默认底层组件

使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

02、简单功能分析

2.1、静态资源访问

2.1.1、静态资源目录
只要静态资源放在类路径下(resources文件夹下),默认就可以被访问到:
	/static 
	/public
	/resources
	/META-INF/resources

访问 :
	当前项目根路径/静态资源名
	例如:localhost:8080/public.jpg
原理: 
	请求进来,先去找Controller看能不能处理,就是看看@RestController中有没有对应的@RequestMapping("/resource")。
	不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面
	
	静态映射/**:双星是默认拦截所有的Controller处理不了的请求。
  • 改变默认的静态资源路径:在配置文件中做如下配置,给静态资源访问加前缀
spring:
  mvc:
    static-path-pattern: /res/**

注意:如果不配置。默认是无前缀的。

配置完之后,访问静态资源必须是加上res前缀

当前项目 + static-path-pattern + 静态资源名

例如:localhost:8080/res/public.jpg

  • 指定静态资源的文件夹为:aismall:其他的默认存放静态资源的文件夹失效
spring:
  mvc:
    static-path-pattern: /res/**
  resources:
    static-locations: classpath:/aismall

当前项目 + static-path-pattern + 静态资源名 ,访问方式不变,就是静态文件夹被指定了

访问路径:localhost:8080/res/aismall.jpg

注意:指定的静态资源文件夹可以为多个,例如:static-locations: [classpath:/aismall01,classpath:/aismall02]

2.1.2、webjar (了解)
- 首先要添加依赖
- 使用webjar自动映射: /webjars/**
- 访问地址:http://localhost:8080/webjars/**jquery/3.5.1/jquery.js**  后面地址要按照依赖里面的包路径
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

2.2、欢迎页支持

  • 欢迎页:就是访问项目根路径的时候返回的页面。
  • SpringBoot支持两种方式的欢迎页:静态资源方式,模板方式
  • SpringBoot默认的静态资源配置方式,静态资源路径下的 :index.html
  • 我们也可以对SpringBoot默认的配置进行修改,如下:
    • 可以配置静态资源路径所在的文件夹,但是页面的名字必须是index.html
    • 但是不可以配置静态资源的访问前缀,否则导致 index.html不能被默认访问,具体原因可以在源码中找到
    • 也可以配置Controller用来处理 /index
spring:
  resources:
    static-locations: [classpath:/aismall]

2.3、自定义 Favicon

  • Favicon:喜欢的图标,也就是页面图标。
  • 把图标名字命名为favicon.ico ,然后放在静态资源目录下即可
  • 注意:名字不能改
  • 静态路径访问前缀会影像这个图标的加载,不可以指定访问前缀

2.4、静态资源配置原理(了解)

1、SpringBoot启动默认加载: xxxAutoConfiguration 类(自动配置类按需加载)

2、SpringMVC功能的自动配置类: WebMvcAutoConfiguration,生效(由于场景启动器帮我们导入了相关的包,自动配置按需加载),点进来看看:

@Configuration(proxyBeanMethods = false)
//是不是web类型的应用,servlet就是web类型的
@ConditionalOnWebApplication(type = Type.SERVLET)
//有没有这几个类,导入有关web的依赖,就会包含这些类,我们不用手动导入。
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
//容器中没有这个类型的组件:WebMvcConfigurationSupport
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 在这个类中有个静态内部类:WebMvcAutoConfigurationAdapter, 点进去看看,给容器中配了什么:
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
//和属性相关的配置类:这个注解将这两个类注入到容器中,用于绑定属性信息
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })这些Properties都是和配置文件绑定的类,遇到了要注意:

配置文件的相关属性和xxx进行了绑定:

WebMvcProperties==spring.mvc

ResourceProperties==spring.resources

配置类中只有一个有参构造器

WebMvcAutoConfigurationAdapter,这个类的部分内容如下

有参构造器所有参数的值都会从容器中确定 :

//获取和spring.resources绑定的所有的值的对象
ResourceProperties resourceProperties; 
//获取和spring.mvc绑定的所有的值的对象
WebMvcProperties mvcProperties;
//Spring的beanFactory
ListableBeanFactory beanFactory;
//找到所有的HttpMessageConverters
HttpMessageConverters 
//找到资源处理器的自定义器。
ResourceHandlerRegistrationCustomizer
//处理的路径
DispatcherServletPath  
// 给应用注册Servlet、Filter....
ServletRegistrationBean  

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;
        }
资源处理的默认规则

WebMvcAutoConfigurationAdapter. addResourceHandlers()方法,内容如下:

  • 我们自己可以通过在配置文件中设置来禁用所有的自动配置静态资源:
spring:
  resources:
    add-mappings: false
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //这放方法返回false下面的配置不生效,所有的静态资源规则不生效
    //我们可以在配置文件中设置这个值isAddMappings为false
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    //下面的代码都是配置静态资源规则的:
    //1.设置缓存的时间
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    //2.webjars的规则
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                                             .addResourceLocations("classpath:/META-INF/resources/webjars/")
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
    //3.静态资源路径配置规则,不配置默认值是/**
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
欢迎页的处理默认规则
  • WebMvcAutoConfigurationAdapter.welcomePageHandlerMapping()方法
  • HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    //创建一个WelcomePageHandlerMapping对象,需要传入四个参数
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
        this.mvcProperties.getStaticPathPattern());
    //拦截器设置
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    //
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}
  • WelcomePageHandlerMapping:有参构造,default权限
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)) {
        // 前面的if处理不了,就调用Controller看看能不能处理/index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}
注意

静态资源的默认配置规则和欢迎页的默认配置规则是分开的,但是都受前缀的影响,也就是不能配置前缀,但是欢迎页不受静态资源配置路径的影响,从源码中可以看出来。

03、请求参数处理原理(掌握)

3.1、请求映射

rest使用

Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

  • 以前:

    • /getUser :获取用户
    • /deleteUser: 删除用户
    • /editUser:修改用户
    • /saveUser:保存用户
  • 现在:

    • /user GET:获取用户
    • /user DELETE:删除用户
    • /user PUT:修改用户
    • /user POST:保存用户
  • 注意:表单只能发Get和Post类型的请求,对于Post请求后台会根据隐藏域中的_method的值来把请求方式替换掉,这个替换需要手动开启

<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
	<!--根据隐藏域中的内容将post请求替换为delete请求-->
    <input name="_method" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
	<!--根据隐藏域中的内容将post请求替换为put请求-->
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>
  • 核心Filter:HiddenHttpMethodFilter:修改隐藏参数的类,需要在SpringBoot中手动配置才能开启
@Bean
//如果我们手动配置了,这个自动配置就不会起作用
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}
  • spring.mvc.hiddenmethod.filter.enabled=true:开启表的Rest功能
#配置文件中
spring:
    mvc:
        hiddenmethod:
          filter:
            enabled: true  #开启页面表单的Rest功能
  • Controller的请求方式:模拟向前端返回字符串
@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-张三";
}
  • 扩展:如何把隐藏域name值_method 修改为我们自己喜欢的,了解即可,没必要修改:
//自定义filter,来更改这个值
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
}
Rest原理

表单提交要使用REST的时候,对于Post请求:

  • 表单提交会带上隐藏域参数和对应的值:_method=PUT

  • 请求过来被HiddenHttpMethodFilter.doFilterInternal()拦截

    • 请求是否正常,并且是POST
      • 获取到_method的值。
      • 兼容以下请求:PUTDELETEPATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的
  • HiddenHttpMethodFilter.doFilterInternal()

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		//获取我们传过来的请求
		HttpServletRequest requestToUse = request;
		//判断:如果是POST请求,获取请求路径
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			//获取请求隐藏参数:_method
			String paramValue = request.getParameter(this.methodParam);
			//如果不为空执行下面的代码,为空直接方行。
			if (StringUtils.hasLength(paramValue)) {
				//将隐藏参数对应的值转换为大写
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				//如果这个值在这个兼容的数组中
				if (ALLOWED_METHODS.contains(method)) {
					//使用这个Request包装类进行包装
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		//过滤器方行
		filterChain.doFilter(requestToUse, response);
	}

RestFul风格注解替换注解:

@RequestMapping(value = "/user",method = RequestMethod.GET)=@GetMapping("/user")

@RequestMapping(value = "/user",method = RequestMethod.POST)=@PostMapping("/user")

@RequestMapping(value = "/user",method = RequestMethod.PUT)=@PutMapping("/user")

@RequestMapping(value = "/user",method = RequestMethod.DELETE)=@DeleteMapping("/user")

使用客户端工具发送REST请求,就不需要Filter,此Filter之只对表单提交的REST风格的请求:

  • PostMan客户端发送工具:直接发送PUT、DELETE等方式请求,无需Filter。
  • 因为表单只能写GET和POST请求,所以发送其他请求类型需要带隐藏值,被后端接收到后进行替换,客户端发送工具可以发送任何形式的请求,所以不需要进行包装,自然也就不需要拦截。
  • 所以这个Filter是选择性开启,如果不是做表单开发,就不需要开启
请求映射原理

请求映射:每次发请求是如何找到对应的处理映射的。

  • 继承关系:HttpServlet-->HttpServletBean-->FrameworkServlet-->DispatcherServlet
    在这里插入图片描述
  • SpringMVC功能分析都从 :org.springframework.web.servlet.DispatcherServlet -->doDispatch()开始

doDispatch()这个方法上打个断点,Debug开始分析:

  • doDispatch()部分代码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		//获取当前的request请求
		HttpServletRequest processedRequest = request;
		//获取Handler执行器链
		HandlerExecutionChain mappedHandler = null;
		//判断是不是文件上传请求,这里是false
		boolean multipartRequestParsed = false;
		//看看是不是异步
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 决定哪个Controller处理当前请求,进入这个方法
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				//HandlerMapping:处理器映射。/xxx->>xxxx

mappedHandler = getHandler(processedRequest);:下面分析一下这句代码是如何实现判断使用什么处理器处理请求的:

进入这个方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//判断处理器映射是不是空,不为空,size=5,下面有截图
		if (this.handlerMappings != null) {
			//遍历这个处理器映射集合   
			for (HandlerMapping mapping : this.handlerMappings) {
				// 当我们遍历到 RequestMappingHandlerMapping时查看变量如下图
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

在这里插入图片描述
在这里插入图片描述

  • 1、AbstractHandlerMapping.getHandler()

HandlerExecutionChain handler = mapping.getHandler(request);

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		//获取到能处理当前request请求的方法,具体可以进入这个方法查看
		Object handler = getHandlerInternal(request);
		//看看拿到的是不是空,如果为空就代表没找到
		if (handler == null) {
			//返回一个defaultHandler
			handler = getDefaultHandler();
		}
		//再次判断是否为空,还是空,就返回一个null
		if (handler == null) {
			return null;
		}
		//Bean name or resolved handler?
		if (handler instanceof String) {
			//强转为字符串
			String handlerName = (String) handler;
			handler = obtainApplicationContext().getBean(handlerName);
		}
		//处理器执行链
		HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		//判断是否在控制台打印日志
		if (logger.isTraceEnabled()) {
			logger.trace("Mapped to " + handler);
		}
		else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
			logger.debug("Mapped to " + executionChain.getHandler());
		}
		if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
			CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			config = (config != null ? config.combine(handlerConfig) : handlerConfig);
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

		return executionChain;
	}
  • 2、AbstractHandlerMapping.getHandler()

Object handler = getHandlerInternal(request);

  • 3、RequestMappingInfoHandlerMapping.getHandlerInternal()

return super.getHandlerInternal(request);

  • 4、AbstractHandlerMethodMapping.getHandlerInternal()中:此处调用达到最深,然后开始返回处理的结果,即使用那个处理器处理请求。

String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

请求映射原理总结

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 :WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认的 :RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 如果我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

注意:SpringBoot自带的RequestMappingHandlerMappingWelcomePageHandlerMapping是在WebMvcAutoConfiguration.EnableWebMvcConfiguration中使用方法添加到容器的。

3.2、普通参数与基本注解

注解

最常用好理解的注解:

  • @PathVariable:路径变量,获取请求路径中的变量值
  • @RequestHeader:获取请求头
  • @RequestParam:获取请求参数
  • @CookieValue:获取cookie值
  • @RequestBody:获取请求体,只有post请求中才会有请求体
  • @RequestAttribute:获取请求的属性值

常用不好理解的注解:

  • @MatrixVariable:矩阵变量
前端测试代码
<h2>======测试基本注解=======</h2>
<br/>
测试@PathVariable,@RequestHeader,@RequestParam,@CookieValue获取数据<br/>
<a href="carSingle/3/owner/aismall?age=18&inters=basketball&inters=game">carSingle/{id}/owner/{username}</a><br/>
<a href="carMap/3/owner/aismall?age=18&inters=basketball&inters=game">carMap/{id}/owner/{username}</a><br/>
<br/>
测试@RequestBody获取数据 <br/>
注意:只有post请求中才有请求体<br/>
<form action="/save" method="post">
    用户名:<input name="userName" value="AISMALL"/> <br>
    邮箱:<input name="email" value="123456"/>
    <input type="submit" value="提交"/>
</form>
<br/>
测试@RequestAttribute获取数据<br/>
<a href="/goto">goto跳转returnMSG</a><br/>
<br/>
测试@MatrixVariable获取数据<br/>
<br/>
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable:矩阵变量,方式一</a><br/>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable:矩阵变量,方式二</a><br/>
<a href="/boss/sell;age=20/2;age=10">@MatrixVariable:矩阵变量,方式三</a><br/>
<br/>
<h2>======测试基本注解=======</h2>
后端测试代码

注意:向浏览器返回的是JSON数据,如果乱码,可以这样设置

  • @GetMapping(value = "/路径",produces = "application/json; charset=utf-8")
/**
 * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
 */

@RestController
public class ParameterTestController {
    //方法一:单个获取
    @GetMapping("/carSingle/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters){
        //创建一个Map存放获取到的值
        Map<String,Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",name);
        map.put("userAgent",userAgent);
        map.put("age",age);
        map.put("inters",inters);
        return map;
    }
    //方法二:使用map获取
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCarMap(@PathVariable Map<String,String> pv,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("NMTID") String _ga,
                                     @CookieValue("NMTID") Cookie cookie){
        //创建一个Map存放获取到的值
        Map<String,Object> map = new HashMap<>();
        map.put("pv",pv);
        map.put("headers",header);
        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;
    }

    //测试矩阵变量的功能:/cars/sell;low=34;brand=byd,audi,yd
    @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/sell;age=20/2;age=10
    @GetMapping(value = "/boss/{bossId}/{empId}",produces = "application/json;charset=utf-8")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @PathVariable("bossId") String pathBoosId,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge,
                    @PathVariable("empId") String pathEmpId){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("pathBoosId",pathBoosId);
        map.put("empAge",empAge);
        map.put("pathEmpId",pathEmpId);
        return map;
    }
}
/**
 * 测试@RequestAttribute获取数据,和页面跳转
 * */
@Controller
public class RequestController {
    //页面跳转
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        //设置属性:在向returnMSG页面跳转的时候获取
        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        //转发到:success请求
        return "forward:/returnMSG";
    }

    //returnMSG
    @ResponseBody
    @GetMapping(value = "/returnMSG",produces = "application/json; charset=utf-8")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
                       @RequestAttribute(value = "code",required = false)Integer code,
                       HttpServletRequest request){
        //使用method获取属性值
        Object msg1 = request.getAttribute("msg");

        //创建一个map
        Map<String,Object> map = new HashMap<>();
        //获取下面这个属性的值由于没有设置,所以结果为null
        Object hello = request.getAttribute("hello");

        map.put("reqMethod_msg",msg1);
        map.put("annotation_msg",msg);
        map.put("hello",hello);
        return map;
    }
}
再谈@MatrixVariable注解
<a href="/cars/sell;low=34;brand=byd,audi,yd">矩阵变量</a>
  • 分号之前的是路径,也就是sell,使用@PathVariable注解获取
  • 分号之后的是矩阵变量,也就是lowbrand,多个矩阵变量使用分号隔开,使用@MatrixVariable注解获取。
  • 矩阵变量会被拼接到路径上,类似于路径拼接一样,只不过拼接的方式不一样获取的值注解也不一样
<a href="/boss/sell;age=20/2;age=10">@MatrixVariable:矩阵变量</a>
  • 真正的路径是:/boss/sell/2
  • sell2这两个路径之间添加了一个矩阵变量age=20
  • 矩阵变量和路径要连在一起看,也就分号之前和之后的连在一起:sell;age=20

矩阵变量需要在SpringBoot中手动开启:

  • 第一步:自定义路径映规则,实现WebMvcConfigurer接口
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
	//配置路径映射
   @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除; 后面的内容不会被截掉,矩阵变量功能生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
SpringBoot默认是禁用了矩阵变量的功能
	手动开启:原理。对于路径的处理,都是使用UrlPathHelper进行解析。
	removeSemicolonContent(移除分号内容)设置为false表示不移除,支持矩阵变量

矩阵变量必须有url路径变量才能被解析,也就是路径和矩阵变量要配合使用
  • 若是有多个矩阵变量,应当使用英文符号;进行分隔。
  • 若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或指明名多个重复的key即可。

如:/cars/sell;low=34;brand=byd,audi,yd

面试问题:

  • 页面开发,cookie禁用了,session里面的内容怎么使用?

每个session有一个sessionId被称为jsessionid,这个jsessionid会被保存在cookie里面,这个cookie每次发请求都会携带
cookie没禁用以前,每次发请求都会带着这个cookie,找到cookie里面的jsessionid,然后你通过get方法获取这个id
session.set(a,b)—> jsessionid —> cookie ----> 每次发请求携带
session.get(a,b)<— jsessionid <— cookie
如果cookie被禁:我们要重写路径,使用矩阵变量的方式带上这个jsessionid

路径重写(url)/car;jsesssionid=xxxx ,把cookie的值使用矩阵变量的方式进行传递
矩阵变量,方式一:"/cars/sell;low=34;brand=byd,audi,yd"
矩阵变量,方式二:"/cars/sell;low=34;brand=byd;brand=audi;brand=yd"
矩阵变量,方式三:"/boss/1;age=20/2;age=10" /boss/{bossId}/{empId}
Servlet API
  • 原生ServletAPI

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

  • ServletRequestMethodArgumentResolver 以上的部分参数
@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);
    }

处理原生SerlvetAPI参数的方法和处理其他的参数的方法差不多,步骤都是:

  • 1、确定使用什么处理器映射器处理请求
  • 2、确定使用什么处理器适配器处理参数
  • 3、使用处理器是适配器处理参数
  • 4、在处理器适配器中确定参数解析器,来解析参数,其中原生ServletAPI的参数解析器是这个:ServletRequestMethodArgumentResolver
  • 5、上面的执行结束,就确定好了什么方法处理,如何解析参数,返回什么结果

注意:这些处理都在DispatcherServlet.doDispatch()方法中完成。

复杂参数

注意:不管传入什么样的参数,都会找到对应的解析器去解析执行,具体的可以自行设计接口,然后debug来验证。

  • 断点打在DispatcherServlet.doDispatch()方法上面
Map
Model
	map、model里面的数据会被放在request的请求域中  			      
	request.setAttribute
Errors/BindingResult
RedirectAttributes( 重定向携带数据)
ServletResponse(response)
SessionStatus
UriComponentsBuilder
ServletUriComponentsBuilder

不同的参数会使用不同的参数解析器,原理都差不多,要学会触类旁通。

自定义对象参数
前端测试代码
<h2>======测试自定义对象参数(测试封装POJO)=======</h2>
<form action="/saveuser" method="post">
    姓名: <input name="userName" value="aismall"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="1993/11/2"/><br/>
    宠物姓名:<input name="pet.name" value="tomcat"/><br/>
    宠物年龄:<input name="pet.age" value="5"/><br/>
    <!--宠物: <input name="pet" value="啊猫,3"/>-->
    <input type="submit" value="保存"/>
</form>
<h2>======测试自定义对象参数(测试封装POJO)=======</h2>
后端测试代码
  • Controller:
@RestController
public class ParameterTestController {
    //测试POJO封装
    @PostMapping("/saveuser")
    public Person saveuser(Person person){
        return person;
    }
}
  • POJO类
public class Person {

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}
自定义对象参数绑定原理

自定义参数的处理也是找到对应的适配器处理请求参数,原理差别不大,我们可以在DispatchServlet.doDispatch()方法中打断点,然后Debug来看:

  • ServletModelAttributeMethodProcessor 这个参数处理器支持,是否为简单类型。

  • WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面

  • WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

  • WebDataBinder binder.binderFactory.createBinder(webRequest, attribute, name);

  • GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
    byte – > file

3.3、参数处理原理(了解)

同样在doDispatch()方法上打断点,分析开始:

前面通过mappedHandler = getHandler(processedRequest);确定了处理器
下面就看这句代码HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());:判断使用什么处理器适配器,下面的分析都是这个方法的调用:

进入这句代码:

  • 1、HandlerExecutionChain.getHandler()中:

return this.handler;

  • 2、DispatcherServlet.getHandlerAdapter()中:

adapter.supports(handler)

//我们使用debug方式计算这个参数:handler的值,结果如下图:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		//判断是否为空
		if (this.handlerAdapters != null) {
			//遍历这个HandlerAdapters,有四种类型
			for (HandlerAdapter adapter : this.handlerAdapters) {
				//进行判断
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

在这里插入图片描述

0适配器:支持方法上标注@RequestMapping
1适配器:支持函数式编程的

在这里插入图片描述

  • 3、AbstractHandlerMethodAdapter.supports()中:调用到达最大深度,开始返回计算结果,使用哪个适配器。

return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));

接下来就是使用这个适配器如何处理请求参数

通过前面发分析我们确定了:请求处理器,处理器适配器,下面来看看如何使用这个适配器处理参数。

  • 1、 DispatcherServlet.doDispatch()中:执行目标方法

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

  • 2、 HandlerExecutionChain.getHandler()中:

return this.handler;

  • 3、AbstractHandlerMethodAdapter.handle()中:

return handleInternal(request, response, (HandlerMethod) handler);

  • 4、RequestMappingHandlerAdapter.handleInternal()中:执行目标方法

mav = invokeHandlerMethod(request, response, handlerMethod);

  • 5、 RequestMappingHandlerAdapter.invokeHandlerMethod()中:判断语句

参数解析器:this.argumentResolvers != null

返回值处理器:this.returnValueHandlers != nul

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
		//把参数值解析器,和返回值处理器都放在ServletInvocableHandlerMethod里面
		//具体的可以点进这个类,查看属性值
		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);

		ModelAndViewContainer mavContainer = new ModelAndViewContainer();
		mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
		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);
		}
		//执行并处理,点进去查看   重点!!!!!!!
		invocableMethod.invokeAndHandle(webRequest, mavContainer);
		if (asyncManager.isConcurrentHandlingStarted()) {
			return null;
		}

		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
		webRequest.requestCompleted();
	}
}

在这里插入图片描述
在这里插入图片描述

  • 6、ServletInvocableHandlerMethod.invokeAndHandle()中:执行当前请求

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

  • 7、InvocableHandlerMethod.invokeForRequest()中:确定方法参数值

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

  • 8、InvocableHandlerMethod.getMethodArgumentValues()中:如何确定方法的参数值
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 {
				//解析这个参数的值,解析完进行返回,可以点进来看看!!!!!
				//调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
				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;
	}
  • 9、InvocableHandlerMethod.invokeForRequest()中:确定方法参数值后,执行目标方法。

return doInvoke(args);

总结
  • 1、无论是请求映射,还是参数处理(后面具体说会说),我们都是从DispatcherServlet.doDispatch()方法开始

  • 2、从请求映射中我们知道,当前端发送请求的时候,使用什么方法去映射这个请求,即处理器映射

  • 3、接着就是判断使用什么映射器适配器去处理当前请求

  • 4、最后使用判断出来的适配器执行目标方法并确定方法参数的每一个值

3.4、返回值处理原理(了解)

请求映射原理:确定使用那个函数处理请求
参数处理原理:给函数确定参数,然后执行函数
返回值处理原理:处理函数的返回值

目标方法执行完成:

  • ServletInvocableHandlerMethod.invokeAndHandle()中:处理返回结果
    • 将所有的数据都放在 ModelAndViewContainer(mavContainer):包含要去的页面地址View,还包含数据Model

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

  • DispatcherServlet.doDispatch()中:处理派发结果

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

  • AbstractView.render()中:

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

  • InternalResourceView. renderMergedOutputModel()中:

protected void renderMergedOutputModel(
         Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

     // 暴露模型作为请求域属性
     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);
     }
 }
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);
            }
        });
    }

04、数据响应与内容协商

4.1、数据响应

响应数据可以有很多种形式:

  • JSON,XML,XLS,图片,音频,自定义数据等。
  • 先介绍JSON数据响应
4.1.1、jackson.jar:JSON数据
  • SpringBoot给前端自动返回json数据
  • 当我们引入web的场景启动器之后,这个启动器会帮我们自动引入json有关的启动器。
  • web场景启动器引入
<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的场景启动器帮我们自动引入了jackson.jar,来处理JSON数据
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
返回值解析器原理

返回值的处理利用返回值解析器,要在处理返回值逻辑的时候在进入:

  • 1、DIspatchServlet.doDospatch():中

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

  • 2、AbstractHandlerMethodAdapter.handle():中

return handleInternal(request, response, (HandlerMethod) handler);

  • 3、RequestMappingHandlerAdapter.handleInternal():中

mav = invokeHandlerMethod(request, response, handlerMethod);

  • 4、RequestMappingHandlerAdapter.invokeHandlerMethod():中

invocableMethod.invokeAndHandle(webRequest, mavContainer); //进入此方法

计算:invocableMethod,得出返回值解析器

if (this.returnValueHandlers != null) {
	invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}

在这里插入图片描述

  • 5、ServletInvocableHandlerMethod.invokeAndHandle():中
try {
	//使用返回值处理器处理返回值
	this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
  • 6、HandlerMethodReturnValueHandlerComposite.handleReturnValue():中
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);
	}
  • 7、ViewNameMethodReturnValueHandler.handleReturnValue():中
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

		if (returnValue instanceof CharSequence) {
			String viewName = returnValue.toString();
			mavContainer.setViewName(viewName);
			if (isRedirectViewName(viewName)) {
				mavContainer.setRedirectModelScenario(true);
			}
		}
		else if (returnValue != null) {
			// should not happen
			throw new UnsupportedOperationException("Unexpected return type: " +
					returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
		}
	}
返回值解析器原理总结
  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    • 1、利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
4.1.2、SpringMVC到底支持哪些返回值

一共15种,前面已经说过了。

  • ModelAndView
  • Model
  • View
  • ResponseEntity
  • ResponseBodyEmitter
  • StreamingResponseBody
  • HttpEntity
  • HttpHeaders
  • Callable
  • DeferredResult
  • ListenableFuture
  • CompletionStage
  • WebAsyncTask
  • 有 @ModelAttribute 且为对象类型的
  • @ResponseBody 注解 —> RequestResponseBodyMethodProcessor;

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

05、视图解析与模板引擎

视图解析:SpringBoot默认不支持JSP,需要引入第三方模板引擎技术实现页面渲染,虽然使用的是HTML页面,支持JSP语法。

注意:

  • 使用模板引擎+SpringBoot可以实心前后端不分离的项目。
  • 目前主流的是前后端分离项目,前端使用VUE的比较多,所以这个章节可以学习仅作了解,也可以不学,直接跳过

5.1、视图解析

视图的处理方式:

  • 转发
  • 重定向
  • 自定义视图
视图解析原理流程
  • 1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址。
  • 2、方法的参数是一个自定义类型对象(从请求参数中确定的),把它重新放在 ModelAndViewContainer
  • 3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
  • 4、processDispatchResult 处理派发结果(页面改如何响应)
    • 1、render(mv, request, response); 进行页面渲染逻辑
      • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
        • 1、所有的视图解析器尝试是否能根据当前返回值得到View对
        • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
        • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
        • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
        • RedirectView 如何渲染【重定向到一个页面】
          • 1、获取目标url地址
          • 2、response.sendRedirect(encodedURL);

视图解析:

  • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
  • 返回值以 redirect: 开始: new RedirectView() -->render就是重定向
  • 返回值是普通字符串: new ThymeleafView()

5.2、模板引擎:Thymeleaf(了解)

5.2.1、thymeleaf简介

现代化、服务端Java模板引擎,性能不是太强,仅作了解!!!!!

注意:如果开发小型的后台管理系统(前后端不分离的那种),使用SpringBoot+Thymeleaf模板引擎还是可以的!!!!!!!

5.2.2、基本语法
  • 1、表达式
表达式名字语法用途
变量取值${…}获取请求域、session域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段
  • 2、字面量
    文本值: ‘one text’ , ‘Another one!’ ,…
    数字: 0 , 34 , 3.0 , 12.3 ,…
    布尔值: true , false
    空值: null
    变量: one,two,… 变量不能有空格
  • 3、文本操作
    字符串拼接: +
    变量替换: |The name is ${name}|
  • 4、数学运算
    运算符: + , - , * , / , %
  • 5、布尔运算
    运算符: and , or
    一元运算: ! , not
  • 6、比较运算
    比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
  • 7、条件运算
    If-then: (if) ? (then)
    If-then-else: (if) ? (then) : (else)
    Default: (value) ?: (defaultvalue)
  • 8、特殊操作
    无操作: _
5.2.3、设置属性值 th:attr
  • 设置单个
<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>
  • 设置多个值
<img src="/images/logo.png"  th:attr="src=@{/images/logo.png},title=#{logo},alt=#{logo}" />
5.2.4、迭代
<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>
5.2.5、条件运算
<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>

5.3、thymeleaf使用

5.3.1、引入Starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
5.3.2、自动配置好了thymeleaf
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration { }

自动配好的策略

  • 1、所有thymeleaf的配置值都在 ThymeleafProperties
  • 2、配置好了 SpringTemplateEngine
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面

默认存放页面的位置:文件的前缀后缀

public static final String DEFAULT_PREFIX = "classpath:/templates/";

public static final String DEFAULT_SUFFIX = ".html";  //xxx.html
5.3.3、页面开发
  • 每个thymeleaf页面都要加thymeleaf的名称空间
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">hello world</h1>
<h2>
    <a href="www.aismall.com" th:href="${link}">百度一下</a> 
</h2>
</body>
</html>
  • 后台方法
@Controller
public class ViewTestController {
    @GetMapping("/aismall")
    public String atguigu(Model model){
        model.addAttribute("msg","你好 aismall");
        model.addAttribute("link","http://www.baidu.com");
        return "success";
    }
}

06、拦截器

我们可以使用servlet自带的过滤器Filter,也可以使用拦截器HandlerInterceptor

6.1、HandlerInterceptor 接口

在这里插入图片描述

  • afterCompletion:页面渲染完成之后做操作
  • postHandle:处理完请求,页面渲染之前做操作
  • preHandle:请求处理之前做操作

6.2、配置拦截器

我们可以给每个映射处理方法设置拦截器,例如:做页面登录检查,不登录不能进入index.html页面。

配置拦截器:

  • 1、编写一个拦截器实现HandlerInterceptor接口
  • 2、拦截器注册到容器中(实现WebMvcConfigurer接口重写addInterceptors方法)
  • 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
@Slf4j		//日志类Sl4j
//登录页面的拦截器
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    	//获取URI
        String requestURI = request.getRequestURI();
        //控制台打印
        log.info("preHandle拦截的请求路径是{}",requestURI);
        //登录检查逻辑,获取session
        HttpSession session = request.getSession();
        //获取这个属性,没有返回null
        Object loginUser = session.getAttribute("loginUser");
        //为null代表未登录,也就是需要拦截
        if(loginUser != null){//放行
            return true;
        }
        //拦截住。向request域设置一个属性
        request.setAttribute("msg","请先登录");
        //请求转发,转发到 / ,访问 / 也会转发到login
      	request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    	//输出一条语句到控制台
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    	//输出一条语句到控制台
        log.info("afterCompletion执行异常{}",ex);
    }
}
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

6.3、拦截器原理

映射方法doDispatch()方法上打断点进行Debug:

  • 1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
  • 2、先来顺序执行 所有拦截器的 preHandle方法
    • 1、如果当前拦截器preHandler返回为true。则执行下一个拦截器的preHandle
    • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
  • 3、如果任何一个拦截器返回false。直接跳出不执行目标方法,拦截成功,所以不能执行目标方法。
  • 4、所有拦截器都返回True。拦截失败,执行目标方法
  • 5、倒序执行所有拦截器的postHandle方法。
  • 6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
  • 7、页面成功渲染完成以后,也会倒序触发 afterCompletion

在这里插入图片描述

07、文件上传

7.1、页面表单

<form method="post" action="/upload" enctype="multipart/form-data">
	<input type="text" name="username"><br>
	<!--单文件上传-->
    <input type="file" name="headerImg"><br>
    <!--多文件上传-->
    <input type="file" name="photos" multiple><br>
    <input type="submit" value="提交">
</form>

7.2、文件上传代码

 @PostMapping("/upload")
 public String upload( @RequestParam("username") String username,
                       @RequestPart("headerImg") MultipartFile headerImg,
                       @RequestPart("photos") MultipartFile[] photos) throws IOException {

       log.info("上传的信息:username={},headerImg={},photos={}",
                username,headerImg.getSize(),photos.length);
      
      //处理头像 :单文件   
      if(!headerImg.isEmpty()){
          //保存到文件服务器,OSS服务器
          String originalFilename = headerImg.getOriginalFilename();
          headerImg.transferTo(new File("D:\\cache\\"+originalFilename));
      }
      //处理图片:多文件
       if(photos.length > 0){
            for (MultipartFile photo : photos) {
                if(!photo.isEmpty()){
                    String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("D:\\cache\\"+originalFilename));
                }
            }
        }
      return "index";
  }

7.3、自动配置原理

文件上传直接研究这个两个类即可,一个自动配置类一个属性类

  • 文件上传自动配置类:MultipartAutoConfiguration
    • 自动配置好了 StandardServletMultipartResolver 文件上传解析器
  • 封装属性值的类:MultipartProperties

断点打在doDispatch()方法上,进行Debug,原理步骤:

  • 1、请求进来使用文件上传解析器判断:isMultipart并封装resolveMultipart返回MultipartHttpServletRequest,文件上传请求

  • 2、参数解析器来解析请求中的文件内容封装成MultipartFile

  • 3、将request中文件信息封装为一个Map ,MultiValueMap<String, MultipartFile>

  • FileCopyUtils:实现文件流的拷贝

08、异常处理之错误处理

8.1、默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。
  • 对于浏览器客户端,响应一个whitelabel错误视图,以HTML格式呈现相同的数据
  • 要对其进行自定义,不是完全的自定义,我们将错误页面(4xx.html和5xx.html存放到静态资源路径下的error文件夹),SpringBoot会帮我们自动添加View解析为error
  • 要完全替换默认行为,完全的自定义,编写控制类实现 ErrorController ,并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析;
    在这里插入图片描述

8.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());
  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
  • ErrorViewResolver 实现自定义处理异常;
    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver ;

8.3、异常处理自动配置原理

  • ErrorMvcAutoConfiguration :异常处理自动配置类
    • 1、给容器中添加组件:类型:DefaultErrorAttributesid:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
    • 2、给容器中的添加组件:类型:BasicErrorControllerid:basicErrorController(json+白页适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
      • 容器中有,组件 View,id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
    • 3、给容器中的添加组件:类型:DefaultErrorViewResolverid:conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html
  • 如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

8.4、异常处理步骤流程

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束,并且用 dispatchException

2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常:HandlerExceptionResolver处理器异常解析器
  • 2、系统默认的 异常解析器;
    • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    • 2、默认没有任何人能处理异常,所以异常会被抛出
      • 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理
      • 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
      • 3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
      • 4、模板引擎最终响应这个页面 error/500.html

09、Web原生组件注入

原生Servlet组件值得就是Servlet的三大组件:

  • Servlet:请求处理器
  • Filter:过滤器
  • Listener:监听器

9.1、使用Servlet API

以前使用Servlet三大组件,是把三大组件写好,配置在web.xml中

现在SpringBoot开发方式如下:

  • 编写处理请求的类,在类上使用注解:@WebServlet

@WebServlet(urlPatterns = “/path”)
public class MyServlet extends HttpServlet{}

效果:直接响应,没有经过Spring的拦截器,原生的Servlet只能使用原生的拦截器拦截,Spring拦截不了原生Servlet。

  • 在主配置类中使用注解,定义扫描包路径:@ServletComponentScan

@ServletComponentScan(basePackages = “com.aismall.servlet”) :指定原生Servlet组件都放在这个包里

  • 编写过滤器类,再类上使用注解:@WebFilter

@WebFilter(urlPatterns={"/css/*","/images/*"})
public class MyFilter implements Filter{}

  • 编写监听器类,再类上使用注解:@WebListener

@WebListener
public class MyListener implements ServletContextListener{} //监听项目初始化

注意:如果我们使用原生Servlet的话,现在SpringBoot中就有两个Servlet,原生ServletDispatchServlet

扩展:DispatchServlet 如何注册进来

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
  • 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。
  • 默认映射的是 / 路径。
    在这里插入图片描述
  • Tomcat-Servlet;
    • 多个Servlet都能处理到同一层路径,精确优选原则:如果路径中包含my优先使用MyServlet处理,不包含优先使用DispatchServlet处理,因为路径中一定会有/这个路径
      • A: /
      • B: /my
      • C:/my/path

9.2、使用RegistrationBean

除了使用上面的方法在SpringBoot中使用原生Servlet API、还可使用下面的注解:

  • ServletRegistrationBean
  • FilterRegistrationBean
  • ServletListenerRegistrationBean
@Configuration
public class MyRegistConfig {
    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/path01","/path02");
    }
    @Bean
    public FilterRegistrationBean myFilter(){

        MyFilter myFilter = new MyFilter();
        //return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/path01","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}

10、嵌入式Servlet容器

SpringBoot内部有自己的嵌入式的容器

10.1、切换嵌入式Servlet容器

默认支持的webServer(web服务:也就是web容器):

  • Tomcat,
  • Jetty
  • Undertow

ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器

切换服务器,SpringBoot默认帮我们导入的是tomcat服务器,如果我们想切换为别的服务器,只需要把web-starter中帮我们自动导入的tomcat服务器去除掉,然后添加我们想要导入的服务器的场景启动器即可:

  • 注意:SpringBoot官方支持的场景启动器可以去参考文档中查找
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <!--去除默认的tomcat服务器-->
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
       </exclusion>
   </exclusions>
</dependency>
<!--添加undertow的场景启动器-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

原理:

  • SpringBoot应用启动发现当前是Web应用。web场景包,导入tomcat
  • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
  • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)
  • SpringBoot底层默认有很多的WebServer工厂
    • TomcatServletWebServerFactory,
    • JettyServletWebServerFactory
    • UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类:ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
  • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包,默认是web-starter导入tomcat包,容器中就有 TomcatServletWebServerFactory
  • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
  • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

10.2、定制Servlet容器

定制Servlet容器,例如:

  • 改变服务器端口号
  • 改变服务器编码方式

方式一:修改配置文件

  • 根web容器自动配置文件绑定的属性类是:ServerProperties

方式二:直接自定义 ConfigurableServletWebServerFactory

@Bean
public ConfigurableServletWebServerFactory webServerFactory(){
	TomcatServletWebServerFactory factory=new TomcatServletWebServerFactory factory();
	factory.setPort(9000);
	factory.setSessionTimeout(10,TimeUnit.MINUTES);
	factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/notfound.html"));
	return factory;
}

方式三:xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

  • 因为SpringBoot默认优先级是,优先使用用户自定义的,只有missbean的时候配置类才会生效
@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

11、定制化原理(了解)

11.1、定制化的常见方式

  • 第一种:修改配置文件,不知道如何修改的话,可以查看自动配置类中绑定的属性类,去属性类中查找

  • 第二种:xxxxxCustomizer定制化器。

  • 第三种:编写自定义的配置类 xxxConfiguration;+ @Bean替换、增加容器中默认组件;

  • 第四种:Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能;+ @Bean给容器中再扩展一些组件

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
	@Bean
	//组件一
	@Bean
	//组件二
}

全面接管SpringMVC:

  • 所有规则全部自己重新配置,实现定制和扩展功能
  • 第一步:编写一个配置类实现WebMvcConfigure接口
  • 第二步:在配置类上使用注解:@EnableWebMvc
  • 第三步:使用@Bean注解在配置类中添加拓展功能
  • 原理:
    • 1、WebMvcAutoConfiguration 默认的SpringMVC的自动配置功能类。静态资源、欢迎页…
    • 2、一旦使用 @EnableWebMvc 会 @Import(DelegatingWebMvcConfiguration.class)
    • 3、DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用
      • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
      • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcAutoConfiguration 里面的配置要能生效 必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。

11.2、原理分析套路

场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值