首页 > 其他分享 >SpringBoot 2x 系列之(七)web场景

SpringBoot 2x 系列之(七)web场景

时间:2024-02-28 10:24:52浏览次数:25  
标签:web SpringBoot 自定义 request 2x public String class 请求

web场景

1. SpringMVC自动配置概览

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.

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document)).

    • 静态资源(包括webjars)
  • Automatic registration of Converter, GenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).

    • 自动注册 MessageCodesResolver (国际化用【国际化一般直接开发两套网站,MessageCodesResolver用的很少】)
  • Static index.html support.

    • 静态index.html 页【欢迎页】支持
  • Custom Favicon support (covered later in this document).

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

官方提出的自定义配置的方式:

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.

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

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.

声明 WebMvcRegistrations 改变默认底层组件

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.

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

2. 简单功能分析

2.1 静态资源

对应2.6.8版本官方文档的8.1.1. The “Spring Web MVC Framework” Static Content 部分的内容

2.1.1 静态资源目录

只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources in the classpath

访问 : 当前项目根路径/ + 静态资源名

原理: 静态映射/**。

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

改变默认的静态资源目录

spring:
  resources:
    static-locations: [classpath:/haha/]

2.1.2 静态资源访问前缀【自定义静态资源访问路径】

方便拦截器配置拦截指定前缀的路径,避免拦截静态资源

默认无前缀

spring:
  mvc:
    static-path-pattern: /res/**

访问:当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

2.1.3 webjars

引入webjars后自动映射到路径:/webjars/**

        <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 欢迎页

对应2.6.8版本官方文档的8.1.1. The “Spring Web MVC Framework” Welcome Page 部分的内容

  • 静态资源路径下 index.html

    • 可以配置静态资源目录
    • 但是不可以配置静态资源的访问前缀【自定义静态资源访问路径】。否则导致 index.html不能被默认访问
  • spring:
    #  mvc:
    #    static-path-pattern: /res/**   这个会导致welcome page功能失效
    
      resources:
        static-locations: [classpath:/haha/]
    
  • controller中能处理/index请求的方法

2.3 自定义Favicon

  • 静态资源路径下 favicon.ico

    • 可以配置静态资源目录
    • 但是不可以配置静态资源的访问前缀【自定义静态资源访问路径】。否则导致 index.html不能被默认访问
    spring:
    #  mvc:
    #    static-path-pattern: /res/**   这个会导致Favicon功能失效
    
      resources:
        static-locations: [classpath:/haha/]
    

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 {}
  • 配置文件的相关属性和xxxProperties进行了绑定。WebMvcProperties对应spring.mvc、ResourceProperties对应spring.resources

一个配置类只有一个有参构造器,该构造器的参数都会从容器中取。

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的IOC容器
//ObjectProvider<HttpMessageConverters> messageConvertersProvider 找到所有的HttpMessageConverters
//ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider 找到 资源处理器的自定义器。=========
//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;
}

静态资源处理的默认规则

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
  			//获取设置的静态资源缓存时间
			Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
			CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
			//webjars的规则
            if (!registry.hasMappingForPattern("/webjars/**")) {
				customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
						.addResourceLocations("classpath:/META-INF/resources/webjars/")
						.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
              //设置该资源的缓存时间,比如1100s内浏览器会缓存该资源,超时之前访问都会直接使用缓存
			}
            
            //
			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;

欢迎页的处理规则

	HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求,请求过来之后,在HandlerMapping中找到处理该请求的Handler,然后通过反射调用能够处理该请求的方法。	

		@Bean
		public 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  /index
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

favicon

浏览器默认发送/favicon.ico请求获取到图标,整个session期间不再获取

2.5 请求参数处理

2.5.1 请求映射

​ 在Controller中的方法上通过@RequestMapping注解声明每个方法能够处理的请求,这个过程就叫做请求映射。

2.5.1.1 rest使用与原理
  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作
    @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-张三";
    }
    • *以前:**/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser *保存用户
    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put

        ​ 在表单中增加一个以请求方式为值的_method参数,发送请求时会携带该参数,到达Controller中的方法之前HiddenHttpMethodFilter会将_method参数的值也就是目标请求方式替换原来的POST,这样在Controller中的方法进行请求方式的判断时就是我们指定的目标请求方式了。

      • SpringBoot中手动开启【如果是通过表单发送DELETE、PUT请求】

    • spring:
        mvc:
          hiddenmethod:
            filter:
              enabled: true   #开启页面表单的Rest功能
      
    • 扩展:如何把_method 这个名字换成我们自己喜欢的。
  •  @Bean
     	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
     	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
     	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
     		return new OrderedHiddenHttpMethodFilter();
     	}
       //自定义filter
       @Bean
       public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
           HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
           methodFilter.setMethodParam("_m");
           return methodFilter;
       }
    

Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上_method=PUT

  • 请求过来被HiddenHttpMethodFilter拦截

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

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。
2.5.1.2 请求映射原理

