123.【SpringBoot 源码刨析B】

SpringBoot-核心功能

  • (三)、SpringBoot核心功能
    • 1.配置文件
      • 1.1、properties
      • 1.2、yaml
        • (1).yaml 简介
        • (2).yaml 基本语法
        • (3).数据类型
        • (4).示列
      • 1.3、配置提示
    • 2.WEB 开发
      • 1.SpringMVC自动配置概览
      • 2.简单功能分析
        • (1).静态资源访问
          • (1.1).静态资源目录
          • (1.2).修改静态资源访问前缀
          • (1.3).修改静态资源默认指定目录
          • (1.4).支持webjars
        • (2).欢迎页支持
        • (3).静态资源配置原理 ⭐
          • (1.1)、配置类只有一个有参构造器
          • (1.2)、资源处理的默认规则
          • (1.3)、静态页的欢迎规则
      • 3.请求参数处理
        • (1).请求映射
          • (1.1)、rest使用与原理
          • (1.2)、修改默认的_method更改为自定义的参数
          • (1.3)、请求映射原理
        • (2).普通参数与基本注解
          • (1.1)、注解:
          • (1.2)、Servlet API
          • (1.5)、自定义对象参数
        • (3).POJO封装过程
          • (1.4)、复杂参数
        • (4).参数处理原理
          • (1.1)、HandlerAdapter
          • (1.2)、执行目标方法
          • (1.3)、参数解析器-`HandlerMethodArgumentResolver`
          • (1.4)、返回值处理器
        • (5).如何确定目标方法每一个参数的值
          • (1.1)、挨个判断所有参数解析器那个支持解析这个参数
          • (1.2)、解析这个参数的值
          • (1.3)、自定义类型参数 封装POJO
        • (6).目标方法执行完成
        • (7).处理派发结果
      • 4.数据响应与内容协商
        • (1).响应JSON
          • (1.1)jackson.jar+@ResponseBody
            • (1.1.1)、返回值解析器
            • (1.1.2)、返回值解析器原理
          • (1.2).SpringMVC到底支持哪些返回值
          • (1.3)/HTTPMessageConverter原理
            • (1.3.1)、MessageConverter规范
            • (1.3.2)、默认的MessageConverter
        • (2).内容协商 (MessageConverter)
          • (2.1)、引入XML文件
          • (2.2)、postman分别测试返回json和xml
          • (2.3)、基于请求参数的内容协商 ⭐⭐
          • (2.4)、内容协商原理
          • (2.5)、自定义 MessageConverter
          • (2.6)、运用参数的方式请求自定义内容协商
        • 5.视图解析与模板引擎
          • (1).视图解析
            • (1.1)、视图解析原理流程
          • (2).Thymeleaf基本语法
            • (2.1)、表达式
            • (2.2)、字面量
            • (2.3)、文本操作
            • (2.4)、数学运算
            • (2.5)、布尔运算
            • (2.6)、比较运算
            • (2.7)、条件运算
            • (2.8)、特殊操作
            • (2.9)、设置属性值-th:attr
            • (2.10)、迭代
            • (2.11)、条件运算
          • (3).Thymeleaf的使用
            • (3.1)、引入依赖
            • (3.2)、自动配置好了thymeleaf
            • (3.2)、页面开发
          • (4).后台管理系统总结
          • (4.1)、举列子(公共方)

(三)、SpringBoot核心功能

CTRL+H 打开的是 继承树

CTRL+F12 打开的是 方法结构

在这里插入图片描述

1.配置文件

1.1、properties

同以前的properties用法

1.2、yaml

(1).yaml 简介

YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。

非常适合用来做以数据为中心的配置文件

(2).yaml 基本语法

  • key: value;kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • '#'表示注释
  • 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义 (单引号会转义特殊字符、双引号不会转义特殊字符。比如 \n ,单引号输出的是\n,双引号会换行)。 这里的转义是: 会不会修改原本的功能,转义就是把原有的功能给转义掉。

(3).数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
  • 对象:键值对的集合。map、hash、set、object

行内写法的时候k1与 : 与 v1 都要有间隔,否则会默认讲k1:v1当作键

行内写法:  k: {k1 : v1,k2 : v2,k3 : v3}
#或
k: 
	k1: v1
  k2: v2
  k3: v3
  • 数组:一组按次序排列的值。array、list、queue
行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

假如一个文件中存在 application.yaml和application.properties那么两者都会生效的。

(4).示列

1.宠物类

package com.jsxs.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author Jsxs
 * @Date 2023/7/2 9:06
 * @PackageName:com.jsxs.bean
 * @ClassName: Pet
 * @Description: TODO
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
    private String name;
    private Double weight;
}

2.Person类: 匹配绑定并放入IOC容器中

package com.jsxs.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Author Jsxs
 * @Date 2023/7/2 9:05
 * @PackageName:com.jsxs.bean
 * @ClassName: Person
 * @Description: TODO
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component  // 声明这是IOC容器中的一个组件
@ConfigurationProperties(prefix = "person")  // 指定组件配置文件的前缀
public class Person {
    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
}

3.application.yaml 配置文件

这里在行内写 k:v 的时候,一定要用空格间隔分来。 k 是橙色

person:
  userName : jsxs
  boss : true
  birth : 2023/07/02 09:27:26
  age : 18
  pet :
    name : 哈吉米
    weight : 18.5
  interests : [swiming,basck]
  animal : [cat,dog]
  score : {math : 131,english : 140}
  salarys : [3999,4999.98,5999.99]
  allPets :
     jsxs : [{name : tom,weight : 15.3}]

4.测试

package com.jsxs.controller;

import com.jsxs.bean.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

