SpringBoot的大时代
1. 微服务
微服务是一种架构风格
一个应用拆分为一组小型服务
每一个服务可以部署在自己的服务器上,运行在自己的进程内,也就是可以独立部署和升级,与单个应用无差
服务之间的交互使用轻量级的HTTP交互
服务围绕业务功能进行拆分
服务可以由全自动部署机制独立部署
去中心化(每一个服务可以用不同的语言来进行开发,也可以使用不同的存储技术)、服务自治
而微服务的出现,将大应用拆分成多个小服务进行独立部署,会导致分布式的产生
2. 分布式
问题:
远程调用
服务发现
负载均衡
服务容错
配置管理
服务监控
链路追踪
日志管理
任务调度
…
分布式的解决:SpringBoot + SpringCloud
3. 云原生
原生应用如何上云:Cloud Native
上云的困难:
服务的自愈
弹性伸缩(拥塞)
服务隔离
自动化部署
灰度发布
流量治理
…
SpringBoot官方文档架构
Spring官方网址
可以通过官方网址的Projects>SpringBoot进行SpringBoot的学习
其中OVERVIEW部分可以看到发布版本的更新情况以及更新的内容;LEARN则可以选择版本来进行对应的学习
本次学习所采用的是2.3.4版本的SpringBoot
SpringBoot2之HelloWorld
系统要求:
配置Maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <mirrors > <mirror > <id > nexus-aliyun</id > <mirrorOf > *</mirrorOf > <name > Nexus aliyun</name > <url > http://maven.aliyun.com/nexus/content/groups/public</url > </mirror > </mirrors > <profiles > <profile > <id > jdk-1.8</id > <activation > <activeByDefault > true</activeByDefault > <jdk > 1.8</jdk > </activation > <properties > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > <maven.compiler.compilerVersion > 1.8</maven.compiler.compilerVersion > </properties > </profile > </profiles >
HelloWorld
需求:浏览发送/hello请求,响应Hello,SpringBoot2
①创建一个普通的Maven项目,编写POM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?xml version="1.0" encoding="UTF-8"?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.ayy</groupId > <artifactId > boot-01-helloworld</artifactId > <version > 1.0-SNAPSHOT</version > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.4.RELEASE</version > </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies > </project >
②编写主程序类,在main>java下创建com.ayy.boot.MainApplication Java类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.ayy.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class,args); } }
③编写controller类,并运行main方法进行测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.ayy.boot.controller;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController { @RequestMapping("/hello") public String handle01 () { return "Hello,SpringBoot2!" ; } }
简化配置
在resources下创建一个application.properties的配置文件,所有的配置如端口号等,都可以写此处,当运行时,SpringBoot会读取里面的配置,若是无更改的则按照SpringBoot默认的配置行事。
当不知道什么配置可以写于其中时,可参照官方文档中的Application Properties的内容来进行设置。
简化部署
maven项目默认是打包为jar包
SpringBoot所打包是一个可执行的jar包 ,通过以下插件配置即可实现
1 2 3 4 5 6 7 8 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
通过maven自带的lifestyle的clean和package进行打包操作
对打好的包通过cmd命令行执行 java -jar 包名
即可执行之!!
注意:
最后展示一下此模块的目录结构:
SpringBoot依赖管理特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //pom.xml里面的父项目<parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.3.4.RELEASE</version > </parent > //starter-parent里面的父项目<parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-dependencies</artifactId > <version > 2.3.4.RELEASE</version > </parent > 在spring-boot-dependencies这一项目里面,几乎声明了所有开发中常用的依赖的版本号,此即为自动版本仲裁机制
1 2 3 4 5 6 7 8 9 10 11 1. spring-boot-starter-* : 此即代表某种场景 2. 只要引入starter,这个场景的所有常规需要的依赖我们都会自动导入 3. SpringBoot所有支持的场景:https://docs.spring.io/spring-boot/docs/2.3.9.RELEASE/reference/html/using-spring-boot.html#using-boot-starter 4. 见到的 *-spring-boot-starter : 是第三方为我们提供的简化开发的场景启动器 5. 所有场景启动器最底层的依赖<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <version > 2.3.4.RELEASE</version > <scope > compile</scope > </dependency >
1 引入依赖默认都可以不写版本号,除非引入的依赖是非版本仲裁的jar,则一定要写版本号
1 2 3 4 5 6 7 8 1. 查看spring-boot-dependencies里面规定的当前依赖的版本所用的关键字 2. 在当前项目里面重写配置 注:利用的时MAVEN提供的特性:就近优先原则<properties > <mysql.version > 5.1.43</mysql.version > </properties >
SpringBoot自动配置特性
默认配置最终都是映射到某一个类上的
配置文件的值会绑定到某一个类上,这个类会在容器中创建对象
引入了哪些场景,这个场景的自动配置才会开启
SpringBoot的所有自动配置功能都在spring-boot-autoconfigure包里面
底层注解-@Configuration(组件添加)解析
在之前,我们对Spring进行一个组件的注册是通过在spring.xml配置文件里增添如下内容实现,在表示配置spring.xml之前,先进行两个类的构建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class User { private String name; private String age; @Override public String toString () { return "User{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}' ; } public User () { } public String getName () { return name; } public User (String name, String age) { this .name = name; this .age = age; } public void setName (String name) { this .name = name; } public String getAge () { return age; } public void setAge (String age) { this .age = age; } }public class Pet { private String name; public String getName () { return name; } @Override public String toString () { return "Pet{" + "name='" + name + '\'' + '}' ; } public Pet (String name) { this .name = name; } public Pet () { } public void setName (String name) { this .name = name; } }
我们通过spring.xml对其中的bean进行注册:
1 2 3 4 5 6 7 8 9 10 <beans > <bean id ="user01" class ="User" > <property name ="name" value ="zhangsan" > </property > <property name ="age" value ="18" > </property > </bean > <bean id ="tomcat" class ="Pet" > <property name ="name" value ="tom" > </property > </bean > </beans >
以上便是Spring通过配置文件的方式,来实现组件的注册,经注册的组件可以在容器中找到
而在SpringBoot中,可以通过@Configuration这个注解来替代spring.xml配置文件,即用一个含有@Configuration注解的类来进行组件的注册,而在需要注册的组件上,只需要通过@Bean注解来声明即可 ,如下类MyConfig所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.ayy.boot.config;import com.ayy.boot.bean.Pet;import com.ayy.boot.bean.User;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class MyConfig { @Bean public User user01 () {return new User("zhangsan" ,"18" );} @Bean public Pet tom () {return new Pet("tomcat" );} }
需要特别注意的是:此时我们所注册在容器中的组件都是单例的 ,无论你通过容器如何getBean或多次getBean,甚至直接获取MyConfig组件(因其也是在配置文件下的类,故其也被注册在容器里 )直接进行方法的调用,也依旧是单例!!
这个单例的构成,与**@Configuration注解下的proxyBeanMethods的默认值为true有直接关系**,proxyBeanMethods即意为代理Bean方法 ,在其为true的情况下,我们通过getBean获得的MyConfig类的实例对象其实是代理对象 ,也即通过这个代理对象,我们无论怎么去调用对象里的方法,也只是从容器里面获取对应的组件而已;当代理Bean方法 值为false时,才会是个普通的对象,通过调用其中方法,获得的实例则不相同。
以下部分是体现代理对象调用方法后所得组件为容器中组件且为单例的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] names = run.getBeanDefinitionNames(); for (String name:names) System.out.println("NANE: " +name); MyConfig myConfig = run.getBean(MyConfig.class); Pet jerry = run.getBean("jerry" , Pet.class); System.out.println(jerry==myConfig.tom()); System.out.println(myConfig); } }
proxyBeanMethods:代理bean的方法根据true/false,有以下两种模式:
Full(proxyBeanMethods=true) 全模式
Lite(proxyBeanMethods=false) 轻量级模式,因为不像全模式,在构建 时需要查询容器中是否存在该实例,加快了运行速度,故为轻量级
在此处举一个单例的实例:比如我们的User里有一个成员变量是Pet,它们两个都是在容器中获得的组件,且此为User依赖Pet ,那么通过单例,可以很好地体现这么一个依赖关系 ,因为User所占有的Pet,也是容器里面所独有的Pet,不存在这些个Pet相异的情况。
因此:当没有依赖组件时则用Lite轻量级模式;当需要依赖时,则需要使用Full全模式。
注:根据后面的学习来看,轻量级模式下的自动类配置,它的参数的获取,有大概率的可能是通过容器中获取。若是自己写的配置类(如笔者自己的MyCofig),使这个代理bean方法失效后,因笔者没有传入参数,故没有进行进一步的测试。因此笔者猜测:SpringBoot在处理配置类的参数时,直接获取的是容器中已有的组件,若是构建当前组件的实例,则对Full会进行单例查询,而对Lite则不查询,直接放入容器中。但随之而来的问题是:自动获取的传入参数如何保证是我们所想要的哪个呢?而框架本身的因为是与配置文件相绑定,所以只要是获取到的实参,都是所需的。
2021.9.7看,不懂上面的注说的啥
底层注解-@Import(导入组件)解析
除了上面所说的@Configuration加上@Bean可以给容器注册组件外,还有之前的@Component(表示为一个组件)、@Controller(表示为一个控制器)、@Service(表示为一个业务逻辑组件)、@Repository(代表它是一个数据库层组件)都能用。
@ComponentScan就是通过指定包扫描路径来实现组件导入,因为告知了Spring该去哪里扫描即哪里找可能是组件的类
@Import是给容器导入组件,可以写在配置类中或组件类中
它的参数是一个数组,这个数组里面写的是想要导入到容器中的组件的类型,它会调用该类的无参构造器来构造出该类的对象加入到容器中,所默认使用的id,即BeanName是全类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Import({User.class, DBHelper.class}) @Configuration() public class MyConfig { @Bean public User user01 () {return new User("zhangsan" ,"18" );} @Bean("jerry") public Pet tom () {return new Pet("tomcat" );} }@SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] beanNames = run.getBeanNamesForType(User.class); System.out.println("<====================================>" ); for (String bean:beanNames) System.out.println("bean:" + bean); DBHelper dbHelper = run.getBean(DBHelper.class); System.out.println("bean:" + dbHelper); } } <====================================> bean:com.ayy.boot.bean.User bean:user01 bean:ch.qos.logback.core.db.DBHelper@1d81e101 不难看出,我们通过导入进去的默认BeanName即我们在Spring.xml注册的id即为全类名,且其为无参构造得到的。
底层注解-@Conditional条件装配
条件装配:满足Conditional指定的条件,则进行组件注入 !!!
Conditional是个根注解,其下的许多注解可以按照名字的意思来进行对应的测试,下面的例子用@ConditionalOnBean来实现,其意为,当某个Bean存在时,则执行下面的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 1. 通过对@Bean 注解注释,使之不会注册到容器中,以下为此例的验证@Import({User.class, DBHelper.class}) @Configuration() public class MyConfig { @Bean public User user01 () {return new User("zhangsan" ,"18" );} public Pet tom () {return new Pet("tomcat" );} }@SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); boolean tom = run.containsBean("tom" ); System.out.println("tom:" + tom); boolean user01 = run.containsBean("user01" ); System.out.println("user01:" + user01); } } 输出结果为: tom:false user01:true 2. 当tom存在时,才注册user01到容器中,否则不注册@Import({User.class, DBHelper.class}) @Configuration() public class MyConfig { @ConditionalOnBean(name={"tom"}) @Bean public User user01 () {return new User("zhangsan" ,"18" );} public Pet tom () {return new Pet("tomcat" );} } @SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); boolean tom = run.containsBean("tom" ); System.out.println("tom:" + tom); boolean user01 = run.containsBean("user01" ); System.out.println("user01:" + user01); } } 输出结果为: tom:false user01:false
底层注解-@ImportResource导入Spring配置文件
@ImportResource用于向SpringBoot中导入Spring的配置文件:spring.xml,即通过此注解可以将无法被SpringBoot所理解的组件注册到容器中。此注解写于配置类上即可
以下为一实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 1. 当在配置类MyConfig中没有加上此注解时: <bean id="springXMLUser" class ="com.ayy.boot.bean.User" > <property name="name" value="lisi" ></property> <property name="age" value="18" ></property> </bean> <bean id="springXMLPet" class ="com.ayy.boot.bean.Pet" > <property name="name" value="lisi's pet" ></property> </bean>@SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); boolean springXMLUser = run.containsBean("springXMLUser" ); System.out.println("springXMLUser:" + springXMLUser); boolean springXMLPet = run.containsBean("springXMLPet" ); System.out.println("springXMLPet:" + springXMLPet); } } springXMLUser:false springXMLPet:false springXMLUser:true springXMLPet:true
底层注解-@ConfigurationProperties配置绑定
在以前,我们对一些常规配置的内容是写在my.properties中然后通过绑定的机制来将之内容写入Javabean中,这个过程较为繁琐,而在SpringBoot中,我们可以通过将配置信息写在application.properties,然后通过注解@ConfigurationProperties来实现绑定 ,且绑定的形式有两种!下图是之前绑定的方法的一个流程显示:
法①
通过在Javabean类Car中进行注解:@Component 和 @ConfigurationProperties(prefix = “mycar”) 来实现配置文件中的内容与该Javabean的绑定,并注册为容器中的一个组件。代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Component @ConfigurationProperties(prefix = "mycar") public class Car { private String brand; private String price; public String getBrand () { return brand; } public void setBrand (String brand) { this .brand = brand; } public String getPrice () { return price; } public void setPrice (String price) { this .price = price; } @Override public String toString () { return "Car{" + "brand='" + brand + '\'' + ", price='" + price + '\'' + '}' ; } public Car (String brand, String price) { this .brand = brand; this .price = price; } public Car () { } } server.port=8888 mycar.brand=TESLA mycar.price=280000 @RestController public class HelloController { @Autowired private Car car; @RequestMapping("/car") public Car myCar () { return car; } } {"brand" :"TESLA" ,"price" :"280000" }
法②
删去Car类中的@Component组件并通过在MyConfig这个配置类中增加注解@EnableConfigurationProperties(Car.class)来开启Car类的属性配置功能
其中@EnableConfigurationProperties的作用有如下两点:
开启Car组件的配置绑定功能
把Car这个组件自动地注册到容器中
此类用法多用于我们使用第三方的jar包中的类的时候,我们不能轻易地去修改别人的源代码,因此可以通过这样的方式来实现组件的配置绑定及加载到容器中。
自动配置-自动包规则原理
自动包配置原理,是在SpringBoot应用下才生效的,即在SpringBoot应用下,可以自动加载配置类,即会自动往容器中导入组件,那么这个实现得从注解@SpringBootApplication先看起;
1 2 3 4 5 6 @SpringBootApplication <=====>等价于@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 接下来我们对它们逐个解析
@SpringBootConfiguration
往此注解内部点去,可以发现其内部的核心是@Configuration,即注明此类是配置类 ,也就是说,我们的MainApplication类也是配置类
@ComponentScan
此即为自动包扫描的配置,配置其下的目录及其子包都会被扫描
@EnableAutoConfiguration
我们通过Ctrl+左键点击进去后发现,@EnableAutoConfiguration注解由如下注解组成:
我们先着重说一下,@AutoConfiguraionPackage这个注解!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Import({Registrar.class}) public @interface AutoConfigurationPackage {}static class Registrar implements ImportBeanDefinitionRegistrar , DeterminableImports { Registrar() { } public void registerBeanDefinitions (AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0 ])); } public Set<Object> determineImports (AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata)); } }
由此可见,我们自动包规则原理,便是基于这个Registrar 这个类,利用这个类,给容器导入一系列的组件。将指定的标注了这个注解(@AutoConfiguraionPackage)或利用之合成的注解(@EnableAutoConfiguraion或@SpringBootApplication)的类所在的包进行了组件注册 !!
自动配置-初始加载自动配置类
上面我们解释了@EnableAutoConfiguraion中的@AutoConfiguraionPackage,紧接着我们讲一下另一个注解@Import({AutoConfigurationImportSelector.class}),我们来详细谈一下这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 在进入AutoConfigurationImportSelector类后,我们看到一个方法: public String[] selectImports(AnnotationMetadata annotationMetadata){...},其中该方法有一行代码是: getAutoConfigurationEntry(annotationMetadata); 2. 上面所说的那个给容器批量导入一批组件的方法,其内调用了: List<String> configurations = this .getCandidateConfigurations(annotationMetadata, attributes); 3. 上述的那个方法是怎么知道导入这些需要导入的配置类的呢?往里面点,我们发现: List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this .getSpringFactoriesLoaderFactoryClass(), this .getBeanClassLoader());4. 再往下点击,可以发现其所加载的内容是: Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader){...}; 5. 那么这些组件是从哪里得到并加载的呢?通过以下对loadSpringFactories的debug过程便可略知一二: Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories" ) : ClassLoader.getSystemResources("META-INF/spring.factories" ); 6. 我们可以通过External Libraries来找对应的存在META-INF/spring.factories位置的jar包,查看其中内容。而最核心的包便是spring-boot-autoconfigure这个包。通过对这个包的内容进行查看,看到其下META-INF/spring.factories中有一行注为Auto Configure的内容,其后紧跟着127 个自动配置类!!也就是说,文件里面写死了spring-boot一启动就要加载到容器中的所有配置类。7. 我们可以通过getBeanDefinitionCount()来查看的确是有这127 个组件的存在,那么此时又存在另一个问题:它那么大,不应该会导致系统很卡嘛? 虽然我们127 个场景的所有自动配置启动的时候默认全部加载,但最终会按需配置!!! 这个按需配置就是利用了之前所学的条件装配规则!!!(@ConditionnalOnClass(使用者所需要导入的类.class) )
上图不难看出,默认导入的组件有127个之多。
自动配置中一些有趣的东西
1 2 3 4 5 6 7 8 9 10 @Bean @ConditionalOnBean({MultipartResolver.class}) @ConditionalOnMissingBean( name = {"multipartResolver"} ) public MultipartResolver multipartResolver (MultipartResolver resolver) { return resolver; }
SpringBoot默认会在底层配好所有的组件,但是如果用户自己配置了的话,则就以用户的优先
下面以字符配置(HttpEncodingAutoConfiguration)为例,并先对默认配置进行解释,再展示自我配置的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ServerProperties.class}) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({CharacterEncodingFilter.class}) @ConditionalOnProperty(prefix="server.servlet.encoding",value = {"enabled"},matchIfMissing = true) public class HttpEncodingAutoConfiguration { private final Encoding properties; public HttpEncodingAutoConfiguration (ServerProperties properties) { this .properties = properties.getServlet().getEncoding(); } @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter () { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this .properties.getCharset().name()); filter.setForceRequestEncoding(this .properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.REQUEST)); filter.setForceResponseEncoding(this .properties.shouldForce(org.springframework.boot.web.servlet.server.Encoding.Type.RESPONSE)); return filter; } }
从上面的源码中我们可以得知HttpEncodingAutoConfiguration是一个配置类,且其开启了类ServerProperties的配置绑定,并将之加入于容器中,然后我们的是Web项目,且是类型为Servlet的,又因为自动导入了SpringMVC,因此对应的CharacterEncodingFilter类也存在,最后的注解标识是实不实现都可以,因此满足自动配置此类的条件,则可以继续往下执行!
它的构造函数会从刚刚加入到容器中的ServerProperties组件获取,然后把一些内容交予本类的成员properties,之后的characterEncodingFilter()这个函数,则利用该成员进行字符编码的设置!!由此可见,若想通过DIY方式配置SpringBoot的环境,可以通过修改配置文件(即application.properties)来实现;
当然也可以通过接下来的手段实现,因为在进行组件的注册时,其有条件装配规定的约束,当容器中无该类才执行,即若是使用者自行注册,则不会再次于其中注册 ,此即满足了用户优先原则,且也为我们DIY配置环境提供了一个方法,就是自己定义配置类进行注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController public class HelloController { @RequestMapping("/hello") public String handle01 (@RequestParam("name") String name) { return "Hello,SpringBoot2!" + name; } } 我们通过查找HttpEncodingAutoConfiguration类发现其prefix为server.servlet.encoding,然后在application.properties中进行修改,将之修改为server.servlet.encoding.charset=ISO-8859 -1 则通过浏览器进行输入测试,会发现,已乱码为:Hello,SpringBoot2!?? 由此可见,配置文件DIY便利着实有效! @Bean public CharacterEncodingFilter characterEncodingFilter () {return null ;}
总结:
xxxAutoConfiguration(自动配置类) —> 导入了一大堆组件 —> 通过xxxProperties去获取值 —> 通过application.properties去重设置值
SpringBoot编写逻辑
引入对应的场景依赖
查看自动配置了哪些组件
自行分析,引入场景对应的自动配置一般都生效了
配置文件中debug=true开启自动配置报告 Negative(不生效) / Positive(生效)
是否需要修改
参照文档修改配置项
自行分析,xxxProperties绑定的配置文件的前缀,然后找到注入的部分
自定义加入或者替换组件
自定义器xxxCustomizer(目前IDon’tKnow)
…
开发小技巧-Lombok
Lombok这个东西可用于简化JavaBean的开发
安装依赖及插件的过程如下:
1 2 3 4 5 1.依赖安装<dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > </dependency >
2.插件安装,通过Settings>Plugins下搜索lombok进行安装即可!
lombok的几个重要注解:
@Getter/@Setter:作用于类上,生成所有成员变量的get/set方法;作用于成员变量上,则只对该变量生成get/set方法。可以设置访问权限(@Getter(value=“AccessLevel.PUBLIC”))和是否懒加载。
@ToString:会自动给Javabean的成员变量们构造toString函数,可以通过of/exclude来指定/排除某些成员变量生成于toString方法中。
@EqualsAndHashCode:生成equals和hashcode方法。
@NoArgsConstructor:自动给Javabean创造无参构造器
@AllArgsConstructor:自动给Javabean创造全参构造器
@RequiredArgsConstructor:生成包含final和@NonNull注解的成员变量构造器
@Data:@Getter+@Setter+@ToString+@EqualsAndHashCode+@RequiredArgsConstructor
@Builder:作用于类上,快速地为类实现建造者模式。可以链式赋值(初始化的时候),若是需要修改要么通过set,要么在实体类的@Builder(toBuilder=true),但它会返回一个全新的对象。
1 User user = User.builder().id(1 ).username("ayy" ).build();
注:若需部分成员构造器,则可以自行编写或利用IDEA的自动编写功能
此物需要增加依赖于pom.xml中,用于制造伪热更新(实质通过restart形式实现,而热更新是通过reload形式实现),以下是其依赖:
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency >
当我们修改我们的代码文件后,不需要通过关闭再打开项目来实现刷新,而直接通过Ctrl + F9重新编译项目即可,然后就实现了刷新!
如果想要使用真正的热更新,可以付费购买插件JRebel。
开发小技巧-Spring Initializer
创建SpringBoot项目通过File>New>Project>Spring Initializer以GUI界面创建SpringBoot,其中要啥starter就自行勾选啥,然后创建的时候要联网,会自动帮你导jar包(就是自动添加依赖啦)。
不过一开始关于mvn和.gitignore不会用到,删去即可。
而在src下,我们可以看到src>main>java + resources。在resources目录下,可以看到application.properties + static(包) + templates(包)
static包用于存储静态资源,如css 、js;templates包用于存放页面
配置文件-yaml用法
基本语法
key: value #k,v之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
‘#’表示注释
‘’ 与 “” 表示字符串内容,会比如 转义/不转义
数据类型
字面量:单个的、不可再分的值 Date、Boolean、String、number、null
对象:键值对的集合。 map、hash、set、object
1 2 3 4 5 6 行内写法: k: {k1:v1,k2:v2,k3:v3} 或 k: k1: v1 k2: v2 k3: v3
数组:一组按次序排列的值。array、list、queue
1 2 3 4 5 6 行内写法: k: [v1,v2,v3] 或 k: - v1 - v2 - v3
配置文件-自定义类绑定的配置提示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 添加如下依赖:<dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency > 在build>plugins下的spring-boot-maven-plugin插件内添加如下配置,以在打包时舍弃之,减少包的大小<configuraion > <excludes > <exclude > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuraion-processor</artifactId > </exclude > </excludes > </configuraion >
Web-静态资源规则与定制化
1. 静态资源目录
只要静态资源放在类路径下:
/static
(or/public
or/resources
or/META-INF/resources
)
则可以直接通过:当前项目根路径/ + 静态资源名 进行访问
原理:静态映射/**,即拦截所有的请求,而controller也是如此。
在运行的时候,请求进来,先去找controller看能不能处理,不能处理的所有请求则交给静态资源处理器,若是静态资源处理器也无法处理则报告404
改变默认的静态资源路径:
1 2 3 spring: resources: static-locations: [classpath:/xxx/ ]
2. 静态资源访问前缀
默认无前缀。
之所以要使用这个东西是因为,如果是一个web项目,需要登录后才可以执行某一些操作,若是拦截器拦截/**,则静态资源也会被拦截。为了拦截器可以放行静态资源,因此可以通过静态资源加上访问前缀来过滤掉它们。
可以通过如下方式在配置文件中设置:
1 2 3 spring: mvc: static-path-pattern: /res/**
3. 支持webjar静态资源访问
webjar即是将如css、js等文件通过jar包的形式给出,可通过依赖获得
获取webjar
1 2 3 4 5 6 <dependency > <groupId > org.webjars</groupId > <artifactId > jquery</artifactId > <version > 3.5.1</version > </dependency >
访问形式:localhost:8080/webjars/…(资源详细路径)
Web-welcome与favicon功能
1. 欢迎页支持
用于直接ip:port访问项目,会显示index.html欢迎页
静态资源路径下放置index.html
可以配置静态资源路径
不可以配置静态资源访问前缀,否则会导致index.html不能默认访问
2. 自定义Favicon
用于更改web项目的小图标,这个在静态资源路径下放置favicon.ico即可,需注意浏览器缓存可能导致的无法显示。同样的也不可以配置静态资源的访问前缀 ,否则会导致其失效
静态资源配置原理
SpringBoot启动默认加载 xxxAutoConfiguration类(自动配置类)
SpringMVC功能的自动配置类 WebMvcAutoConfiguration生效
比如:OrderedHiddenHttpMethodFilter(用来兼容rest风格,表单可以提交PUT、DELETE等)、OrderedFormContentFilter(表单内容过滤器),然后有一个叫做WebMvcAutoConfigurationAdapter这么一个配置类(那么它肯定也在容器中)。接下来研究一下它:
1 2 3 4 5 @Configuration(proxyBeanMethods = false) @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class}) @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
先看到配置文件,它让WebMvcProperties和ResourceProperties跟对应的配置文件绑定。spring.mvc==WebMvcProperties 、spring.resources==ResourceProperties
1. 配置类只有一个有参构造器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public WebMvcAutoConfigurationAdapter (ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this .resourceProperties = resourceProperties; this .mvcProperties = mvcProperties; this .beanFactory = beanFactory; this .messageConvertersProvider = messageConvertersProvider; this .resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this .dispatcherServletPath = dispatcherServletPath; this .servletRegistrations = servletRegistrations; }
资源处理的默认规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void addResourceHandlers (ResourceHandlerRegistry registry) { if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); } else { Duration cachePeriod = this .resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this .resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**" )) { this .customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**" }).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/" }).setCachePeriod(this .getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this .mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this .customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this .resourceProperties.getStaticLocations())).setCachePeriod(this .getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } }
欢迎页的处理规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this .getWelcomePage(), this .mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(this .getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(this .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()); this .setRootViewName("forward:index.html" ); } else if (this .welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index" ); this .setRootViewName("index" ); } }
请求参数处理
请求映射(这里说的不是RequestMapping,而是Rest风格的请求映射注解)
@xxxMapping
Rest风格支持*(使用HTTP请求方式动词 来表示对资源的操作)*
以前是通过:/getUser获取用户 /deleteUser删除用户 /editUser修改用户 /saveUser保存用户
现在是通过: /user (就只这一个访问路径)
GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
核心Filter:HiddenHttpMethodFilter
用法: 表单method=post , 隐藏域type=hidden,_method=PUT
需注意还需要手动开启:spring.mvc.hiddenmethod.filter.enable=true;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @RequestMapping(value = "/user",method = RequestMethod.POST) public String PostUser () { return "Post User" ; } @RequestMapping(value = "/user",method = RequestMethod.DELETE) public String DeleteUser () { return "Delete User" ; } @RequestMapping(value = "/user",method = RequestMethod.PUT) public String PutUser () { return "Put User" ; } @RequestMapping(value = "/user",method = RequestMethod.GET) public String GetUser () { return "Get User" ; } 源码部分: @Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"}, matchIfMissing = false ) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter () { return new OrderedHiddenHttpMethodFilter(); }
Rest原理(表单提交,且需要使用REST时)
表单提交会带上_method=PUT
请求过来的时候会被HiddenHttpMethodFilter拦截
请求是否是POST,且是否正常
获取到_method的值
原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值(_method=XXX)
兼容以下请求:PUT DELETE PATCH等
过滤链放行的时候使用的是wrapper。以后调用的getMethod方法是调用requestWrapper的。
Rest使用客户端工具:
如postman直接发生put、delete等方式请求,无需filter重新包装
上述的@RequestMapping(value=“/user”,method=“RequestMethod.POST”)这些注解,可以更改为以下的注解(由上述注解合成而来):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @PostMapping("/user") public String PostUser () { return "Post User" ; }@DeleteMapping("/user") public String DeleteUser () { return "Delete User" ; }@PutMapping("/user") public String PutUser () { return "Put User" ; }@GetMapping("/user") public String GetUser () { return "Get User" ; }
**如何更改默认的_method为我们想要的名字呢?**请看以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter(); hiddenHttpMethodFilter.setMethodParam("_m" ); return hiddenHttpMethodFilter; } }
请求映射原理
普通参数与基本注解
@PathVariable、@RequestHeader、@RequestParam、@CookieValue、@RequestAttribute、@RequestBody、@MatrixVariable
WebRequest、ServletRequest、MultipartRequest、HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、Zoneld
Map、Model(map,model里面的数据会被放在request请求域中 即req.setAttribute(xxx))、Errors/BindingResult、RedirectAttributes(重定向携带数据 )、ServletResponse(Response )、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
可以自动类型转换和格式化,可以级联封装
1. 注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String userName, @PathVariable Map<String,String> pv) { Map<String,Object> map = new HashMap<>(); map.put("id" ,id); map.put("username" ,userName); map.put("pv" ,pv); return map; }public Map<String,Object> getCar (@RequestHeader("User-Agent") String userAgent,@RequestHeader Map<String,String> rh) { Map<String,Object> map = new HashMap<>(); map.put("userAgent" ,userAgent); map.put("rh" ,rh); return map; } @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String userName, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> rh, @RequestParam("age") Integer age, @RequestParam("interest") List<String> interests, @RequestParam Map<String,String> rp) { Map<String,Object> map = new HashMap<>(); map.put("age" ,age); map.put("interests" ,interests); map.put("rp" ,rp); return map; } @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String userName, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> rh, @RequestParam("age") Integer age, @RequestParam("interest") List<String> interests, @RequestParam Map<String,String> rp, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie) { Map<String,Object> map = new HashMap<>(); map.put("_ga" ,_ga); System.out.println(cookie.getName() + "-->" + cookie.getValue()); return map; } @PostMapping("/save") public Map getRequestBody (@RequestBody String content) { Map map = new HashMap<>(); map.put("内容" ,content); return map; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > com.ayy.test</title > </head > <body > <h1 > HelloIndexHtml</h1 > <form action ="/user" method ="get" > <input value ="GET-TYPE" type ="submit" /> </form > <form action ="/user" method ="post" > <input value ="POST-TYPE" type ="submit" /> </form > <form action ="/user" method ="post" > <input name ="_m" value ="DELETE" type ="hidden" /> <input value ="DELETE-TYPE" type ="submit" /> </form > <form action ="/user" method ="post" > <input name ="_m" value ="PUT" type ="hidden" /> <input value ="PUT-TYPE" type ="submit" /> </form > <ul > <a href ="car/3/owner/zhangsan?age=18&interest=basketball&interest=tennis" > car/{id}/owner/{username}</a > <li > @PathVariable 路径变量</li > <li > @RequestHeader 获取请求头</li > <li > @RequestParam 获取请求参数</li > <li > @CookieValue 获取Cookie值</li > <li > @RequestAttribute 获取request域属性</li > <li > @RequestBody 获取请求体</li > <li > @MatrixVariable 矩阵变量</li > </ul > <form action ="/save" method ="post" > <br /> <input type ="password" placeholder ="Pwd" name ="passwd" /> <br /> <input type ="text" placeholder ="Usr" name ="usrName" > <br /> <input type ="submit" value ="提交" > <br /> </form > <a href ="/cars/sell;price=300000;brand=BYD,AUTO,TESLA" > MatrixVariable111 /cars/sell;price=300000;brand=BYD,AUTO,TESLA</a > <a href ="/cars/boss;id=666/emp;id=888" > MatrixVariable222 /cars/boss;id=666/emp;id=888</a > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Controller public class RequestController { @GetMapping("/goto") public String gotoNextPage (HttpServletRequest request) { request.setAttribute("user" ,"张三" ); request.setAttribute("status" ,200 ); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public Map<String,Object> successPage (@RequestAttribute("user") String user, @RequestAttribute("status") Integer status, HttpServletRequest request) { Map<String,Object> map = new HashMap<>(); map.put("r_attribute:" ,user); map.put("r_servletRequset" ,request.getAttribute("user" )); return map; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @GetMapping("/cars/{path}") public Map getCarSell (@PathVariable("path") String path, @MatrixVariable("price") Integer price, @MatrixVariable("brand") List<String> brand) { Map<String,Object> map = new HashMap<>(); map.put("path" ,path); map.put("price" ,price); map.put("brand" ,brand); return map; } @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer() { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper=new UrlPathHelper(); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } }; } @GetMapping("/cars/{path1}/{path2}") public Map getCarDetail (@MatrixVariable(value = "id",pathVar = "path1") Integer bid, @MatrixVariable(value = "id",pathVar = "path2") Integer eid) { Map<String, Object> map = new HashMap<>(); map.put("bid" ,bid); map.put("eid" ,eid); return map; }
2. Servlet API
就如上面所说的那么多的Servlet API,是怎么通过Resolver(即参数解析器)来实现对应参数获取的?此外,注解获得的参数,也是通过参数解析器实现参数的获取。
以下以HttpServletRequest这个ServletAPI来展示如何通过参数解析器来获取之:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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;private <T> T resolveNativeRequest (NativeWebRequest webRequest, Class<T> requiredType) { T nativeRequest = webRequest.getNativeRequest(requiredType); if (nativeRequest == null ) { throw new IllegalStateException("Current request is not of type [" + requiredType.getName() + "]: " + webRequest); } else { return nativeRequest; } }
3. 复杂参数
对于复杂参数:Model、Map它俩存放的区域是request的请求域(渲染时存放的),即request attribute那个东西。以下例子通过直接转发来体现之:访问localhost:8080/params -> localhost:8080/success
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @GetMapping("/params") public String testParam (Map<String,Object> map, Model model, HttpServletRequest req, HttpServletResponse resp) { map.put("map1" ,"map content" ); model.addAttribute("md" ,"model content" ); req.setAttribute("req" ,"request content" ); Cookie cookie = new Cookie("c1" ,"v1" ); resp.addCookie(cookie); return "forward:/success" ; }@ResponseBody @GetMapping("/success") public Map<String,Object> successPage (@RequestAttribute(value ="user",required = false) String user, @RequestAttribute(value = "status",required = false) Integer status, HttpServletRequest request) { Map<String,Object> map = new HashMap<>(); map.put("r_attribute:" ,user); map.put("r_servletRequset" ,request.getAttribute("user" )); map.put("map1" ,request.getAttribute("map1" )); map.put("md" ,request.getAttribute("md" )); map.put("req" ,request.getAttribute("req" )); return map; }
4. 自定义对象的参数
它是通过一个叫“数据绑定”的东西:当页面提交的请求数据(GET、POST)都可以和对象属性进行绑定,包括级联绑定。以下是数据绑定的一个例子
1 2 3 4 5 @PostMapping("/saveUser") public Person saveUser (Person person) { return person; }
1 2 3 4 5 6 7 8 <form action ="/saveUser" method ="post" > 姓名:<input name ="userName" value ="zhangsan" /> <br /> 年龄:<input name ="age" value ="18" /> <br /> 生日:<input name ="birth" value ="2000/1/5" /> <br /> 宠物姓名:<input name ="pet.name" value ="cat" /> <br /> 宠物年龄:<input name ="pet.age" value ="5" /> <br /> <input type ="submit" > </form >
响应处理
响应JSON
jackson.jar + ResponseBody
1 2 3 4 5 6 7 8 9 10 11 <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.7.RELEASE</version > <scope > compile</scope > </dependency >
可以通过jackson.jar包和@ResponseBody注解返回给前端json数据:
1 2 3 4 5 6 7 8 @Autowired Person person;@ResponseBody @GetMapping("/response/test") public Person person () { return this .person; }
那么这个返回值是如何变成了json数据的格式呢?
先前说过ArgumentResolver参数解析器,在确定方法的参数值的时候,会用各种参数解析器来确定;
而现在有ReturnValueHandler,故可知springmvc对返回值的所有解析也是采取了返回值解析器的方法。
ReturnValueHandler原理
HTTPMessageConverter原理
内容协商原理
基于请求参数的内容协商原理
自定义MessageConverter
浏览器与PostMan内容协商完全适配
视图解析与模板引擎