​ 如何找到Controller中处理对应请求的方法的

请求首先来到DispatcherServlet

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:里面包含了Controller中处理当前请求的目标方法的整个信息
  			mappedHandler = getHandler(processedRequest);
              
              //HandlerMapping:处理器映射。/xxx->>xxxx

![](https://gitee.com/honourer/picturebed/raw/master/springboot2/image (1).png)

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

![](https://gitee.com/honourer/picturebed/raw/master/springboot2/image (2).png)

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

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

  • SpringBoot自动配置了默认的 RequestMappingHandlerMapping【解析所有Controller中标注了@RequestMapping(包括GetMapping、PostMapping等)的方法,将请求与处理该请求的Controller中对应方法的映射关系保存起来】

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

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

    自定义 HandlerMapping的场景

    ​ 当我们的项目有多个版本时,比如v1,v2两个版本,当我们发送/v1/...请求我们希望自定义HandlerMapping到指定的包下找,当我们发送/v2/...请求我们希望去另一个包中找。

	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;
	}

2.5.2 普通参数与基本注解

​ SpringMVC在底层处理web请求可以接收的传参类型:注解、Servlet API、复杂参数、自定义对象参数。【Controller中方法的参数可以是哪些】

2.5.2.1 注解

@PathVariable@RequestHeader、@ModelAttribute、@RequestParam@MatrixVariable@CookieValue@RequestBody@RequestAttribute

  • @PathVariable

    获取所有的路径变量:

    ​ @PathVariable Map<String, String>:标注在Map<String, String>上,所有的路径变量都会封装到该Map中

    获取指定的单个路径变量:@PathVariable("name") String name

    SpringMVC.md 三、常用注解 4. PathVariable

  • @RequestHeader

    获取所有的请求头:

    ​ @RequestHeader Map<String,String> header:标注在Map<String, String>/MultiValueMap<String, String>/HttpHeaders上,所有的路径变量都会封装到该变量中

    获取指定的单个请求头:@RequestHeader("User-Agent") String userAgent

    SpringMVC.md 三、常用注解 5. RequestHeader

  • @RequestParam

    获取所有的请求参数:

    ​ @RequestParam Map<String,String> params:标注在Map<String, String>/MultiValueMap<String, String>上,所有的请求参数都会封装到该变量中

    获取指定的单个请求参数:@RequestParam("age") Integer age,如果传过来的某个参数有多个值,通过List接收【@RequestParam("inters") List<String> inters】

    接收一个有多个值的参数的值时可能拿不到所有的值

    SpringMVC.md 三、常用注解 2. RequestParam

  • @CookieValue

    获取指定的单个Cookie:@CookieValue("_ga") String _ga 【仅仅拿到Cookie的值】 / @CookieValue("_ga") String _ga【可以拿到该Cookie对象,包含完整的Cookie信息】

    SpringMVC.md 三、常用注解 6. CookieValue

  • @RequestBody

    SpringMVC.md 三、常用注解 3. RequestBody

  • @RequestAttribute:获取请求域中的数据

  • @MatrixVariable

    几种传参方式:

       	1. 请求参数(查询字符串):/cars/{path}?xxx=xxx&aaa=ccc【这种方式通过@RequestParam获取传递的参数】
        	2. 矩阵变量:/cars/{path;xxx=xxx;aaa=ccc}/{path2;kkk=zzz;bbb=ddd}
    

    矩阵变量的应用场景:页面开发中cookie禁用了,session里面的内容怎么使用?

    ​ cookie禁用前:比如我们在session中放了自己的内容(session.set(a,b)),每个session都有自己的

    ​ jsessionid,jsessionid是保存在cookie中的,每次发请求时浏览器会携带cookie,服务

    ​ 器拿到浏览器携带的cookie,取出jsessionid,根据jsessionid的值就可以拿到对应的

    ​ session,从而调用session的get方法拿到指定的内容。

    ​ cookie禁用后:浏览器发送请求就不会携带cookie,这样服务器就取不到cookie中的jsessionid,也就拿

    ​ 不到对应的session,也就拿不到session中的内容。

    ​ 我们可以通过url重写的方式解决页面cookie禁用的问题。

    ​ url重写:把cookie的值以矩阵变量【如果使用请求参数的方式进行传递,cookie的值就没法跟普

    ​ 通的参数进行区分了】的方式进行传递。

@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进行解析的,UrlPathHelper中有个属性
    //              removeSemicolonContent(移除分号内容)是用来支持矩阵变量,这个属性的值默认	  	  //		     是true,设置为false就是开启对矩阵变量的支持。
    //3、矩阵变量必须有url路径变量才能被解析(@GetMapping("/cars/{path}")中必须要有路径变量的存在,入参中才能获取矩阵变量)
    @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
    //当路径变量有多个时,如果每个路径变量下都有矩阵变量,需要通过pathVar = "bossId"属性进行指定要获取的是哪个路径变量下的矩阵变量。
    @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;

    }

}