/**
 * @Author Jsxs
 * @Date 2023/7/2 9:37
 * @PackageName:com.jsxs.controller
 * @ClassName: IndexController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
@ResponseBody
public class IndexController {

    @Resource
    Person person;

    @GetMapping("/person")
    public Person index(){
        return person;
    }
}

在这里插入图片描述

1.3、配置提示

自定义的类和配置文件绑定一般没有提示。

加入下面的依赖和配置之后,我们配置自定义的类时会有提示。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

2.WEB 开发

DispathServletSpringMvc

1. 先获得 url 请求的路径 ()
2. 获得 url 的那种解析器(5)
3. 获得方法体参数的类型和个数
4. 遍历获得的参数 获得方法体参数的具体解析器 (27)
5. 通过解析器开始解析方法体参数。
6. 再判断参数体中是否由注解,后面会给我们创建一个实列对象
7. 最后给我们创建的空实列对象进行赋值

在这里插入图片描述
SpringBoot官方 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.
    • 自动注册 ConverterGenericConverter Formatter
  • Support for HttpMessageConverters (covered later in this document).
    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Automatic registration of MessageCodesResolver (covered later in this document).
    • 自动注册 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.简单功能分析

(1).静态资源访问

(1.1).静态资源目录

1.静态页面路径和静态资源不冲突

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

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

在这里插入图片描述

  1. 静态页面与静态资源冲突 (同名)

原理: 静态映射/**。 (即 localhost:8080/**)

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

控制层存在一个 /1.jpg的页面跳转,static下面存在着一张图片叫做 1.jpg

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author Jsxs
 * @Date 2023/7/2 10:54
 * @PackageName:com.jsxs.controller
 * @ClassName: HelloController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
@ResponseBody
public class HelloController {

    @GetMapping("/1.jpg")
    public String hello(){
        return "aaa";
    }
}

在这里插入图片描述
1.静态资源默认访问的位置是 /** 即(localhost:8080/**)

spring:
  mvc:
    static-path-pattern: /**
(1.2).修改静态资源访问前缀

1.正常业务的开发中,我们需要对静态元加上前缀(主要目的是为了实现静态资源的拦截,这个前缀通常为控制层类的前缀)

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

http://localhost:8080/res/2.jpg

在这里插入图片描述

(1.3).修改静态资源默认指定目录

静态资源存放的默认路径有四个,实际开发中我们可能需要自定义指定我们的静态资源访问路径放哪里。

spring:
  mvc:
    static-path-pattern: /res/**
  web:
    resources:
    # 这里的路径我们可以指定一个或多个,多个用[ ]括起来。
      static-locations: classpath:/haha

查看static-locations的源码发现是一个数组
在这里插入图片描述

http://localhost:8080/res/2.jpg

在这里插入图片描述
把静态资源放在指定的目录下,才会被查找的到。
在这里插入图片描述

(1.4).支持webjars

通俗的讲就是讲 css js 等一些资源封装成了 Maven或者jar包

自动映射: webjars/**

webjars官网: https://www.webjars.org/

在这里插入图片描述

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>

访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

在这里插入图片描述

(2).欢迎页支持

Spring Boot supports both static and templated welcome pages. It first looks for an index.html file in the configured static content locations. If one is not found, it then looks for an index template. If either is found, it is automatically used as the welcome page of the application.

支持两种: 第一种(任意)静态资源目录目录下放index.html 第二种Controller下进行处理请求。

    1. 静态资源路径下 index.html
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
spring:
#  mvc:
#    static-path-pattern: /res/**
  web:
    resources:
      static-locations: classpath:/haha

在这里插入图片描述

    1. controller能处理/index

(3).静态资源配置原理 ⭐

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 给容器中配了什么。
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和xxx进行了绑定。
    • WebMvcProperties==spring.mvc、
    • ResourceProperties==spring.web.resources
(1.1)、配置类只有一个有参构造器
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnEnabledResourceChain
    static class ResourceChainCustomizerConfiguration {
        ResourceChainCustomizerConfiguration() {
        }

        @Bean
        WebMvcAutoConfiguration.ResourceChainResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer(ResourceProperties resourceProperties, WebProperties webProperties) {
            return new WebMvcAutoConfiguration.ResourceChainResourceHandlerRegistrationCustomizer((Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources()));
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @EnableConfigurationProperties({WebProperties.class})
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
        private final Resources resourceProperties;
        private final WebMvcProperties mvcProperties;
        private final WebProperties webProperties;
        private final ListableBeanFactory beanFactory;
        private final WebMvcRegistrations mvcRegistrations;
        private final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
        private ResourceLoader resourceLoader;
        
	//有参构造器所有参数的值都会从容器中确定 ⭐⭐⭐
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory ⭐ (容器)
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
        public EnableWebMvcConfiguration(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, WebProperties webProperties, ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ListableBeanFactory beanFactory) {
            this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
            this.mvcProperties = mvcProperties;
            this.webProperties = webProperties;
            this.mvcRegistrations = (WebMvcRegistrations)mvcRegistrationsProvider.getIfUnique();
            this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.beanFactory = beanFactory;
        }
(1.2)、资源处理的默认规则
因为这里分析得到: 假如resourceProperties.isAddMappings()false下面的业务逻辑都不生效,也就是说默认配置的路径都不会生效。
@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));
			}
            
            //
			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/**
  web:
    resources:
#    静态资源目录位置
      static-locations: classpath:/haha
      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;
(1.3)、静态页的欢迎规则
	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");
		}
	}

欢迎页 静态资源路径如果不是 /** 那么欢迎页将不会生效

在这里插入图片描述

3.请求参数处理

(1).请求映射

(1.1)、rest使用与原理
  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
    • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    • 核心Filter;HiddenHttpMethodFilter
      • 用法: 表单method=post,隐藏域 _method=put
      • SpringBoot中手动开启
    • 扩展:如何把_method 这个名字换成我们自己喜欢的。
  1. 前端页面

问题:我们后端有四种风格提交Http的风格,而前端只有两种分别为: post、get。如果我们强制把前端的两种提交方式修改成 DELETE风格和 PUT风格,我们发现在后端运行的时候会默认给我们走成 GET风格。这里的原因是我们需要对SpringMVC源码进行查看。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>你好</h1>
<form action="/user" method="post">
    <button type="submit">Post方式进行跳转</button>
</form>
<form action="/user" method="get">
    <button type="submit">Get方式进行跳转</button>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <button type="submit">DELETE方式进行跳转</button>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT">
    <button type="submit">PUT方式进行跳转</button>
</form>

</body>
</html>

业务控制层:

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author Jsxs
 * @Date 2023/7/2 10:54
 * @PackageName:com.jsxs.controller
 * @ClassName: HelloController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
@ResponseBody
public class HelloController {

    @GetMapping("/1.jpg")
    public String hello(){
        return "aaa";
    }

    @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-张三";
    }
}

在没有配置和遵从SpringMVC源码的情况下,我们是不能实现对多种提交方式进行处理的。
在这里插入图片描述

  1. SpringMvc源码

所有的提交方式源码在这里

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},
        matchIfMissing = false
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();
    }

点进OrderedHiddenHttpMethodFilter

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.web.servlet.filter;

import org.springframework.web.filter.HiddenHttpMethodFilter;

public class OrderedHiddenHttpMethodFilter extendsHiddenHttpMethodFilter implements OrderedFilter {
    public static final int DEFAULT_ORDER = -10000;
    private int order = -10000;

    public OrderedHiddenHttpMethodFilter() {
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

点进 HiddenHttpMethodFilter

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";
  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }

        filterChain.doFilter((ServletRequest)requestToUse, response);
    }
}

得出结论: 假如我们要使用其他的提交方式,首先我们要对 spring.mvc.hiddenmethod.filter.enables 默认的false更改为true。然后我们表单的提交方式要设置为 post 方式进行提交。然后再添加一个隐藏的文本框,将其的name设置成 源码需要的 _methods 并对这个隐藏的文本框进行设置值为 我们想要的提交方式 (DELETEPUT)

在这里插入图片描述

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

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

Rest使用客户端工具

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。
spring:
  mvc:
    # 允许多种方式提交
    hiddenmethod:
      filter:
        enabled: true
  web:
    resources:
#    静态资源目录位置
      static-locations: classpath:/haha
      # 静态资源是否映射?
      add-mappings: true
      cache:
        # 设置静态资源的过期时间
        period: 10

小结:

@RequestMapping(value = "/user",method = RequestMethod.GET) = GetMapping("/user")
@RequestMapping(value = "/user",method = RequestMethod.POST)= PostMapping("/user")
...
(1.2)、修改默认的_method更改为自定义的参数

假如在业务的实际开发中,我们不想使用SpringBoot提供的默认的参数 _methods 作为我们的rest请求映射,我们可以对这个组件进行重写赋值即可。

  1. 查看源码
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }

    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }
}
  1. 开始重写这个源码组件
package com.jsxs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }
}

这里我们对其进行修改了 name的值为 aaaa

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>你好</h1>
<form action="/user" method="post">
    <button type="submit">Post方式进行跳转</button>
</form>
<form action="/user" method="get">
    <button type="submit">Get方式进行跳转</button>
</form>
<form action="/user" method="post">
		⭐⭐⭐
    <input name="aaaa" type="hidden" value="DELETE">
    <button type="submit">DELETE方式进行跳转</button>
</form>
<form action="/user" method="post">
		⭐⭐⭐
    <input name="aaaa" type="hidden" value="PUT">
    <button type="submit">PUT方式进行跳转</button>
</form>

</body>
</html>
  1. 测试运行

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

(1.3)、请求映射原理

CTRL+H 打开的是 继承树

CTRL+F12 打开的是 方法结构

在这里插入图片描述
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()

最重要的是 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);

				// 找到当前的我们处理器要跳转的路径 (/xxx)
		⭐⭐		mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx
  1. 第一个断点我们可以得到我们要请求的路径和方式

在这里插入图片描述

2.第二个断点

我们步入进 mappedHandler = getHandler(processedRequest);

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

在这里插入图片描述
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping 链表看是否有请求信息。
    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

在这里插入图片描述

(2).普通参数与基本注解

(1.1)、注解:

注解通常有三个源码属性。
name="" 接受的名字?
value="" 接受的名字?
name和value因为互相为别名所以两者都一样的作用,用谁都一样
required ="" 是否必须接受到值?

1. 主要用于Rest风格传参方面。
@PathVariable: 假如说方法里面有一个 Map<String,String>的参数,那么SPringBoot会自动帮助我们以键值对的方式进行自动收集里面的数据。
2. 主要用于获取请求头
@RequestHeader: 加入方法里面有一个Map<String,String>的参数,那么所有的请求头都会放进去。
3.获取HttpRequest设置的值(这是一个系列的)
@RequestAttribute: 主要是获取setAttribute(k,v)的值
4.获取非REST风格传参的参数
@RequestParam: 假如方法里面有一个Map<String,String>的参数,那么获取到的参数都会放在这里
5. 矩阵注解
@MatrixVariable: 假如方法里面有一个
6.获取Cookie的值
@CookieValue: 假如方法里面有一个Cookie对象,那么获取到的cookie的名字和值都会放在这里。
7. 获取POST的非REST@RequestBody: 假如是POST请求,那么会获取到POST的非REST的参数值。
  1. 非矩阵注解

非@RequestAttribute的后端代码

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author Jsxs
 * @Date 2023/7/3 16:14
 * @PackageName:com.jsxs.controller
 * @ClassName: ParamterTestController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
@ResponseBody
public class ParameterTestController {

    //    1. @PathVariable 注解 : 获取REST的值

    @GetMapping("/car/{id}/owner/{username}/{df}")
    public Map<String, Object> getCar1(@PathVariable("id") Integer id,
                                       @PathVariable("username") String username,
                                       @PathVariable("df") String df,
                                       @PathVariable Map<String, String> mp) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name", username);
        map.put("df", df);
        map.put("mp", mp);
        return map;
    }

    //    2.@RequestHeader 注解 : 获取请求头的信息

    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar2(@PathVariable("id") Integer id,
                                       @PathVariable("username") String username,
                                       @PathVariable Map<String, String> mp,
                                       @RequestHeader("User-Agent") String userAgent,
                                       @RequestHeader Map<String, String> header) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name", username);
        map.put("mp", mp);
        map.put("userAgent", userAgent);
        map.put("header", header);
        return map;
    }

    //    3. @RequestParam 注解 : 获取GET拼接的提交的值
    @GetMapping("/car/{id}/owner3/{username}")
    public Map<String, Object> getCar3(@PathVariable("id") Integer id,
                                       @PathVariable("username") String username,
                                       @PathVariable Map<String, String> mp,
                                       @RequestHeader("User-Agent") String userAgent,
                                       @RequestHeader Map<String, String> header,
                                       @RequestParam("age") Integer age,
                                       @RequestParam("inters") List<String> inters,
                                       @RequestParam Map<String, String> params
    ) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name", username);
        map.put("mp", mp);

        map.put("userAgent", userAgent);
        map.put("header", header);

        map.put("age", age);
        map.put("inters", inters);
        map.put("params", params);
        return map;
    }

    //      4. @CookieValue :获取指定的cookie值

    @GetMapping("/car/{id}/owner4/{username}")
    public Map<String, Object> getCar4(@PathVariable("id") Integer id,
                                       @PathVariable("username") String username,
                                       @PathVariable Map<String, String> mp,
                                       @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("Idea-d024f886") String cookie_ga,
                                       @CookieValue("Idea-d024f886") Cookie cookie
    ) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name", username);
        map.put("mp", mp);

        map.put("userAgent", userAgent);
        map.put("header", header);

        map.put("age", age);
        map.put("inters", inters);
        map.put("params", params);

        map.put("cookie_ga", cookie_ga);
        System.out.println("通过注解获取到Idea-d024f886的cookie对象为:" + cookie);
        return map;
    }

    //      5.@RequestBody 注解:  主要作用获得POST提交的数据
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content) {
        Map<String, Object> map = new HashMap<>();
        map.put("content", content);
        return map;
    }
}

@RequestAttribute的后端代码

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author Jsxs
 * @Date 2023/7/3 18:34
 * @PackageName:com.jsxs.controller
 * @ClassName: RequestController
 * @Description: TODO  1.HttpServletRequest 经过一次转发就失效(利用这个重定向是获取不到值的,因为重定向属于第二次转发了) 2.HttpSession 浏览器关闭失效 3. HttpServletContent 服务器关闭
 * @Version 1.0
 */
