SpringBoot学习笔记02

1.JSR303数据校验及多环境切换

1.1 JSR303数据校验

springboot中可以使用@Validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理,我们这里写个注解,让我们的name只支持email格式

注意在这之前我们需要先导入Validated的依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-validation</artifactId>
  4. </dependency>
  1. //注册bean到容器中
  2. @Component
  3. @Data
  4. @AllArgsConstructor
  5. @NoArgsConstructor
  6. @ConfigurationProperties(prefix = "person")
  7. @Validated //数据校验
  8. public class Person {
  9. @Email(message="邮箱格式错误") //name必须是邮箱格式
  10. private String name;
  11. }

运行结果

Springboot学习笔记02 - 图1

使用数据校验,可以保证数据的正确性

常见参数:

  1. @NotNull(message="名字不能为空")
  2. private String userName;
  3. @Max(value=120,message="年龄最大不能查过120")
  4. private int age;
  5. @Email(message="邮箱格式错误")
  6. private String email;
  7. 空检查
  8. @Null 验证对象是否为null
  9. @NotNull 验证对象是否不为null, 无法查检长度为0的字符串
  10. @NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
  11. @NotEmpty 检查约束元素是否为NULL或者是EMPTY.
  12. Booelan检查
  13. @AssertTrue 验证 Boolean 对象是否为 true
  14. @AssertFalse 验证 Boolean 对象是否为 false
  15. 长度检查
  16. @Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
  17. @Length(min=, max=) string is between min and max included.
  18. 日期检查
  19. @Past 验证 Date Calendar 对象是否在当前时间之前
  20. @Future 验证 Date Calendar 对象是否在当前时间之后
  21. @Pattern 验证 String 对象是否符合正则表达式的规则
  22. .......等等
  23. 除此以外,我们还可以自定义一些数据校验规则

JSR303校验源码位置

Springboot学习笔记02 - 图2

1.2 多环境切换

profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境

多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml,用来指定多个环境版本

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境

application.properties

  1. #springboot的多环境配置,可以选择激活哪一个配置文件
  2. spring.profiles.active=test

Springboot学习笔记02 - 图3

appplication-dev.properties

  1. server.port=8081

application-test.properties

  1. server.port=8082

运行结果:

Springboot学习笔记02 - 图4

yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了

  1. server:
  2. port: 8081
  3. #选择要激活哪个环境块
  4. spring:
  5. profiles:
  6. active: prod
  7. ---
  8. server:
  9. port: 8082
  10. spring:
  11. profiles: dev #配置环境的名称
  12. ---
  13. server:
  14. port: 8083
  15. spring:
  16. profiles: prod #配置环境的名称

运行结果

Springboot学习笔记02 - 图5

注意:如果yml和properties同时都配置了端口,并且没有激活其他环境,默认会使用properties配置文件的

配置文件加载位置

外部配置文件的方式非常多,我们选择最常用的即可,在开发的资源文件中进行配置

官方外部配置文件说明参考文档:https://docs.spring.io/spring-boot/docs/2.2.10.BUILD-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-spring-application

Springboot学习笔记02 - 图6

springboot启动会扫描以下位置的application.properties或application.yml文件作为springboot的默认配置文件

Springboot学习笔记02 - 图7

  • 优先级1:项目路径下的config文件夹配置文件
  • 优先级2:项目路径下配置文件
  • 优先级3:资源路径下的config文件夹配置文件
  • 优先级4:资源路径下配置文件

优先级从高到低,高优先级的配置会覆盖低优先级的配置

springboot会从这四个位置全部加载主配置文件,互补配置

我们在最低级的配置文件中设置一个项目访问路径的配置来测试互补问题

  1. #配置项目的访问路径
  2. server.servlet.context-path=/jcsune

扩展,运维小技巧

指定位置加载配置文件

我们还可以通过spring.config.location来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动 项目的时候,来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

  1. java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

2.自动配置原理

分析自动原理