开启矩阵变量的功能:

这里引用官方提出的自定义配置的方式:

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

来自七、web场景 1. SpringMVC自动配置概览

  • 编写一个配置类,通过@Bean注解向容器中增加一个WebMvcConfigurer类型的组件,实现configurePathMatch方法【JDK 8及以后接口是有默认实现的,仅实现需要的方法即可】

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
    
  • 编写一个继承了WebMvcConfigurer接口的配置类,实现configurePathMatch方法

2.5.2.2 Servlet API

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);
	}
2.5.2.3 复杂参数

MapModel(我们放在Map、Model里面的数据会被放在request的请求域中,相当于调用 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(Servlet API中的原生response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

//Map<String,Object> map,  Model model, HttpServletRequest request 都可以给request域中放数据。
@GetMapping("/params")
    public String testParam(Map<String, Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){

        map.put("hello", "world666");
        model.addAttribute("world", "hello666");
        request.setAttribute("message", "HelloWorld");

        Cookie cookie = new Cookie("c1", "v1");
        response.addCookie(cookie);

        return "forward:/success";
    }

@ResponseBody
    @GetMapping("/success")
    public Map<String, Object> success(HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        //跳转到的请求可以通过request.getAttribute()获取请求域中的数据。
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");

        map.put("hello", hello);
        map.put("world", world);
        map.put("message", message);

        return map;
    }

Map、Model类型的参数,会使用MapMathodProcessor(Map类型)、ModelMathodProcessor(Model类型)作为参数解析器,会返回 mavContainer.getModel()【BindingAwareModelMap(是Model 也是Map)】作为解析结果。

mavContainer中包含了view视图信息和model模型数据

2.5.2.4 自定义对象参数

SpringMVC将页面传过来的参数自动封装到自定义对象中。

可以自动类型转换与格式化,可以级联封装。

/**
 *     姓名: <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

2.5.3 POJO(自定义对象)封装过程

  • ServletModelAttributeMethodProcessor

2.5.4 参数处理原理

这一块用于说明SpringBoot底层是如何将客户端传递过来的参数按照我们在Controller方法中标注的注解进行解析得到要传递给方法的实参,并进行方法的调用,调用的同时将解析出的实参传递给方法。

参数处理的总流程如下:

处理用户请求的入口:DispatchServlet

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个处理器适配器(HandlerAdapter); RequestMappingHandlerAdapter
  • 处理器适配器执行目标方法并确定方法参数的每一个值
2.5.4.1 HandlerAdapter

不同的HandlerAdapter用于完成不同的功能:

0 - 支持标注了@RequestMapping注解的Controller方法(Handler)

1 - 支持函数式编程方式实现的Controller方法(Handler)

xxxxxx

2.5.4.2 执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch方法中的代码
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法


//真正执行目标方法(Controller中处理请求的方法)
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//确定方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

2.5.4.3 参数解析器

HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么【确定方法的参数值

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

  • 首先判断当前解析器是否支持解析传入的参数(MethodParameter)
  • 支持就调用 resolveArgument方法
2.5.4.4 返回值处理器

决定了目标方法的返回值可以写哪些类型。

2.5.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;
	}
2.5.4.5.1 挨个判断所有参数解析器哪个支持解析当前参数
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
2.5.4.5.2 解析当前参数的值

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

2.5.4.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
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (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.Optional
			if (!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 model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

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

WebDataBinder :web数据绑定器,用于将请求参数的值绑定到指定的JavaBean(就是上面的attribute)里面

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型,然后利用反射将这些数据绑定到target的Person对象的属性中


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

byte -- > file

@FunctionalInterface**public interface **Converter<S, T>

未来我们可以给WebDataBinder里面放自己的Converter;

**private static final class **StringToNumber<T **extends **Number> **implements **Converter<String, T>

2.5.4.5.4 自定义 Converter的应用场景

​ 通过表单提交数据时,改变原来Person类中pet属性的提交方式,不使用SpringMVC默认的级联封装,通过“小狗狗,1”这种方式进行提交,同时提交后还能成功的封装到JavaBean中,这里就可以通过自定义Converter的方式进行解决。这种问题可以抽象为String类型与自定义对象类型的转换问题。

​ 自定义一个配置类,向容器中放一个WebMvcConfigurer组件,并且实现WebMvcConfigurer中的addFormatters方法,用于添加Converter和Formatter

@Configuration
public class MyConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {
                        //String source:前端表单传过来的参数值,这里就是“小狗狗,1”
                        if (!StringUtils.isEmpty(source)){
                            String[] split = source.split(",");
                            Pet pet = new Pet();
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
}
2.5.4.6 目标方法执行完成

将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

2.5.4.7 处理派发结果

派发结果是指要去哪个页面

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

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

InternalResourceView:
@Override
	protected 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);
			}
		});
	}

2.6 数据响应与内容协商

响应页面:发送请求跳转到指定页面,在后面的视图解析章节中进行说明

响应页面常用于开发单体应用,响应数据常用于开发前后端分离项目

2.6.1 SpringMVC是如何响应JSON的

2.6.1.1 jackson.jar+@ResponseBody
  1. pom中引入依赖
        <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场景引入了如下处理json的相关依赖

  1. 方法上标注@Responsebody,给前端自动返回json数据

接下来看SpringMVC相应JSON的原理

2.6.1.1.1 返回值处理器

SpringMVC在底层对Controller中方法返回值的解析使用的是返回值处理器

返回值处理器是如何处理返回值的

try {
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
	@Override
	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);
	}

RequestResponseBodyMethodProcessor  	
@Override
	public 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.6.1.1.2 返回值处理器原理

  • 1、返回值处理器通过supportsReturnType方法判断是否支持这种类型返回值

  • 2、如果支持,返回值处理器调用 handleReturnValue方法 进行处理

  • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的返回值。

      1. 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商(浏览器默认会以请求头【Accpet项】的方式告诉服务器自己能接受什么样的内容类型),其中,q=0.9、q=0.8是指权重,优先接收权重值大的内容类型。

      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,

      • 3、一旦决定了使用什么样的内容类型数据,SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?

        • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
2.6.1.2 SpringMVC Controller中的方法到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable【返回值是不是异步的】
DeferredResult【被SpringMVC包装过的异步返回方式】
ListenableFuture【被SpringMVC包装过的异步返回方式】
CompletionStage【被SpringMVC包装过的异步返回方式】
WebAsyncTask
@ModelAttribute 【方法标注了@ModelAttribute并且返回值为对象类型】
@ResponseBody 【方法标注了@ResponseBody】 ---> RequestResponseBodyMethodProcessor;
2.6.1.3 HTTPMessageConverter原理

响应就是canWrite,请求就是canRead

2.6.1.3.1 MessageConverter规范

HttpMessageConverter的作用: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

例子:Person对象转为JSON。或者 JSON转为Person

2.6.1.3.2 默认的MessageConverter

每一种MessageConverter的功能是不同的。

0 - 只支持返回值为Byte类型的

1 - String

2 - String

3 - Resource

4 - ResourceRegion

5 - DOMSource.class \ SAXSource.class) \ StAXSource.class **StreamSource.class **Source.class

**6 - **MultiValueMap

7 - **true **

8 - true

9 - 支持注解方式xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的),写到response里面去了

2.6.2 内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

有这么一个需求,不同的客户端(浏览器、安卓)发送请求返回的内容类型不同,比如返回给浏览器JSON,返回给安卓xml

2.6.2.1 引入xml依赖
<!--jackson支持xml的模块-->
<dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2.6.2.2 postman分别测试返回json和xml

只需要改变请求头中Accept字段。这个字段是Http协议中规定的,告诉服务器本客户端可以接收的数据类型。

2.6.2.3 开启浏览器参数方式内容协商功能

为了方便浏览器进行内容协商,开启基于请求参数的内容协商功能。【默认是根据请求头Accept的内容进行内容协商的,这种方式对于浏览器我们没法自定义Accept的值】

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求: http://localhost:8080/test/person?format=json

http://localhost:8080/test/person?format=xml

通过format参数的值确定浏览器可以接收的媒体类型

开启请求参数内容协商模式后,内容协商管理器会增加一个策略(strategy),这个策略中的mediaTypes属性包含了请求参数中的format可以写哪些值。

确定客户端接收什么样的内容类型;

1、Parameter策略优先,在这里就是确定是要返回json数据(获取请求头中的format的值),内容协商管理器中只要有一个策略的媒体类型返回值不是"*/*",就会直接将这个策略的返回值返回,如果遍历了所有的策略都是“*/*”,才会返回“*/*”。

2、最终进行内容协商返回给客户端json即可。

2.6.2.4 内容协商原理
  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType【经过拦截器可能会确定媒体类型的值】

  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【这里得到了application/xml】

    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略来获取能接收的内容类型

  • 3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)【第一次使用MessageConverter,用于统计当前系统支持操作目标对象(Person)的MessageConverter所支持的媒体类型】

  • 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。【result:当前系统对于当次请求支持返回的媒体类型】

  • 5、客户端需要【application/xml】。服务端能力【10种,包括json、xml】

  • 6、进行内容协商的最佳匹配媒体类型【selectedMediaType】

  • 7、用支持将对象转为最佳匹配媒体类型 的MessageConverter。调用它进行转化 。

导入了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()));
		}