@Controller
public class RequestController {

    @Resource
    HttpSession session;

    @GetMapping("/goto")
    public String goToPage(HttpServletRequest httpServletRequest){

        httpServletRequest.setAttribute("info","转发成功了..");
        httpServletRequest.setAttribute("msg","jsxs");

        // 切记如果我们没有使用thymeleaf的话,是不能实现前后端跳转的。 (下面会显示找不到MVC)
        return "forward:/success";   // 转发到 /success 请求;并不是转发到success页面的。
    }

    @ResponseBody
    @GetMapping("/success")
    public Map<String, Object> SuccessPage(HttpServletRequest httpServletRequest, @RequestAttribute("msg") String name){
        String info = (String)httpServletRequest.getAttribute("info");
        System.out.println(info+" "+name);
        HashMap<String, Object> map = new HashMap<>();
        map.put("info_代码方式",info);
        map.put("msg_注解方式",name);
        return map;
    }
}

index.html 前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>你好</h1>
<form action="/user" method="post">
    <button type="submit">Post方式进行跳转</button>
</form>
<form action="/user" method="get">
    <button type="submit">Get方式进行跳转</button>
</form>
<form action="/user" method="post">
    <input name="aaaa" type="hidden" value="DELETE">
    <button type="submit">DELETE方式进行跳转</button>