以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理

  1. //表示这是一个配置类,和以前编写的配置文件一样, 可以给容器中添加组件
  2. @Configuration(proxyBeanMethods = false)
  3. //启动指定类的ConfigurationProperties功能
  4. //进入这个ServerProperties查看,将配置文件中对应的值和ServerProperties绑定起来
  5. //并把ServerProperties加入到IOC容器中
  6. @EnableConfigurationProperties(ServerProperties.class)
  7. //Spring的底层@Conditional注解:
  8. //根据不同的条件判断,如果满足的条件,整个配置类里面的配置就会生效
  9. //这里的意思就是判断当前应用是否是web应用,如果是,当前配置就会生效
  10. @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
  11. //判断当前项目又没有这个类:CharacterEncodingFilter.class SpringMVC中进行乱码解决的过滤器
  12. @ConditionalOnClass(CharacterEncodingFilter.class)
  13. //判断配置文件中是否存在某个配置:server.servlet.encoding
  14. //如果不存在,判断也是成立的
  15. //即使配置文件中不配置server.servlet.encoding.enabled=true,也是默认生效的
  16. @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
  17. public class HttpEncodingAutoConfiguration {
  18. //它已经和springboot的配置文件映射了
  19. private final Encoding properties;
  20. //只有一个参数的情况下,参数的值就会从容器中拿
  21. public HttpEncodingAutoConfiguration(ServerProperties properties) {
  22. this.properties = properties.getServlet().getEncoding();
  23. }
  24. //给容器中添加一个组件,这个组件的某些值需要从properties中获取
  25. @Bean
  26. @ConditionalOnMissingBean //判断容器中有没有这个组件
  27. public CharacterEncodingFilter characterEncodingFilter() {
  28. CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
  29. filter.setEncoding(this.properties.getCharset().name());
  30. filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
  31. filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
  32. return filter;
  33. }
  34. .....
  35. }

一句话总结:根据当前不同的条件判断,决定这个配置类是否生效

  • 一旦这个配置类生效,这个配置类就会给容器中添加各种组件
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的
  • 所有在配置文件中能配置的属性都是在xxxxxProperties类中封装着
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
  1. //从配置文件中获取指定的值很bean的属性进行绑定
  2. @ConfigurationProperties(
  3. prefix = "server",
  4. ignoreUnknownFields = true
  5. )
  6. public class ServerProperties {
  7. private Integer port;
  8. private InetAddress address;
  9. }

我们去配置文件里面试试前缀,看提示

Springboot学习笔记02 - 图8

这就是自动装配的原理

总结

  1. SpringBoot启动会加载大量的自动配置类
  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中
  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在于其中,我们就不需要再手动配置了)
  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,我们只需要在 配置文件中指定这些属性的值即可

xxxxxAutoConfigurartion:自动配置类,给容器中添加组件

xxxxxxProperties:封装配置文件中相关属性

了解:@Conditionl

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定 的条件下才能生效

使用Profile能根据不同的Profile进行条件装配,但是Profile控制比较糙,如果想要精细控制,例如,配置本地存储,AWS存储和阿里云存储,将来很可能会增加Azure存储等,用Profile就很难实现。

Spring本身提供了条件装配@Conditional,但是要自己编写比较复杂的Condition来做判断,比较麻烦。Spring Boot则为我们准备好了几个非常有用的条件:

  • @ConditionalOnProperty:如果有指定的配置,条件生效;
  • @ConditionalOnBean:如果有指定的Bean,条件生效;
  • @ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
  • @ConditionalOnMissingClass:如果没有指定的Class,条件生效;
  • @ConditionalOnWebApplication:在Web环境中条件生效;
  • @ConditionalOnExpression:根据表达式判断条件是否生效。

那么多的自动配置类,必须在一定的条件下才能生效,也就是说,我们加载了这么多的配置类,但不是所有的都生效了

那么我们怎么知道哪些自动配置类生效

我们可以启用debug=true属性,来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效

  1. debug: true

Positive matches :自动配置类启用的:正匹配

Springboot学习笔记02 - 图9

Negative matches: 没有启动,没有匹配成功的自动配置类:负匹配

Springboot学习笔记02 - 图10

Unconditional classes:没有条件的类

Springboot学习笔记02 - 图11

3.Web开发静态资源处理

3.1 Web开发探究

接下来进行SpringBoot于Web开发

其实SpringBoot的东西用起来非常简单,因为springboot最大的特点就是自动装配

使用SpringBoot的步骤

  1. 创建一个SpringBoot的应用,选择我们需要的模块,SpringBoot就会默认将我们的需要的模块自动配置好
  2. 手动在配置文件中配置部分配置项目就可以运行起来了
  3. 专注编写业务代码,不需要考虑以前那样一大堆配置了

3.2 静态资源处理

首先搭建一个普通的SpringBoot项目,回顾一下helloworld程序

写请求非常简单,如果我们要引入我们的前端资源,我们项目中就会有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢

如果我们是一个web应用,我们的main下会有一个webapp,以前我们是将所有的页面导入到这里面,但是我们现在的pom,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?答案是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的

静态资源映射规则:

SpringBoot中,springMVC的web配置都在WebMvcAutoConfiguration这个配置类里面;我们可以去看看WebMvcAutoConfigurationAdapter中有很多配置方法

有一个方法:addResourceHandlers 添加资源处理

  1. @Override
  2. public void addResourceHandlers(ResourceHandlerRegistry registry) {
  3. if (!this.resourceProperties.isAddMappings()) {
  4. //已禁用默认资源处理
  5. logger.debug("Default resource handling disabled");
  6. return;
  7. }
  8. //缓存控制
  9. Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
  10. CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
  11. //webjars配置
  12. if (!registry.hasMappingForPattern("/webjars/**")) {
  13. customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
  14. .addResourceLocations("classpath:/META-INF/resources/webjars/")
  15. .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
  16. .setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
  17. }
  18. //静态资源配置
  19. String staticPathPattern = this.mvcProperties.getStaticPathPattern();
  20. if (!registry.hasMappingForPattern(staticPathPattern)) {
  21. customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
  22. .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
  23. .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl)
  24. .setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
  25. }
  26. }

通过源代码,可以看出所有的/webjars/**,都需要去classpath:/META-INF/resources/webjars/找对应的资源

什么是webjars

webjars本质上就是以jar包的方式引入我们的静态资源,我们以前要入一个静态资源文件,直接导入即可

使用SpringBoot需要使用Webjars

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

要使用jQuery,我们只要引入jQuery对应版本的pom依赖即可

  1. <dependency>
  2. <groupId>org.webjars</groupId>
  3. <artifactId>jquery</artifactId>
  4. <version>3.5.1</version>
  5. </dependency>

导入完毕,查看webjars目录结构,并访问Jquery.js文件

Springboot学习笔记02 - 图12

访问:只要是静态资源,SpringBoot就会去对应的路径寻找资源我们这里访问:

http://localhost:8080/webjars/jquery/3.5.1/jquery.js

Springboot学习笔记02 - 图13

第二种静态资源映射规则

实际项目中要是使用自己的静态资源该怎么导入呢,

我们去找我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:

  1. // 进入方法
  2. public String[] getStaticLocations() {
  3. return this.staticLocations;
  4. }
  5. // 找到对应的值
  6. private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
  7. // 找到路径
  8. private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
  9. "classpath:/META-INF/resources/",
  10. "classpath:/resources/",
  11. "classpath:/static/",
  12. "classpath:/public/"
  13. };

ResourceProperties可以设置和我们静态资源有关的参数,这里面指向了它会去寻找资源的文件夹,即上面数组的内容,所以得出结论,以下四个目录存放的静态资源可以被我们识别

  1. "classpath:/META-INF/resources/"
  2. "classpath:/resources/"
  3. "classpath:/static/"
  4. "classpath:/public/"

我们可以在resource根目录下新建对应的文件夹,都可以存放我们的静态文件

优先级:resources>static>public

自定义静态资源路径

我们也可以自己通过配置文件来指定一下,哪些文件夹是需要我们放静态资源文件的,在application.properties中配置

  1. spring.resources.static-locations=classpath:/coding/,classpath:/jcsune/

一旦自己定义了静态文件夹的路径,原来的自动配置就都会失效了

3.3 首页处理

在源码中有一个欢迎页的映射,就是我们需要的首页

  1. @Bean
  2. public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
  3. FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
  4. WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
  5. new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
  6. this.mvcProperties.getStaticPathPattern());
  7. welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
  8. welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
  9. return welcomePageHandlerMapping;
  10. }

继续往下看

  1. private Optional<Resource> getWelcomePage() {
  2. String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
  3. // ::是java8 中新引入的运算符
  4. // Class::function的时候function是属于Class的,应该是静态方法。
  5. // this::function的funtion是属于这个对象的。
  6. // 简而言之,就是一种语法糖而已,是一种简写
  7. return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
  8. }
  9. // 欢迎页就是一个location下的的 index.html 而已
  10. private Resource getIndexHtml(String location) {
  11. return this.resourceLoader.getResource(location + "index.html");
  12. }

欢迎页,静态资源文件夹下的所有index.html页面;被/**映射

比如我访问 http://localhost:8080/ ,就会找静态资源文件夹下的 index.html