2.6.2.5 自定义 MessageConverter
2.6.2.5.1 内容协商的流程

0、Controller中的方法标注@ResponseBody后,表示要响应数据出去,SpringMVC底层会调用 **RequestResponseBodyMethodProcessor **返回值处理器处理Controller中方法的返回值

1、Processor 处理方法返回值。通过 **MessageConverter **处理

2、所有 **MessageConverter **合起来可以支持各种媒体类型数据的操作(读、写)

3、内容协商找到最终的 messageConverter

2.6.2.5.2 应用场景

实现多协议数据兼容。json、xml、x-guigu

需求描述如下:

1、浏览器发请求直接返回xmL [application/xml] jacksonXmlConverter
2、如果是ajax请求返回json [application/json] jacksonJsonConverter
3、如果硅谷app发请求,返回自定义协议数据 [appliaction/x-guigu] xxxxConverter
​ 自定义协议数据格式:属性值1;属性值2;
思路:
1、添加自定义的MessageConverter进系统底层
2、系统底层就会统计出所有MessageConverter能操作哪些类型
3、客户端内容协商

自定义配置SpringMVC的功能,通过给容器中添加一个 WebMvcConfigurer 实现,这个WebMvcConfigurer可以通过配置类+@Bean的方式放入容器,也可以通过配置类+配置类实现WebMvcConfigurer接口的方式放入容器。

 @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            //extendMessageConverters与configureMessageConverters的区别:
          	//extendMessageConverters:在SpringMVC底层增加的MessageConverter基础上增加自己的				 MessageConverter。
          	//configureMessageConverters:完全自定义MessageConverters,会将底层的MessageConverter			  覆盖掉。
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

            }
        }
    }