</form>
<form action="/user" method="post">
    <input name="aaaa" type="hidden" value="PUT">
    <button type="submit">PUT方式进行跳转</button>
</form>

<ul>
    <a href="http://localhost:8080/car/1/owner/jsxs/df"> @PathVariable注解</a>
</ul>

<ul>
    <a href="http://localhost:8080/car/1/owner/jsxs"> @PathVariable注解 + @RequestHeader</a>
</ul>

<ul>
    <a href="http://localhost:8080/car/1/owner3/jsxs?age=18&inters=basketball&inters=game"> @PathVariable注解 +
        @RequestHeader + @RequestParam</a>
</ul>

<ul>
    <a href="http://localhost:8080/car/1/owner4/jsxs?age=18&inters=basketball&inters=game"> @PathVariable注解 +
        @RequestHeader + @RequestParam + @CookieValue</a>
</ul>

<form method="post" action="/save">
    <input value="liming" name="user" type="hidden">
    <input value="123456" name="password" type="hidden">
    <button type="submit">@RequestBody注解</button>
</form>

<ul>
    <a href="http://localhost:8080/goto">@RequestAttribute注解(这是一个系列的中的其中一个)</a>
</ul>
</body>
</html>

在这里插入图片描述

  1. 矩阵注解

我们要开启矩阵注解的支持,因为SpringBoot默认是关闭的

WebMvcAutoConfiguration类下 -> configurePathMatch()方法体中 -> UrlPathHelper类中 -> removeSemicolonContent属性默认为(true)
在这里插入图片描述
假如我们访问矩阵路径的时候报错400 (请求异常) 就是我们的配置矩阵注解的支持。

1. 第一种配置方式 : 继承WebMvcConfigurer 接口 + 实现configurePathMatch方法

2.第二种配置方式: @Bean WebMvcConfigurer接口并重写里面的方法

3.因为JDK1.8支持接口默认方法所以我们不必要重写接口中所有的方法。

配置文件:

package com.jsxs.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }

//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper helper = new UrlPathHelper();
//        helper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(helper);
//    }


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
        };
    }
}

假如页面报404,主要原因是因为我们在使用矩阵注解的时候,没有使用@PathVablied注解获取路径所以报404的错误。分号的前面是路径。分号前面的路径一定要使用@PathVabiled()

java

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author Jsxs
 * @Date 2023/7/3 16:14
 * @PackageName:com.jsxs.controller
 * @ClassName: ParamterTestController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
@ResponseBody
public class ParameterTestController {

    //      6. @MatrixVariable

    // /car/{path;low=10,brand=byd}
    // 面试题: cookie被静用了,session里面存放的值怎么获取?
    // 正常流程: session->(生成)sessionID(会保存在cookie中)->cookie(需要携带SessionID)才能进行获取具体session
    // cookie被禁用怎么处理?: url重写,使用矩阵变量进行传递(把Session的值使用矩阵变量的方式进行传递)


    // /cars/sell;low=34;brand=byd,audi,yd
    // 我们访问的时候发现页面报400 请求异常的错误,主要原因是SpringBoot禁用了矩阵变量
    // 我们需要手动的进行开启 : 原理对路径的处理都是 WebMvcAutoConfiguration类下 -> configurePathMatch()方法体中 -> UrlPathHelper类中 -> removeSemicolonContent属性默认为(true)

    // 1.成功配置开启之后,我们访问的页面是404.因为我们矩阵变量有格式
    @GetMapping("/cars/{path}")

    public Map<String, Object> carsSell(@MatrixVariable("low") Integer low,
                                        @MatrixVariable("brand") List<String> brand,
                                        @MatrixVariable Map<String, String> mv,
                                        @PathVariable("path") String path
    ) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("low", low);
        map.put("brand", brand);
        map.put("path", path);
        map.put("mv", mv);
        return map;
    }
	
	// 非紧密性矩阵, ;之前的路径一定要使用Rest 风格

    @GetMapping("/cars/{path}/{a}")

    public Map<String, Object> carsSell2(@MatrixVariable("low") Integer low,
                                        @MatrixVariable("brand") List<String> brand,
                                        @MatrixVariable Map<String, String> mv,
                                        @PathVariable("path") String path,
                                        @PathVariable("a") String a
    ) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("low", low);
        map.put("brand", brand);
        map.put("path_1", path);
        map.put("path_2", a);
        map.put("mv", mv);
        return map;
    }

    //      7. 紧密型矩阵 : 查询是1号的且年龄等于20的老板手下员工2号且员工年龄是10岁的
    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossID}/{empID}")
    public Map<String, Object> Boss(
            @MatrixVariable(value = "age", pathVar = "bossID") Integer BossAge,
            @MatrixVariable(value = "age", pathVar = "empID") Integer empAge,
            @PathVariable("bossID") String path_bossID,
            @PathVariable("empID") String path_empID,
            @MatrixVariable Map<String, String> mv
    ) {
        Map<String, Object> map = new HashMap<>();

        map.put("path_boss_id",path_bossID);
        map.put("path_emp_id",path_empID);
        map.put("boss_age",BossAge);
        map.put("empAge",empAge);
        map.put("mv",mv);
        return map;
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<ul>
    <a href="http://localhost:8080/cars/sell;low=34;brand=byd,bmw,mi">@MatrixVariable注解 (数组不分开)</a>
</ul>

<ul>

    <a href="http://localhost:8080/cars/sell;low=34;brand=byd;bmw;mi">@MatrixVariable注解 (数组分开)</a>
</ul>

<ul>

    <a href="http://localhost:8080/cars/sell/a;low=34;brand=byd;bmw;mi">@MatrixVariable注解 (非紧密双矩阵)</a>
</ul>

<ul>
    <a href="http://localhost:8080/boss/1;age=20/2;age=10">@MatrixVariable注解 (紧密双矩阵)</a>
</ul>
</body>
</html>

在这里插入图片描述

(1.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);
	}
(1.5)、自定义对象参数

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

(3).POJO封装过程

我们自定义的类在SpringMVC中是由下面的类进行封装的

  • ServletModelAttributeMethodProcessor 类进行封装的

这里我们使用级联数据绑定 pet.name 这个就叫级联

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/saveuser" method="post">

    姓名: <input name="userName" value="jsxs">
    年龄: <input name="age" value="18">
    生日: <input name="birth" value="2019/12/17">
    宠物姓名: <input name="pet.name" value="阿猫">
    宠物年龄: <input name="pet.age" value="5">
    <input type="submit" value="保存">
</form>
</body>
</html>
package com.jsxs.bean;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import lombok.Data;

import java.util.Date;