实现步骤:

  1. 创建自定义的MessageConverter,实现HttpMessageConverter接口,泛型指的是原类型,对于读操作来说,就是支持将什么类型的数据转换为指定媒体类型的数据,前者就是泛型。

    public class GuiguConverter implements HttpMessageConverter<Person> {
        @Override
        public boolean canRead(Class<?> clazz, MediaType mediaType) {
            return false;
        }
    
        @Override
        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
            return true;
        }
    
        @Override
        public List<MediaType> getSupportedMediaTypes() {
            return MediaType.parseMediaTypes("application/x-guigu");
        }
    
        @Override
        public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return null;
        }
    
        @Override
        public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            String s = person.getAge() + ";" + person.getName();
            OutputStream body = outputMessage.getBody();
            body.write(s.getBytes());
    
        }
    }
    
  2. 将自定义的MessageConverter加入到容器中

    public class MyConfig {
    
        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {
                @Override
                public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                    converters.add(new GuiguConverter());
                }
            };
        }
    }
    

内容协商可以实现一个请求适配多种内容类型

MessageConverter中的canRead方法是对于注解@RequestBody来说的,注解@RequestBody标注在Controller中方法的参数上,canRead方法的读逻辑是,JSON、xml或者自定义数据传到了Controller的方法这,由canRead方法判断能否将传过来的数据类型转换为指定类型,从而将指定类型的数据赋值给当前Controller方法的参数。

以请求参数的方式完成内容协商

我得往ParameterContentNegotiationStrategy的mediaTypes中放一个映射 “guigu -----》application/x-guigu”,这样才能实现以请求参数的方式完成内容协商,但是又没法直接拿到内容协商管理器中的参数内容协商策略,所以只能先拿到内容协商管理器,然后自定义内容协商管理器的strategies属性。

这里依然通过WebMvcConfigurer 进行实现,重写configureContentNegotiation方法即可

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    List<ContentNegotiationStrategy> strategies = new ArrayList<>();
    Map<String, MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json", MediaType.APPLICATION_JSON);
    mediaTypes.put("xml", MediaType.APPLICATION_XML);
    mediaTypes.put("guigu", MediaType.parseMediaType("application/x-guigu"));
    strategies.add(new ParameterContentNegotiationStrategy(mediaTypes));
    strategies.add(new HeaderContentNegotiationStrategy());
    configurer.strategies(strategies);
}

有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效(就像这里自定义ContentNegotiationManager的strategies属性,自定义后,原有的配置全都被覆盖了,这种情况下就可能会影响到原有的功能)。

大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】

2.6.2.6 总结

浏览器通过两种方式同服务器进行内容协商

  1. 请求头的Accept项

  2. 请求参数方式【默认参数名为format】

2.7 视图解析与模板引擎

视图解析:SpringBoot处理完请求后跳转到页面的过程。

SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染、页面跳转。

JSP实际上也是一种模板引擎,但是SpringBoot不支持,SpringBoot默认的打包方式是jar包,jar包是压缩包,JSP不支持在压缩包内编译的方式,所以SpringBoot默认不支持 JSP。

SpringBoot支持的第三方模板引擎技术:

  • freemarker(spring-boot-starter-freemarker)
  • groovy-templates (spring-boot-starter-groovy-templates)
  • thymeleaf (spring-boot-starter-thymeleaf)

2.7.1 视图解析

2.7.1.1 视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

2、如果Controller中方法的参数是一个自定义类型对象(这个自定义类型对象的值是从请求参数中确定的),会先将这个对象放到Model中,后面会将Model放在 ModelAndViewContainer 中