/**
 * @Author Jsxs
 * @Date 2023/7/5 11:39
 * @PackageName:com.jsxs.bean
 * @ClassName: Person
 * @Description: TODO
 * @Version 1.0
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}

package com.jsxs.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author Jsxs
 * @Date 2023/7/5 11:40
 * @PackageName:com.jsxs.bean
 * @ClassName: Pet
 * @Description: TODO
 * @Version 1.0
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Pet {
    private String name;
    private String age;
}


    //  数据绑定: 页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
    @PostMapping("/saveuser")
    // 我们的想法是: 因为传递过来的都是实体类的数据
    public Person saveUser(Person person,@RequestBody String content){

        return person;
    }

结果我们发现我们提交的数据不仅进行了自动类型转换,而且还赋值了数据。
在这里插入图片描述

(1.4)、复杂参数

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

  1. Map和Model (会转储到HttpRequest中)

假如说我们在方法种放的参数是: Map类型和Model类型。那么Map和Model中添加的值相当于(HttpRequest)request.setAttribute()中放置数据。
我们在传地方设置两个参数Map和Model类型,转发到

// 接受方
    @ResponseBody
    @GetMapping("/success")
    public Map<String, Object> SuccessPage(HttpServletRequest httpServletRequest,
                                           @RequestAttribute(name = "msg",required = false) String name,
                                           HttpServletRequest request
    ) {
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");


        String info = (String) httpServletRequest.getAttribute("info");
        System.out.println(info + " " + name);
        HashMap<String, Object> map = new HashMap<>();
        map.put("info_代码方式", info);  // 假如说是: 通过/params转发过来的就会为空 -》 (因为这个属于二次请求过来的)
        map.put("msg_注解方式", name);   // 假如说是: 通过/params转发过来的就会为空

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

// 传地方

    @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", "hello world");
        Cookie cookie = new Cookie("cookie_self", "88888888888");
        cookie.setDomain("localhost");  // 作用域设置为 本机
        response.addCookie(cookie);
        return "forward:/success";
    }

在这里插入图片描述
MapMethodProcessor 类下
Map、Model类型的参数,会返回 mavContainer.getModel();—> BindingAwareModelMap 是Model 也是Map
在这里插入图片描述
下面是Model和Map的对象,我们发现对象是一样的。所以Model是等于Map的。
在这里插入图片描述

(4).参数处理原理

路径跳转全在DispatcherServlet.java这里

  • HandlerMapping中找到能处理请求的Handler(Controller.method()) -》 请求的URL和请求方式
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter -》 处理方法里面的参数和注解
  • 适配器执行目标方法并确定方法参数的每一个值
(1.1)、HandlerAdapter

在这里插入图片描述
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx

(1.2)、执行目标方法

获取适配器

	// Determine handler adapter for the current request.
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

执行目标方法

// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// RequestMappingHandlerAdapter 类

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


//ServletInvocableHandlerMethod 类
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//InvocableHandlerMethod 类 获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

(1.3)、参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器

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

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument
(1.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;
	}

假如说注解的参数不支持: "No suitable resolver"

(1.1)、挨个判断所有参数解析器那个支持解析这个参数

HandlerMethodArgumentResolverComposite@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
(1.2)、解析这个参数的值
InvocableHandlerMethod 类 进入这个方法
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

在这里插入图片描述

(1.3)、自定义类型参数 封装POJO

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

BeanUtils/**
	 * Check if the given type represents a "simple" value type: a primitive or
	 * primitive wrapper, an enum, a String or other CharSequence, a Number, a
	 * Date, a Temporal, a URI, a URL, a Locale, or a Class.
	 * <p>{@code Void} and {@code void} are not considered simple value types.
	 * @param type the type to check
	 * @return whether the given type represents a "simple" value type
	 * @see #isSimpleProperty(Class)
	 */
	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));
	}

这里是将我们原来的request的值赋值给我们新创建的实列对象。

@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里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

GenericConversionService(转换器):在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
具有124个类型转换、
在这里插入图片描述
自动类型转换

在这里插入图片描述
赋值
在这里插入图片描述
以 kv 键值对的方式进行赋值
在这里插入图片描述
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter<String, T>。 就是说String转换成T类型

自定义conterver 转换器

由于公司业务的需要,我们在提交宠物表单的时候不使用级联的方式。要求名字是都好前面的。年龄是逗号后面的,这个时候我们可以使用SpringMVC的conterver转换器进行转换我们表单的类型

<form action="/saveuser" method="post">

    姓名: <input name="userName" value="jsxs">
    年龄: <input name="age" value="18">
    生日: <input name="birth" value="2019/12/17">
<!--    宠物姓名: <input name="pet.name" value="阿猫">-->
<!--    宠物年龄: <input name="pet.age" value="5">-->
    宠物: <input name="pet" value="阿毛,5">
    <input type="submit" value="保存">
</form>

配置类转换器


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            //  配置支持我们的矩阵注解
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
            // 配置支持我们的自定义converter转换器

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {  //source 就是页面提交过来的值。只获得过来的值
                        if (!StringUtils.isEmpty(source)){  // 假如说提交的数据不为空
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);  // 逗号之前的设置成姓名
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }

                        return null;
                    }
                });
            }
        };
    }

在这里插入图片描述

    // 数据绑定: 页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
    @PostMapping("/saveuser")
    // 我们的想法是: 因为传递过来的都是实体类的数据
    public Person saveUser(Person person,@RequestBody String content){

        return person;
    }

当124个找不到的时候就会找 配置文件看看有没有配置的。
在这里插入图片描述

(6).目标方法执行完成

HandlerMethodArgumentResolverComposite 类
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据
在这里插入图片描述

(7).处理派发结果

这里解释了为什么我们Map 和 Model的值会变成 请求域中的值。
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.
// 这个Model值就是Map 和 Model 一直追踪的值。exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

    // ⭐model中的所有数据遍历挨个放在请求域中
		model.forEach((name, value) -> {
			if (value != null) {
			⭐⭐	request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

4.数据响应与内容协商

在这里插入图片描述

(1).响应JSON

(1.1)jackson.jar+@ResponseBody

在SpringMVC场景中,并不会自动给我们返回Json字符串的也没有Json字符串的过滤器,在SpringBoot中

只要我们使用了@ResponseBody (就会利用返回值处理器里面的消息转换器进行处理)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
web场景自动引入了json场景
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-json</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>

在这里插入图片描述
给前端自动返回json数据;

package com.jsxs.controller;

import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @Author Jsxs
 * @Date 2023/7/6 10:16
 * @PackageName:com.jsxs.controller
 * @ClassName: ResponseTestController
 * @Description: TODO
 * @Version 1.0
 */

@Controller
@ResponseBody
public class ResponseTestController {

    @GetMapping("/test/person")
    public Person person() throws ParseException {
        Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
        return person;
    }
}
(1.1.1)、返回值解析器

15个方法返回值解析器
在这里插入图片描述

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

(1.1.2)、返回值解析器原理
  • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 2、返回值处理器调用 handleReturnValue 进行处理
    • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
        1. 利用 MessageConverters 进行处理 将数据写为json
        • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型 浏览器接受7种)
          在这里插入图片描述
        • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
          在这里插入图片描述
        • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter (消息转换器),看谁能处理?
          在这里插入图片描述

            • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
            • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
(1.2).SpringMVC到底支持哪些返回值

这里对应着15种返回值解析器

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask@ModelAttribute 且为对象类型的


@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor
(1.3)/HTTPMessageConverter原理
(1.3.1)、MessageConverter规范

在这里插入图片描述
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据

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

(1.3.2)、默认的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 (不管是谁直接为true,也就是说不管是啥文件直接接受)
8 - true (不管是谁直接为true,也就是说不管是啥文件直接接受)
9 - 支持注解方式 xml处理的。

最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

·AbstractGenericHttpMessageConverter类种的·

outputMessage.getBody().flush();

在这里插入图片描述

(2).内容协商 (MessageConverter)

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

(2.1)、引入XML文件
 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

在这里插入图片描述

(2.2)、postman分别测试返回json和xml

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

在这里插入图片描述

(2.3)、基于请求参数的内容协商 ⭐⭐

为了方便内容协商,开启基于请求参数的内容协商功能

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

通过点开源码我们发现我们需要什么样的类型,我们只需要在路径后面添加上 format='xxx' 格式即可。前提是我们需要什么类型的时候要有依赖在 pom.xml 中。比如我们需要使用xml的,我们要有上面的那个 jackson-dataformat-xml 依赖。

在这里插入图片描述

获取json: http://localhost:8080/test/person?format=json
获取xml格式: http://localhost:8080/test/person?format=xml
在这里插入图片描述

源码中发现相比于以前的请求头的内容协商多了一个参数的请求协商。

在这里插入图片描述

确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值

在这里插入图片描述

2、最终进行内容协商返回给客户端xml即可。
在这里插入图片描述

(2.4)、内容协商原理
  • 1.判断当前响应头种是否已经有已经确定的媒体类型。MediateType
  • 2.获取客户端(浏览器或者PostMan)支持的请求的Accept头(浏览器)。通过 contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • (1).先得到客户端能够接受的所有媒体类型是什么。
  • 3.获取服务器能够生产的媒体类型。(服务器方)
  • 4.遍历服务器所有支持的媒体类型 进行 与客户端能够接受的类型进行匹配的操作,选择最佳匹配。
  • 5.用支持将对象转为最佳媒体类型的converter,调用它进行转化。

为什么说引入xml的转环包就会被底层接受的原理

WebMvcConfigurationSupport 类下

917行 jackson2XmlPresent

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

(2.5)、自定义 MessageConverter

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

0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;

package com.jsxs.controller;

import com.jsxs.bean.Person;
import com.jsxs.bean.Pet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @Author Jsxs
 * @Date 2023/7/6 10:16
 * @PackageName:com.jsxs.controller
 * @ClassName: ResponseTestController
 * @Description: TODO
 * @Version 1.0
 */

@Controller
@ResponseBody
public class ResponseTestController {

    /**
     *
     * @return
     * @throws ParseException
     *
     * @TODO: 1.浏览器请求返回xml文件。2.ajax请求返回json文件。3.硅谷app请求返回自定义文件
     *  在以前一个请求完成这项工作这是不可能完成的任务,但是在现在我们有了内容协商我们可以完成这个任务。
     *  步骤: 1.添加自定义的MessageConverter进入系统底层。2.系统底层就会统计出所有MessageConverter
     * 		3.进行内容客户端与服务器内容协商匹配。
     */

    @GetMapping("/test/person")
    public Person person() throws ParseException {
        Person person = new Person("jsxs", 12, new SimpleDateFormat("yyyy-MM-dd").parse("2012-02-01"), new Pet("哈吉米", 2));
        return person;
    }
}

在这里插入图片描述
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer

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

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

            }
        }
    }

首先配置协议转换器

package com.jsxs.convert;

import com.jsxs.bean.Person;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/7 12:38
 * @PackageName:com.jsxs.convert
 * @ClassName: GguiGuMessageConverter
 * @Description: TODO   自定义的消息转换器
 * @Version 1.0
 */
public class GuiGuMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    /**
     *   条件是什么
     * @param clazz
     * @param mediaType
     * @return
     */
    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Person.class);  //只有返回的类型是Person类行就能进行读写
    }

    /**
     *   服务器要统计所有的MessageConverter 都能写哪些内容类型
     *
     *   application/x-jsxs
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x-jsxs");
    }

    @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 data =person.getUserName()+";"+person.getAge()+";"+person.getAge()+";"+person.getPet();

        // 写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

package com.jsxs.config;

import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.util.List;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }

//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper helper = new UrlPathHelper();
//        helper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(helper);
//    }


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            //  配置支持我们的矩阵注解
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
            // 配置支持我们的自定义converter转换器

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {  //source 就是页面提交过来的值。只获得过来的值
                        if (!StringUtils.isEmpty(source)){  // 假如说提交的数据不为空
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);  // 逗号之前的设置成姓名
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }

            // 扩展 内容消息转换器

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiGuMessageConverter());
            }
        };
    }
}

上面的内容可以通过 PostMan 进行处理。浏览器因为我们自己设置不了请求头,所以目前测试不了我们自定义的内容协商。

(2.6)、运用参数的方式请求自定义内容协商

我们通过debug的方式进入到了我们浏览器接受的内容协议上,并查看到有两种接收方式,并在请求参数的方式上没有看到 自定义的格式,所以我们要进行自定义的操作。
在这里插入图片描述
因为只兼容上面两种 所以我们要进行配置内容协商功能

package com.jsxs.config;

import com.jsxs.bean.Pet;
import com.jsxs.convert.GuiGuMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.util.*;

/**
 * @Author Jsxs
 * @Date 2023/7/3 11:13
 * @PackageName:com.jsxs.config
 * @ClassName: WebConfig
 * @Description: TODO
 * @Version 1.0
 */
@Configuration(proxyBeanMethods = false)
// 第一种方式 @Configuration + 实现WebMvcConfigurer接口 (因为JDK8允许接口的默认方法和默认实现所以我们不需要将所有方法全部重写)
// 第二种方式: @Configuration +@Bean 重新注入我们的组件
public class WebConfig /*implements WebMvcConfigurer */{

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("aaaa");
        return hiddenHttpMethodFilter;
    }

//    @Override
//    public void configurePathMatch(PathMatchConfigurer configurer) {
//        UrlPathHelper helper = new UrlPathHelper();
//        helper.setRemoveSemicolonContent(false);
//        configurer.setUrlPathHelper(helper);
//    }


    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer(){
            //  配置支持我们的矩阵注解
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper helper = new UrlPathHelper();
                helper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(helper);
            }
            // 配置支持我们的自定义converter转换器

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String source) {  //source 就是页面提交过来的值。只获得过来的值
                        if (!StringUtils.isEmpty(source)){  // 假如说提交的数据不为空
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);  // 逗号之前的设置成姓名
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }

            // 扩展 内容消息转换器
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                converters.add(new GuiGuMessageConverter());
            }

            // 自定义(重写)内容协商  ⭐⭐⭐

            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                // 要求需要为String
                HashMap<String, MediaType> mediaTypeHashMap = new HashMap<>();
                // 配置支持的请求参数
                mediaTypeHashMap.put("json",MediaType.APPLICATION_JSON);
                mediaTypeHashMap.put("xml",MediaType.APPLICATION_XML);
                mediaTypeHashMap.put("jsxs",MediaType.parseMediaType("application/x-jsxs"));
                // 支持解析哪些参数对应的哪些媒体类型 -》 参数内容协商支持
                ParameterContentNegotiationStrategy parameterContentNegotiationStrategy = new ParameterContentNegotiationStrategy(mediaTypeHashMap);
                //  支持解析哪些参数对应的哪些媒体类型 -》 请求头内容协商支持 (这里通过PostMan进行测试)
                HeaderContentNegotiationStrategy headerContentNegotiationStrategy = new HeaderContentNegotiationStrategy();
                //    真正执行
                configurer.strategies(Arrays.asList(parameterContentNegotiationStrategy,headerContentNegotiationStrategy));
            }
        };
    }
}