3、任何目标方法执行完成以后都会返回 ModelAndView(包含了数据和视图地址)。

4、processDispatchResult 处理派发结果(决定页面该如何响应)

  • 1、render(mv, request, response); 进行页面渲染逻辑

    • 1、根据方法的String返回值得到 **View **对象【View对象的render方法定义了页面的渲染逻辑】
      • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象

      • 2、得到了 redirect:/main.html --> ThymeleafViewResolver new RedirectView()

      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到 View 对象,View对象用于进行内容匹配。

        BeanNameViewResolver:如果IOC容器中存在以视图名命名的组件,就用该解析器进行解析。

      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render方法进行页面渲染工作【Ctrl + F12弹出如下界面】,render决定了如何进行页面相应。

        • RedirectView 如何渲染【重定向到一个页面】
        • 1、获取目标url地址
        • 2、****response.sendRedirect(encodedURL);

视图解析:

    • **返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发****request.getRequestDispatcher(path).forward(request, response); **

    • **返回值以 ****redirect: 开始: ****new RedirectView() --》 render就是重定向 **

    • **返回值是普通字符串: new ThymeleafView()---> **

      自定义视图解析器+自定义视图; 大厂学院。

2.7.2 模板引擎-Thymeleaf

2.7.2.1 Thymeleaf简介

Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.

现代化、服务端Java模板引擎

Thymeleaf不适用的场景:支持页面跳转的高并发应用,这种应用应该用前后端分离来做,或者是高并发的后台管理系统,也不适合用Thymeleaf来做,简单的单体应用是可以使用Thymeleaf来做的。

Thymeleaf不是一个高性能的模板引擎。

官方文档:https://www.thymeleaf.org/documentation.html

2.7.2.2 Thymeleaf基本语法

Spring Boot 1.md 四、Spring Boot与Web开发 3.5 Thymeleaf语法

2.7.2.3 Thymeleaf使用

2.7.2.3.1 引入starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
2.7.2.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 ** 【Thymeleaf与Spring整合的主引擎】
  • 3、配好了 ThymeleafViewResolver
  • 4、我们只需要直接开发页面
	public static final String DEFAULT_PREFIX = "classpath:/templates/";
	public static final String DEFAULT_SUFFIX = ".html";  //xxx.html
2.7.2.3.3 页面开发
<!DOCTYPE html>
<!--xmlns:th="http://www.thymeleaf.org"   Thymeleaf的名称空间,不加这个Thymeleaf的标签也能用,但是没有标签提示-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- thymleaf的标签有个好处是会有默认值,比如这里的哈哈,如果经过后端,会取出msg的值显示出来,如果不经过后端,直接单独访问当前这个页面,就会显示哈哈-->
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.atguigu.com" th:href="${link}">去百度</a>  <br/>
    <!-- @{link} 不会从请求域中取值,${link}才会-->
    <a href="www.atguigu.com" th:href="@{link}">去百度2</a>
</h2>
</body>
</html>

关于项目的访问路径(假如指定server.servlet.context-path=/world)

那么以后所有的请求都必须以/world开始

2.7.2.4 构建后台管理系统
2.7.2.4.1 项目创建

thymeleaf、web-starter、devtools、lombok

2.7.2.4.2 静态资源处理

自动配置好,我们只需要把所有静态资源放到 static 文件夹下

2.7.2.4.3 路径构建

th:action="@{/login}"

2.7.2.4.4 模板抽取

th:insert/replace/include

2.7.2.4.5 页面跳转
    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){

        if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登陆成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html;  重定向防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账号密码错误");
            //回到登录页面
            return "login";
        }

    }
2.7.2.4.6 数据渲染
    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("lisi", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe ", "aaddd"));
        model.addAttribute("users",users);

        return "table/dynamic_table";
    }
        <table class="display table table-bordered" id="hidden-table-info">
        <thead>
        <tr>
            <th>#</th>
            <th>用户名</th>
            <th>密码</th>
        </tr>
        </thead>
        <tbody>
        <tr class="gradeX" th:each="user,stats:${users}">
            <td th:text="${stats.count}">Trident</td>
            <td th:text="${user.userName}">Internet</td>
            <td >[[${user.password}]]</td>
        </tr>
        </tbody>
        </table>

解决Post方式表单重复提交的问题:重定向

拦截器/过滤器的应用场景:每个方法都需要加相同的判断时,比如登录后才能访问系统内的其他页面

thymleaf的行内写法应用场景:适用于没有被标签包裹的文本,同时这个文本又是动态获取的。

遗留问题1:

当Controller中的方法如下时,访问localhost:8080也会来到这个请求,localhost:8080和localhost:8080/是一样的吗

thymleaf模板引擎要解析,在html页面中必须要有thymleaf的名称空间(要通过模板引擎跳转到哪个页面,哪个页面就必须要有thymleaf的名称空间)?

2.8 拦截器

拦截器的应用场景:

Controller中的多个方法需要相同的拦截逻辑时,考虑使用拦截器。比如登录检查,只有用户登录后才有权访问系统内的各个页面。

2.8.1 HandlerInterceptor 接口

/**
 * 登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2、把这些配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

2.8.2 配置拦截器

/**
 * 使用拦截器的步骤:
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(通过实现WebMvcConfigurer的addInterceptors方法的方式)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); 
      		    //另一种方法可以通过配置spring.mvc.static-path-pattern=XXX静态资源访问路径,这样在配置拦截器时只需要指定不拦截该静态资源访问路径下的请求就可以过滤掉所有的静态资源请求。
      //放行的请求
    }
}

2.8.3 拦截器原理

1、根据当前请求,找到HandlerExecutionChain【包含了可以处理当前请求的handler以及handler的所有拦截器】

2、先来**顺序执行 **所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器prehandler返回为false。倒序执行所有已经执行了的拦截器【不包括当前返回false的拦截器】的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出,不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

**6、前面的步骤有任何异常都会直接倒序触发已经执行了的拦截器的 **afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

2.9 文件上传

2.9.1 页面表单

<!--文件上传表单要求必须 method="post" enctype="multipart/form-data"-->
<form method="post" enctype="multipart/form-data" action="/upload">
    单文件上传:<input type="file" name="onefile">
    多文件上传:<input type="file" name="files" multiple>
    <input type="submit">
</form>

2.9.2 文件上传代码

通过MultipartFile封装文件上传项即可。

    /**
     * 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";
    }

application.properties/yml中的相关配置项

设置单文件上传最大大小:spring.servlet.multipart.max-file-size=10MB

设置单个请求上传的文件总大小:spring.servlet.multipart.max-request-size=100MB

2.9.3 自动配置原理

分析功能的原理的思路

  1. SpringBoot有没有就该功能做自动配置
  2. 调试源码,看功能真正是怎么实现的。

SpringBoot对文件上传这个功能做了自动配置,在MultipartAutoConfiguration中。

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】 (只能处理Servlet方式上传的文件,如果是自定义上传的文件流,需要自定义文件上传解析器来进行处理)

  • 原理步骤

    • 1、请求进来使用文件上传解析器判断(isMultipart(),判断的方法是通过请求头的Content-Type项是否以“multipart/”开头)并封装(resolveMultipart(),返回 StandardMultipartHttpServletRequest ---》 MultipartHttpServletRequest)文件上传请求 【一句话总结,文件上传请求进来之后首先封装为StandardMultipartHttpServletRequest】

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

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

FileCopyUtils。实现文件流的拷贝,Spring的工具类

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

2.10 异常处理

2.10.1 错误处理

2.10.1.1 默认规则

来自官方文档:

  • 默认情况下,Spring Boot提供/error处理所有错误的映射【一旦有错误会转到/error】

  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。【json中有的数据都是可以取出来的】

  • 对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。

  • 要对其进行自定义,添加**View**解析为**error**``****

  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

  • error/下的4xx,5xx页面会被自动解析;

2.10.1.2 定制错误处理逻辑
  • 自定义错误页

    • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的。方法返回值为String(视图地址)或ModelAndView。【推荐通过这种方式进行处理】

  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error请求。

  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。

    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());

      该方法的作用是:此次请求立即结束,Tomcat服务器底层会转出一个/error请求给DispatcherServlet,如果该请求框架无法处理Tomcat就会相应一个Tomcat最原生的错误页(蓝白配色风格的错误页)

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则【优先级需要调到最高】

  • ErrorViewResolver 实现自定义处理异常;

    • response.sendError 。error请求就会转给basicErrorController
    • 你的异常没有任何异常解析器能处理。tomcat底层 response.sendError。error请求就会转给basicErrorController
    • basicErrorController 要去的页面地址是 ErrorViewResolver 解析的
2.10.1.3 异常处理自动配置原理
  • ErrorMvcAutoConfiguration 自动配置异常处理规则

    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes 【未指定id的情况下默认以方法名作为id】
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver

        DefaultErrorAttributes 用于处理处理器期间发生的异常。

      • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。

    • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
      • 处理默认 ****/error 路径的请求;页面响应 ****new ModelAndView("error", model);

      • 容器中有组件 View->id是error;(响应默认错误页)

      • 容器中放组件 BeanNameViewResolver(视图解析器):按照返回的视图名(error)作为组件的id去容器中找View对象。 BeanNameViewResolver是跟View配合起来的,由视图解析器解析得到视图,找到的视图就是最终的渲染结果。

      • 就是因为有BasicErrorController,才使得对于浏览器返回错误白页,对于其他客户端可以返回JSON

        以下是返回JSON的代码

        以下是返回错误白页的代码

    • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 视图解析器(ViewResolver)的作用:解析得到视图对象
      • 如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html

如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)

自定义错误页可以通过自定义视图的方式实现,视图名是error

2.10.1.4 异常处理步骤流程

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

2、进入视图解析流程(页面渲染?)

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

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

  • 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【****HandlerExceptionResolver处理器异常解析器】

  • 2、系统默认的异常解析器;

    • 1、DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null;

    • 2、默认没有任何异常解析器能处理异常,所以异常会被抛出

      • 1、如果没有任何异常解析器能处理,最终底层就会发送 /error 请求。会被底层的BasicErrorController处理 【Servlet规范约定的】
      • 2、BasicErrorController来解析错误视图,解析方法为遍历所有的 ErrorViewResolver 看谁能解析产生视图。
      • **3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html ** ,如果以状态码命名的视图名没有在静态资源路径下找到对应的html,就去静态资源下找4XX或5XX的错误页面,如果也没找到,DefaultErrorViewResolver 会直接创建一个ModelAndView,其中,视图名为error,model为此次报错存储的model数据。

      • **4、模板引擎最终响应这个页面 error/500.html **

标签:web,SpringBoot,自定义,request,2x,public,String,class,请求
From: https://www.cnblogs.com/wzzzj/p/18039185

相关文章

  • SpringBoot 2x 系列之(五)开发技巧
    开发技巧1.Lombok1.应用场景简化JavaBean的开发帮我们在编译时生成get、set、toString方法2.安装及使用引入依赖【SpringBoot已经做了版本仲裁】<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></depende......
  • SpringBoot 2x 系列之(四)自动配置
    自动配置1.引导加载自动配置类见SpringBoot1.md一、SpringBoot入门4.2主程序类,主入口类【自动配置原理】1.1@SpringBootConfiguration见SpringBoot1.md一、SpringBoot入门4.2主程序类,主入口类【自动配置原理】[email protected]@AutoConfi......
  • SpringBoot 2x 系列之(三)容器功能
    容器功能1.组件添加1.1@Configuration结合@Bean注册组件@Configuration见[email protected]@Component、@Controller、@Service、@Repository见Spring.md相关内容1.3@ComponentScan见[email protected]@Import见Spring.md8.......
  • SpringBoot 2x 系列之(二)SpringBoot特点
    SpringBoot特点1.依赖管理特性SpringBoot已经为我们做了版本仲裁,那么如果我们要修改已经做了版本仲裁的依赖,比如mysql驱动的依赖,该怎么做呢?查看spring-boot-dependencies里面规定的当前依赖版本用的properties属性在当前项目里面重新配置<properties><mysql.versi......
  • SpringBoot 2x 系列之(一)基础入门
    基础入门课程规划1.时代背景响应式编程解决的问题:如何使用少量资源编写一个极高吞吐量、能承担大并发的应用响应式应用:占用少量的资源(线程)处理大量的并发springboot:整合spring整个生态圈的一站式框架​ 高层框架,底层是SpringFrameworkSpring:1)从微观角度说Spring指Spri......
  • SpringBoot 1x 系列之(十一)Spring Boot与任务
    SpringBoot与任务异步任务、定时任务、邮件任务1.异步任务1.1应用场景执行一些操作(如:邮件任务等)不想阻塞当前线程的情况下,可以通过多线程的方式进行异步处理。1.2快速使用主配置类//开启@Async异步注解功能@EnableAsync@EnableRabbit@EnableCaching@MapperScan("co......
  • SpringBoot 1x 系列之(十)Spring Boot与检索
    SpringBoot与检索ElasticSearch1.ElasticSearch简介1)Java语言编写的开源全文搜索引擎。2)用于快速的存储、搜索和分析海量数据。3)是一个分布式搜索服务。4)提供RestfulAPI,通过发送请求的方式就可以将ElasticSearch用起来。5)底层基于Lucene(开源的搜索引擎软件工具包)2.Doc......
  • SpringBoot 1x 系列之(九)Spring Boot与消息
    SpringBoot与消息JMS、AMQP、RabbitMQ1.概述消息服务的两个常见规范(消息代理规范):JMS、AMQPJMS(JavaMessageService)JAVA消息服务:​ 基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现AMQP(AdvancedMessageQueuingProtocol)高级消息队列协议​ 也是一个消息代理的规范......
  • SpringBoot 1x 系列之(八)Spring Boot与缓存
    SpringBoot与缓存JSR-107、Spring缓存抽象、整合Redis缓存:加速系统访问,提升系统性能热点数据、临时数据(如验证码)1.JSR-1071.1背景统一缓存的开发规范及提升系统的扩展性,J2EE发布了JSR-107缓存规范1.2JSR107简介CacheManager与Cache的关系,类比连接池与连接涉及的包ja......
  • SpringBoot 1x 系列之(七)自定义starter
    自定义starterstarters原理、自定义starters如何自定义starter:​ 1、这个场景需要使用到的依赖是什么?​ 2、如何编写自动配置@Configuration//指定这个类是一个配置类@ConditionalOnXXX//在指定条件成立的情况下自动配置类生效@AutoConfigureAfter//指定自动配置类的......