在这里插入图片描述

在这里插入图片描述
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

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

5.视图解析与模板引擎

视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染
在这里插入图片描述

(1).视图解析
(1.1)、视图解析原理流程

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址
2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)。
4、processDispatchResult 处理派发结果(页面该如何响应)

DisplatchServlet的 第1078
  • 1、render(mv, request, response); 进行页面渲染逻辑
    • 1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】

      • 1、所有的视图解析器尝试是否能根据当前返回值得到View对象 (for遍历尝试)
      • 2、得到了 redirect:/main.html --> Thymeleaf new RedirectView()
      • 3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
        在这里插入图片描述
      • 4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
    • view.render(mv.getModelInternal(), request, response); 1393行

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

视图解析:

  • 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
  • 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
(2).Thymeleaf基本语法
(2.1)、表达式

在这里插入图片描述

(2.2)、字面量

文本值: ‘one text’ , ‘Another one!’ ,…数字: 0 , 34 , 3.0 , 12.3 ,…布尔值: true , false
空值: null
变量: one,two,… 变量不能有空格

(2.3)、文本操作

字符串拼接: +
变量替换: |The name is ${name}|

(2.4)、数学运算

运算符: + , - , * , / , %

(2.5)、布尔运算

运算符: and , or
一元运算: ! , not

(2.6)、比较运算

比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )

(2.7)、条件运算

If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)

(2.8)、特殊操作

无操作: _

(2.9)、设置属性值-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/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

以上两个的代替写法 th:xxxx

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
行内写法

1. 假如要写的内容不在标签中而在行内,那么就用这个方式。(非session)

<h1>[[${xxx}]]</h1>

2. 假如是取Session的值
<h1>[[$session.name.xxx}]]</h1>
(2.10)、迭代
<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>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
  <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>
(2.11)、条件运算
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>
(3).Thymeleaf的使用
(3.1)、引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
(3.2)、自动配置好了thymeleaf

ThymeleafAutoConfiguration 类

@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、我们只需要直接开发页面

在这里插入图片描述

(3.2)、页面开发
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Success</h1>
<h1 th:text="${A}"></h1>
<a th:href="${baidu}">点击我去金橘社区 ${baidu}</a>
<br>
<br>
<a th:href="@{baidu}">点击我去金橘社区 @{baidu}</a>
<br>
<br>
<a th:href="@{/baidu}">点击我去金橘社区 @{/baidu}</a>
</body>
</html>
package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @Author Jsxs
 * @Date 2023/7/7 17:30
 * @PackageName:com.jsxs.controller
 * @ClassName: ViewTestController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
public class ViewTestController {

    @GetMapping("/toTest")
    public String toTest(Model model){
        // model 会自动放入到请求域中和HttpRequest是一样的,只能接受一次请求的操作
        model.addAttribute("A","a");
        model.addAttribute("baidu","https://www.jsxs1.cn");
        return "success";   // 假如说没有模板解析器的话,这里的路径会报黄。
    }
}

# 给整个服务器添加前缀
server:
  servlet:
    context-path: /jsxs

在这里插入图片描述

(4).后台管理系统总结
1. 假如在template中再新建包的话,我们只需要在 controller 的返回值中添加上新建包路径即可  /新建包名/xxx。

2. controller 页面跳转的实质是转发;不是重定向。

3. return: 的值会默认拼接  templates/xxxx.html; return forward: return redirect 找的都是请求的路径不是页面。

4. 静态资源只要放在四大区域就行,前端调用的时候可以省略掉前面的四大区域路径只写相对路径即可。

5. 抽取公共模板(第一种)
	(1). 公共页(top.html): 在标签中设置  th:fragment="AAAA"
		eg: <div  th:fragment="AAAA"></div>
	(2). 使用公共页方: th:insert="~{公共页的HTML名字 :: AAAA}"
		eg:   1. <div th:insert="~{top :: AAAA}"></div>2.<div th:insert="top :: AAAA"></div>3.<div th:replace="~{top :: utopbar}"></div>4.<div th:include="~{top :: topbar}"></div>
	假如说公共页面和被添加公共页面不再同一个包中那么就要加上路径指定在哪
		eg:<div th:include="~{commons/top :: topbar}"></div>

6.抽取公共模板(第二种 ->选择器方式)
	(1). 公共页(top.html): 在标签中设置  id="BBB"
	(2). 使用方: <div th:replace="top :: #BBB"> (当然以上的几种方法都适用)

公共方:

<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</footer>

使用公共方

<body>

  ...
	会带上div(全部都要)
  <div th:insert="~{footer :: copy}"></div>
	引入的东西不会在div里面
  <div th:replace="~{footer :: copy}"></div>
  	引入的中西会在div里面
  <div th:include="~{footer :: copy}"></div>
  
</body>

实际效果:

假如引入的是css、js。也是一样的只不过把div改为link或script
<body>

  ...

  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>
  
  <div>
      &copy; 2011 The Good Thymes Virtual Grocery
  </div>
  
</body>
(4.1)、举列子(公共方)

1.公共方的页面
在这里插入图片描述

2.replace->引入的东西会在link标签里面内嵌link标签

<link th:replace="~{}">

在这里插入图片描述
3.include->引入的东西会在link标签里面内嵌link标签

<link th:include="~{}">

在这里插入图片描述
4.标签改为div,div是万能的。

<div th:replace="~{}"></div>

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/35473.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】程序员的基本素养学习

这是目录 写在前面一、内存管理1、分段2、分页 二、线程管理三、静态库1、编译1.1、预处理1.2、编译1.3、汇编1.4、链接2、编译器3、目标文件**.text****.data****.bss****__attribute__** 3.1、符号3.2、兼容C语言 -- extern C4、链接 -- ld 写在前面 本文记录自己的学习生涯…

Haproxy搭建Web集群

目录 一.Haproxy简介 1.1Haproxy的概述 1.2 Haproxy的主要特征 1.3 Haproxy常见负载均衡策略 二 LVS、Nginx、HAproxy的区别 2.1 nginx 2.2 LVS 2.3 haproxy 三 Haproxy搭建Web群集 环境准备 haproxy 服务器部署 节点服务器部署 四 测试 五 日志定义 一.Haproxy简…

Python网络爬虫使用教程

文章目录 一、URL资源抓取1.urllib2.requests3.requests-html二、正则表达式三、数据解析1.Beautiful Soup2.lxml3.selectolax四、自动化爬虫selenium五、爬虫框架1.Scrapy2.pyspider框架六、模拟登录与验证码识别七、autoscraper&#xff08;不需要编程基础&#xff09; 一、U…

6阶高清视频滤波驱动MS1681

MS1681 是一个单通道视频缓冲器&#xff0c;它内部集成6dB 增益的轨到轨输出驱动器和6 阶输出重建滤波器。MS1681 的-3dB 带宽为35MHz&#xff0c;压摆率为160V/us。MS1681 比无源LC 滤波器与外加驱动的解决方案能提供更好的图像质量。它单电源供电范围为2.5V 到5.5V&#xff0…

DAY39:贪心算法(七)根据身高重建队列(注意思路)+最少箭引爆气球(重叠区间)

文章目录 406.根据身高重建队列&#xff08;注意思路&#xff09;思路两个维度降序排序注意点 完整版vector容器插入相关复习为什么能直接根据ki数值插入ki位置的下标 时间复杂度vector-insert操作存在的问题链表优化版时间复杂度list和vector的插入与访问操作区别 452.最少弓箭…

CentOS7安装详细安装

CentOS 7镜像下载 官网下载链接&#xff1a;http://isoredirect.centos.org/centos/7/isos/x86_64/ step1: 进入下载页&#xff0c;选择阿里云站点进行下载 Actual Country 国内资源 Nearby Countries 周边国家资源 阿里云站点&#xff1a;http://mirrors.aliyun.com/cento…

​浅谈大型语言模型

大型语言模型&#xff08;Large Language Models&#xff0c;LLMs&#xff09;是一类强大的人工智能模型&#xff0c;具有出色的自然语言处理能力。它们在许多任务中表现出色&#xff0c;如机器翻译、文本摘要、对话生成和情感分析等。下面我们将介绍大型语言模型的训练和生成过…

计算机体系结构基础知识介绍之缓存性能的十大进阶优化之非阻塞缓存(四)

优化四&#xff1a;非阻塞缓存&#xff0c;提高缓存带宽 对于允许乱序执行的流水线计算机&#xff0c;处理器不需要因数据高速缓存未命中而停止。 例如&#xff0c;处理器可以继续从指令高速缓存获取指令&#xff0c;同时等待数据高速缓存返回丢失的数据。 非阻塞高速缓存或无…

MYSQL的基本数据类型和建库

1.创建数据库&#xff0c;删除数据库&#xff0c;查询创建数据的语句&#xff0c;使用数据库&#xff0c;查询当前默认的数据库以及使用的编码方式校验规则 2.数字&#xff0c;文本&#xff0c;日期 在一章表中定义多个字段&#xff0c;要使用今天提到的所有的数据类型 1创建数…

为了实现上网自由,我做了一个多功能串口服务器

项目作者&#xff1a;小华的物联网嵌入式之旅 介绍&#xff1a;从事电气自动化行业&#xff0c;多次获得物联网设计竞赛&#xff0c;爱好嵌入式设计开发&#xff0c;物联网开发。 设计方案思路的由来&#xff0c;是因为我们现在的开发板基本需要通过串口与WIFI模组或以太网模…

图片框架Glide学习总结及插件实现

一.前言 图片加载框架个人选择的是Glide&#xff0c;该框架非常优秀&#xff0c;其知识体系很庞大&#xff0c;个人就对Glide部分知识的学习做一下总结&#xff0c;同时对框架的使用做一下封装&#xff0c;做成插件。 二.知识主干 知识主干如下&#xff0c;每一部分的知识会…

Network Neuroscience:整个生命周期的功能连接体指纹

导读 随着年龄的增长&#xff0c;人脑功能结构发生了系统性的变化。然而&#xff0c;功能连接(FC)作为一种检测独特“连接体指纹”的强大特征&#xff0c;使个体能够在同龄人中被识别出来。虽然已在年轻人样本中观察到这种指纹&#xff0c;但该方法在整个生命周期内的可靠性尚…

实现【Linux--NTP 时间同步服务搭建】

实现【Linux--NTP 时间同步服务搭建】 &#x1f53b; 前言&#x1f53b; 一、NTP 校时&#x1f530; 1.1 NTP 服务校时与 ntpdate 校时的区别&#x1f530; 1.2 NTP 校时服务搭建&#x1f530; 1.2.1 确认 ntp 的安装&#x1f530; 1.2.2 配置 ntp 服务&#x1f530; 1.2.3 启动…

QNAP威联通NAS搭建SFTP服务,并内网穿透实现公网远程访问

文章目录 前言1. 威联通NAS启用SFTP2. 测试局域网访问3. 内网穿透3.1 威联通安装cpolar内网穿透3.2 创建隧道3.3 测试公网远程访问 4. 配置固定公网TCP端口地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址4.3 测试使用固定TCP端口地址远程连接威联通SFTP 转载自远程内…

优化|一阶方法:求解不具有凸性和lipschitz连续性的复合问题

论文解读者&#xff1a;陈康明&#xff0c;赵田田&#xff0c;李朋 编者按&#xff1a;​ 对于大多数一阶算法&#xff0c;我们会在收敛性分析时假设函数是凸的&#xff0c;且梯度满足全局 Lipschitz 条件。而本文中&#xff0c;对于某一类特殊函数。我们不仅不要求函数是凸的…

一次源码编译安装PostgreSql失败

需要perl&#xff1b;之前博文已提到&#xff1b;之前有一种编程语言叫perl&#xff0c;此perl应该不是那个&#xff1b;可到其官网下载&#xff0c;Perl Download - www.perl.org 安装时添加到环境变量&#xff1b; 可能是一个东西&#xff1b;有编程语言和工具&#xff1b;大…

html实现多种风格的时间轴(附源码)

文章目录 1.设计来源1.1 对称风格时间轴1.2 横向风格时间轴1.3 回忆风格时间轴1.4 记事风格时间轴1.5 简易风格时间轴1.6 科技风格时间轴1.7 列表风格时间轴1.8 跑道风格时间轴1.9 人物风格时间轴1.10 容器风格时间轴1.11 沙滩风格时间轴1.12 双边风格时间轴1.13 图文风格时间轴…

CRM系统中AI如何进行销售线索评分?有什么好处(上)

每个公司的TOP销售都是精明的猎手。他们善于从大量潜在客户中挑出最可能购买的&#xff0c;把最好的时间、精力和资源给到高意向客户。意向度差一些的排在后面&#xff0c;在资源分配上也会降低。现在&#xff0c;您可以通过AI来进行线索评分&#xff0c;可以说CRM销售线索评分…

【Linux】 Linus世界,WIndows VS Linux

文章目录 前言WindowsLinux操作系统Windows VS Linux收费情况技术支持安全性开源 区别 前言 在电脑世界有两种十分常见的电脑操作系统——Linux与和Windows&#xff0c;相信对电脑有一定了解的人对它们一定并不陌生&#xff01;但是在我们的使用过程中&#xff0c;是否有什么事…

MySQL用户管理

目录 用户管理 用户 用户信息 创建用户 删除用户 修改用户密码 数据库的权限 给用户授权 回收权限 用户管理 如果我们只能使用root用户&#xff0c;这样存在安全隐患。这时&#xff0c;就需要使用MySQL的用户管理。 用户 用户信息 MySQL中的用户&#xff0c;都存储…