javase: OOP
mysql:持久化
html+css+js+jquery+框架:视图,框架不熟练,css不好;
javaweb:独立开发MVC三层架构的网站了:原始
ssm :框架:简化了我们的开发流程,配置也开始较为复杂;
war: tomcat运行
spring再简化:SpringBoot - jar:内嵌tomcat;微服务架构!
服务越来越多: springcloud;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6B6Rh6v4-1655994880247)(assets/1655689129434.png)]
1、SpringBoot简介 1.1、回顾什么是SpringSpring是一个开源框架,2003 年兴起的一个轻量级的Java 开发框架,作者:Rod Johnson 。
Spring是为了解决企业级应用开发的复杂性而创建的,简化开发。
1.2、Spring是如何简化Java开发的为了降低Java开发的复杂性,Spring采用了以下4种关键策略:
1、基于POJO的轻量级和最小侵入性编程,所有东西都是bean;
2、通过IOC,依赖注入(DI)和面向接口实现松耦合;
3、基于切面(AOP)和惯例进行声明式编程;
4、通过切面和模版减少样式代码,RedisTemplate,xxxTemplate;
1.3、什么是SpringBoot学过javaweb的同学就知道,开发一个web应用,从最初开始接触Servlet结合Tomcat, 跑出一个Hello Wolrld程序,是要经历特别多的步骤;后来就用了框架Struts,再后来是SpringMVC,到了现在的SpringBoot,过一两年又会有其他web框架出现;你们有经历过框架不断的演进,然后自己开发项目所有的技术也在不断的变化、改造吗?建议都可以去经历一遍;
言归正传,什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。
所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。
是的这就是Java企业级应用->J2EE->spring->springboot的过程。
随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;
Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。
简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。
Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。
Spring Boot的主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
真的很爽,我们快速去体验开发个接口的感觉吧!
1.4、微服务 1.4.1、什么是微服务? 微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成―系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构。
1.4.2、单体应用架构 所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中。无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war包内。
- 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
- 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。
all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡。
所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制。
这样做的好处是:
1.节省了调用资源。
2.每个功能元素的服务都是一个可替换的、可独立升级的软件代码。
Martin Flower于2014年3月25日写的《Microservices》,详细的阐述了什么是微服务。
- 原文地址: http://martinfowler.com/articles/microservices.html
- 翻译:https://www.cnblogs.com/liuning8023/p/4493156.html
一个大型系统的微服务架构,就像一个复杂交织的神经网络,每一个神经元就是一个功能元素,它们各自完成自己的功能,然后通过http相互请求调用。比如一个电商系统,查缓存、连数据库、浏览页面、结账、支付等服务都是一个个独立的功能服务,都被微化了,它们作为一个个微服务共同构建了-个庞大的系统。如果修改其中的一个功能,只需要更新升级其中一个功能服务单元即可。
但是这种庞大的系统架构给部署和运维带来很大的难度。于是,spring为我们带来了构建大型分布式微服务的全套、全程产品:
- 构建一个个功能独立的微服务应用单元,可以使用springboot,可以帮我们快速构建一个应用;
- 大型分布式网络服务的调用,这部分由spring cloud来完成,实现分布式;
- 在分布式中间,进行流式数据计算、批处理,我们有spring cloud data flow。
- spring为我们想清楚了整个从开始构建应用到大型分布式应用全流程方案。
我们将学习如何快速的创建一个Spring Boot应用,并且实现一个简单的Http请求处理。通过这个例子对Spring Boot有一个初步的了解,并体验其结构简单、开发快速的特性。
我的环境准备:
- java version “1.8.0_181”
- Maven-3.6.1
- SpringBoot 2.x 最新版
开发工具:
- IDEA
换下载源
在custom里写http://start.aliyun.com
Spring官方提供了非常方便的工具让我们快速构建应用
Spring Initializr:https://start.spring.io/
项目创建方式一:使用Spring Initializr 的 Web页面创建项目 1、打开 https://start.spring.io/
2、填写项目信息
3、点击”Generate Project“按钮生成项目;下载此项目
4、解压项目包,并用IDEA以Maven项目导入,一路下一步即可,直到项目导入完毕。
5、如果是第一次使用,可能速度会比较慢,包比较多、需要耐心等待一切就绪。
项目创建方式二:使用 IDEA 直接创建项目 1、创建一个新项目
2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现
3、填写项目信息
4、选择初始化的组件(初学勾选 Web 即可)
5、填写项目路径
6、等待项目构建成功
2.1.3、项目结构分析:通过上面步骤完成了基础项目的创建。就会自动生成以下文件。
1、程序的主启动类
2、一个 application.properties 配置文件
3、一个 测试类
4、一个 pom.xml
2.1.3.1、pom.xml 分析打开pom.xml,看看Spring Boot项目的依赖:
org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin
如上所示,主要有四个部分:
- 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括: groupld、 artifactld、 version、name、 description等
- parent:继承spring-boot-starter-parent的依赖管理,控制版本与打包等内容
- dependencies:项目具体依赖,这里包含了spring-boot-starter-web用于实现HTTP接口(该依赖中包含了Spring MVC),官网对它的描述是:使用Spring MVC构建Web(包括RESTful)应用程序的入门者,使用Tomcat作为默认嵌入式容器。; spring -boot-starter-test用于编写单元测试的依赖包。更多功能模块的使用我们将在后面逐步展开。
- build:构建配置部分。默认使用了spring-boot-maven-plugin,配合spring-boot-starter-parent就可以把Spring Boot应用打包成JAR来直接运行。
1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到
2、在包中新建一个HelloController类
//本身就是Spring的一个组例
@RestController
public class HelloController {
//接口:http://localhost:8080/hello
@RequestMapping("/hello")
public String hello(){
return "hello,world";
}
}
3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jBROm9PM-1655994880250)(assets/1655696006836.png)]
2.4、将项目打成jar包,点击 maven的 package[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qqu2L7eo-1655994880250)(assets/微信图片_20220620113453.jpg)]
如果遇到以上错误,可以配置打包时 跳过项目运行测试用例
org.apache.maven.plugins maven-surefire-plugin true
如果打包成功,则会在target目录下生成一个 jar 包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-piPWTy0W-1655994880251)(assets/1655696210653.png)]
打成了jar包后,就可以在任何地方运行了!OK
2.5、更改启动图案如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案;
只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。
图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!
3、运行原理探究我们之前写的HelloSpringBoot,到底是怎么运行的呢,Maven项目,我们一般从pom.xml文件探究起;
3.1、pom.xml 3.1.1、父依赖其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!
org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE
点进去,发现还有一个父依赖
org.springframework.boot spring-boot-dependencies 2.2.5.RELEASE ../../spring-boot-dependencies
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;
以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
3.1.2、启动器 spring-boot-starter
org.springframework.boot
spring-boot-starter
springboot-boot-starter-xxx:就是spring-boot的场景启动器
spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件;
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter;
3.2、主启动类//@SpringBootApplication 来标注一个主程序类,说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
3.2.1、@SpringBootApplication
作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
进入这个注解:可以看到上面还有很多其他注解!
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
3.2.1.1、@ComponentScan
这个注解在Spring中很重要 ,它对应XML配置中的元素。
作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中
3.2.1.2、@SpringBootConfiguration作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;
我们继续进去这个注解查看
@SpringBootConfiguration - @Configuration Spring配置类 - @Component 说明这是spring的组件
这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;
里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!
我们回到 SpringBootApplication 注解中继续看。
3.2.1.3、@EnableAutoConfiguration(1)@EnableAutoConfiguration :开启自动配置功能
以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
点进注解接续查看:
@AutoConfigurationPackage :自动配置包
@Import({Registrar.class})
public @interface AutoConfigurationPackage {}
@import :Spring底层注解@import , 给容器中导入一个组件
Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;
这个分析完了,退到上一步,继续看
(2)**@Import({AutoConfigurationImportSelector.class}) **:给容器导入组件 ;
AutoConfigurationImportSelector :自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:
1、这个类中有一个这样的方法
// 获得候选的配置 protected ListgetCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { //这里的getSpringFactoriesLoaderFactoryClass()方法 //返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration List configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."); return configurations; }
2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法
public static ListloadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); //这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); }
3、我们继续点击查看 loadSpringFactories 方法
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { //获得classLoader,我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //将读取到的资源遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
4、发现一个多次出现的文件:spring.factories,全局搜索它
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UzIIR47-1655994880252)(assets/1655713346351.png)]
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!
WebMvcAutoConfiguration
我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p96sVvaM-1655994880252)(assets/1655713696201.png)]
可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!
所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
结论:
- SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
- 将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
- 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;
- 容器中也会存在非常多的xAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration , JavaConfig!
- 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
**简而言之:**springboot所有自动配置都是在启动的时候扫描并加载: spring.factories所有的自动配置类都在这里面,但是不一定生效要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!
3.3、SpringApplicationSpringApplication.run分析
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
SpringApplication这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
查看构造器:public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
run方法流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dc5Wwn13-1655994880252)(assets/640.jpg)]
关于SpringBoot,谈谈你的理解:- 自动装配
- run()
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
-
application.properties
-
- 语法结构 :key=value
-
application.yml
-
- 语法结构 :key:空格 value
**配置文件的作用 :**修改SpringBoot自动配置的默认值,因为SpringBoot在底层都给我们自动配置好了;
比如我们可以在配置文件中修改Tomcat 默认启动的端口号!测试一下!
server.port=80814.1.2、yaml概述
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
这种语言以数据作为中心,而不是以标记语言为重点!
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
传统xml配置:
8081
yaml配置:
server: prot: 80804.1.3、yaml基础语法
说明:语法要求严格!
(1)空格不能省略
(2)以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。
(3)属性和值的大小写都是十分敏感的。
(1)字面量:普通的值 [ 数字,布尔值,字符串 ]字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
注意:
-
“ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;
比如 :name: “kuang n shen” 输出 :kuang 换行 shen
-
‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出
比如 :name: ‘kuang n shen’ 输出 :kuang n shen
#对象、Map格式 k: v1: v2:
在下一行来写对象的属性和值得关系,注意缩进;比如:
student: name: qinjiang age: 3
行内写法
student: {name: qinjiang,age: 3}
(3)数组( List、set )
用 - 值表示数组中的一个元素,比如:
pets: - cat - dog - pig
行内写法
pets: [cat,dog,pig]4.2、注入配置文件
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
4.2.1、在springboot项目中的resources目录下新建一个文件 application.yml 4.2.2、编写一个实体类 Dog;@Component //注册bean到容器中
public class Dog {
private String name;
private Integer age;
//有参无参构造、get、set方法、toString()方法
}
4.2.3、思考,我们原来是如何给bean注入属性值的!@Value,给狗狗类测试一下:
@Component //注册bean到容器中
public class Dog {
@Value("旺财")
private String name;
@Value("3")
private Integer age;
//有参无参构造、get、set方法、toString()方法
}
4.2.4、在SpringBoot的测试类下注入狗狗输出一下;
@Autowired
private Dog dog;
@Test
void contextLoads() {
System.out.println(dog);
}
结果成功输出,@Value注入成功,这是我们原来的办法对吧。
4.2.5、我们在编写一个复杂一点的实体类:Person 类@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map maps;
private List
4.2.6、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!
person:
name: kuangshen
age: 4
happy: false
birth: 2020/02/02
maps: {k1: v1,k2: v2}
lists:
- music
- movie
- girl
dog:
name: 旺财
age: 3
4.2.7、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map maps;
private List
4.2.8、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5WdLMsJy-1655994880253)(assets/1655775348386.png)]
点击Open Documentation实际上是让配置一个依赖,点开应该是这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEQWACdN-1655994880254)(assets/640-1655775856605.jpg)]
4.2.9、确认以上配置都OK之后,我们去测试类中测试一下:org.springframework.boot spring-boot-configuration-processor true
@Autowired
private Person person;
@Test
void contextLoads() {
System.out.println(person);
}
yaml配置注入到实体类完全OK!
4.3、加载指定的配置文件配置文件除了yml还有我们之前常用的properties,properties配置文件在写中文的时候,会有乱码,我们需要去IDEA中设置编码格式为UTF-8;
settings–>FileEncodings中配置;
4.3.1、我们去在resources目录下新建一个yang.properties文件name=kuangshen4.3.2、然后在我们的代码中指定加载yang.properties文件
@Component
//javaconfig绑定我么配置文件的值,可以采取这些方式!
@PropertySource("classpath:yang.properties")// 加载指定的配置文件,不常用
public class Person {
//SpEL表达式取出配置文件的值
@Value("${name}")
private String name;
}
4.3.3、再次输出测试一下:指定配置文件绑定成功!
4.4、对比小结
@Value这个使用起来并不友好!我们需要为每个属性单独注解赋值,比较麻烦;我们来看个功能对比图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F1AoySlP-1655994880258)(assets/640.png)]
(1)@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
(2)松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下
dog: first-name: 阿黄 age: 5
@Component //注册bean到容器中
@ConfigurationProperties(prefix = "dog")
public class Dog {
private String firstName;
private Integer age;
//有参无参构造、get、set方法、toString()方法
}
测试输出:
Dog(firstName=阿黄, age=5)
(3)JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性
(4)复杂类型封装,yml中可以封装对象 , 使用value就不支持
结论:
配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
4.5、配置文件占位符person:
name: kuangshen${random.uuid}
age: ${random.int}
happy: false
birth: 2020/02/02
maps: {k1: v1,k2: v2}
lists:
- music
- movie
- girl
dog:
name: ${person.hello:hehe}_旺财
# 表示如果person.hello存在,则dog.name=person.hello_旺财,否则为冒号后面的值,即hehe_旺财
# 如person.name是存在的,则dog.name=kuangshen${random.uuid}_旺财
age: 3
测试输出:
Person(
name=kuangshen7401628a-fbe8-43d0-b8c3-54905c0bf974,
age=-675946025,
happy=false,
birth=Sun Feb 02 00:00:00 CST 2020,
maps={k1=v1, k2=v2},
lists=[music, movie, girl],
dog=Dog(name=hehe_旺财, age=3)
)
如果是:
person:
hello: world
dog:
name: ${person.hello:hehe}_旺财
# 表示如果person.hello存在,则dog.name=person.hello_旺财,否则为冒号后面的值,即hehe_旺财
# 如person.name是存在的,则dog.name=kuangshen${random.uuid}_旺财
age: 3
则输出:
Person(
dog=Dog(name=world_旺财, age=3)
)
5、JSR303数据校验及多环境切换
5.1、JSR303数据校验
先看看如何使用
导入依赖(2.2以上版本的都需要导入):
org.springframework.boot spring-boot-starter-validation
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;
@Component
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message = "邮箱格式错误")
private String name;
}
运行测试:
报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKYPbdvq-1655994880260)(assets/1655781863364.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pXmccxtm-1655994880261)(assets/1655781997606.png)]
使用数据校验,可以保证数据的正确性;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65n45QNF-1655994880261)(assets/1655782764097.png)]
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
5.2、多环境切换
5.2.1、配置文件加载位置
外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置!
官方外部配置文件说明参考文档
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zohitAsm-1655994880262)(assets/640-1655784024095.png)]
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件 优先级2:项目路径下配置文件 优先级3:资源路径下的config文件夹配置文件 优先级4:资源路径下配置文件 注:数字越小优先级越高
优先级由高到低,高优先级的配置会覆盖低优先级的配置;
5.2.2、多配置文件我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件;
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试; #我们启动SpringBoot,就可以看到已经切换到dev下的配置了; spring.profiles.active=dev5.2.3、yaml的多文档块
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev #配置环境的名称
---
server:
port: 8084
spring:
profiles: prod #配置环境的名称
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
6、自动配置原理我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
注:在低版本的spring-boot的spring.factories才能找到,如spring-boot 2.0
//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration
//启动指定类的ConfigurationProperties功能;
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class})
//Spring底层@Conditional注解
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})
//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
//如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final Encoding properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpProperties properties) {
this.properties = properties.getEncoding();
}
//给容器中添加一个组件,这个组件的某些值需要从properties中获取
@Bean
@ConditionalOnMissingBean //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
return filter;
}
//。。。。。。。
}
一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!
- 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
- 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
- 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
- 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
// .....
}
我们去配置文件里面试试前缀,看提示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqPSlDcU-1655994880262)(assets/640-1655799728257.jpg)]
这就是自动装配的原理!
精髓 (1)SpringBoot启动会加载大量的自动配置类
(2)我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;
(3)我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)
(4)给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;
**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件
xxxxProperties:封装配置文件中相关属性;
了解:@Conditional了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;
@Conditional派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1AL2mj5-1655994880263)(assets/640-1655799840604.jpg)]
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
我们怎么知道哪些自动配置类生效?
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;
#开启springboot的调试类 debug=true
Positive matches:(自动配置类启用的:正匹配)
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
Unconditional classes: (没有条件的类)
7、SpringBoot Web开发问题:
-
springboot到底帮我们配置了什么?
-
我们能不能进行修改?
-
能修改哪些东西?
-
能不能扩展?
首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!
写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?
如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!
我们先来聊聊这个静态资源映射规则:
SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一个方法:addResourceHandlers 添加资源处理
@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
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.
我们来仔细对照,看一下它怎么实现的,它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
ContentNegotiatingViewResolver 内容协商视图解析器自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
我们去看看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
我们可以点进这类看看!找到对应的解析视图的代码;
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
// .....
}
我们继续点进去看,他是怎么获得候选的视图的呢?
getCandidateViews中看到他是把所有的视图解析器拿来,进行while循环,挨个解析
Iterator var5 = this.viewResolvers.iterator();
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
我们再去研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!
protected void initServletContext(ServletContext servletContext) {
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
}
// ...............
}
既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?
我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;我们去实现一下
(1)我们在我们的主程序中去写一个视图解析器来试试;
//如果。你想diy一些定制化的功能,只要写这个组件,然后将它交给springboot,springboot就会帮我们自动裴配!
//扩展springMvc
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//ViewResolver实现了视图解析器按口的类,我们就可以把它看做视图解析器
@Bean
public ViewResolver myViewResolver(){
return new MyViewResolver();
}
//自定义了一个自已的视图解析器MyViewResolver
public static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
(2)怎么看我们自己写的视图解析器有没有起作用呢?
我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0dDoN2R3-1655994880265)(assets/1655873441095.png)]
3、我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;
找到this,找到视图解析器,我们看到我们自己定义的就在这里了;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nfb3xMl9-1655994880265)(assets/1655873525679.png)]
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!
转换器和格式化器找到格式化转换器:
@Bean
@Override
public FormattingConversionService mvcConversionService() {
// 拿到配置文件中的格式化规则
WebConversionService conversionService =
new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
点击去:
private String dateFormat;
public String getDateFormat() {
return this.dateFormat;
}
可以看到在我们的Properties文件中,我们可以进行自动配置它!
如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
spring.mvc.date-format=9.2、修改SpringBoot的默认配置
这么多的自动配置,原理都是一样的,通过这个WebMVC的自动配置原理分析,我们要学会一种学习方式,通过源码探究,得出结论;这个结论一定是属于自己的,而且一通百通。
SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
扩展使用SpringMVC 官方文档如下:
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
我们要做的就是编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;
//因为类型要求为WebMvcConfigurer,所以我们实现其接口
//可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 浏览器发送/test , 就会跳转到test页面;
registry.addViewController("/test").setViewName("test");
}
}
我们去浏览器访问一下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NEQr0u3A-1655994880266)(assets/1655894838342.png)]
确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!
我们可以去分析一下原理:
(1)WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter
(2)这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)
(3)我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个父类:DelegatingWebMvcConfiguration
这个父类中有这样一段代码:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
(4)我们可以在这个类中去寻找一个我们刚才设置的viewController当做参考,发现它调用了一个
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
(5)我们点进去this.*.addViewControllers看一下
public void addViewControllers(ViewControllerRegistry registry) {
Iterator var2 = this.delegates.iterator();
while(var2.hasNext()) {
// 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
delegate.addViewControllers(registry);
}
}
所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;
9.3、全面接管SpringMVC官方文档:
If you want to take complete control of Spring MVC you can add your own @Configuration annotated with @EnableWebMvc.
全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!
只需在我们的配置类中要加一个@EnableWebMvc。
我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;
不加注解之前,访问首页:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOHJwbng-1655994880266)(assets/1655895772796.png)]
给配置类加上注解:@EnableWebMvc
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mJqpsf1-1655994880266)(assets/1655895535862.png)]
我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;
当然,我们开发中,不推荐使用全面接管SpringMVC
思考问题?为什么加了一个注解,自动配置就失效了!我们看下源码:
(1)这里发现它是导入了一个类,我们可以继续进去看
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
(2)它继承了一个父类 WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
(3)我们来回顾一下Webmvc自动配置类
@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 {
}
总结一句话:@EnableWebMvc将WebMvcConfigurationSupport组件导入进来了;
而导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能!
在SpringBoot中会有非常多的扩展配置,只要看见了这个,我们就应该多留心注意~
10、案例:员工管理系统 10.1、准备工作静态资源导入:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKdpLz9t-1655994880267)(assets/1655907324999.png)]
编写实体类:
- 部门表
//部门表
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
- 员工表
//员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; // 0:女 1:男
private Department department;
private Date birth;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.department = department;
this.birth = new Date();
}
}
编写Dao:
- 部门Dao
@Repository
public class DepartmentDao {
//模拟数据库中的数据
private static Map departments = null;
static {
departments = new HashMap();//创建一个部门表
departments.put(101,new Department(101,"教学部"));
departments.put(102,new Department(102,"市场部"));
departments.put(103,new Department(103,"教研部"));
departments.put(104,new Department(104,"运营部"));
departments.put(105,new Department(105,"后勤部"));
}
//获得所有部门信息
public Collection getDepartments(){
return departments.values();
}
//通过id得到部门
public Department getDepartmentById(Integer id){
return departments.get(id);
}
}
- 员工Dao
@Repository
public class EmployeeDao {
//模拟数据库中的数据
private static Map employees = null;
//员工所属部门
@Autowired
private DepartmentDao departmentDao;
static {
employees = new HashMap();//创建一个员工表
employees.put(1001,new Employee(1001,"AA","A110@163.com",0,new Department(101,"教学部")));
employees.put(1002,new Employee(1002,"BB","B110@163.com",1,new Department(102,"市场部")));
employees.put(1003,new Employee(1003,"CC","C110@163.com",0,new Department(103,"教研部")));
employees.put(1004,new Employee(1004,"DD","D110@163.com",1,new Department(104,"运营部")));
employees.put(1005,new Employee(1005,"EE","E110@163.com",0,new Department(105,"后勤部")));
}
//主键自增
private static Integer initId = 1006;
//增加一个员工
public void save(Employee employee){
if (employee.getId()==null){
employee.setId(initId++);
}
employee.setDepartment(departmentDao.getDepartmentById(employee.getDepartment().getId()));
employees.put(employee.getId(),employee);
}
//查询全部员工信息
public Collection getAll(){
return employees.values();
}
//通过id查询员工
public Employee getEmployeeById(Integer id){
return employees.get(id);
}
///删除员工通过id
public void delete( Integer id){
employees.remove( id);
}
}
10.2、首页实现
方式一:编写Controller
@Controller
public class IndexController {
@RequestMapping({"/","/index.html"})
public String index(){
return "index";
}
}
方式二:扩展mvc配置
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
}
通过访问http://localhost:8080/index.html或者http://localhost:8080都可以顺利访问到。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzOGI5Oy-1655994880267)(assets/1655909148169.png)]
继续优化:
在templatesindex.html引入thymeleaf:
并修改本地链接:
同时将404.html、dashboard.html、list.html都引入thymeleaf并修改相关的本地链接
使用@{}好处:
server.servlet.context-path=/kuang
当修改配置时也会一直生效。
注意点: 所有页面的静态资源都需要使用thymeleaf接管——@{}
10.3、页面国际化有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化了!
10.3.1、准备工作先在IDEA中统一设置properties的编码问题!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mM382xjp-1655994880267)(assets/1655947623056.png)]
编写国际化配置文件,抽取页面需要显示的国际化页面消息。我们可以去登录页面查看一下,哪些内容我们需要编写国际化的配置!
10.3.2、配置文件编写1、我们在resources资源文件下新建一个i18n目录,存放国际化配置文件
2、建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作;文件夹变了!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HGP7MzoW-1655994880268)(assets/640-1655954842933.png)]
3、我们可以在这上面去新建一个文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbh12v9W-1655994880268)(assets/640-1655954858971.png)]
弹出如下页面:我们再添加一个英文的;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adQyRa9C-1655994880269)(assets/640-1655954873887.png)]
这样就快捷多了!
4、接下来,我们就来编写配置,我们可以看到idea下面有另外一个视图;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nj8nEQIw-1655994880269)(assets/640-1655954918564.png)]
这个视图我们点击 + 号就可以直接添加属性了;我们新建一个login.tip,可以看到边上有三个文件框可以输入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWeAf4BM-1655994880269)(assets/640-1655954938182.png)]
我们添加一下首页的内容!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqAoOIf5-1655994880269)(assets/640-1655954967874.png)]
然后依次添加其他页面内容即可!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qik9YQHL-1655994880270)(assets/640-1655954982852.png)]
然后去查看我们的配置文件;
login.properties :默认
login.btn=登录 login.password=密码 login.remember=记住我 login.tip=请登录 login.username=用户名
英文:login_en_US.properties
login.btn=sign in login.password=Password login.remember=Remember me login.tip=Please sign in login.username=Username
中文:login_zh_CN.properties
login.btn=登录 login.password=密码 login.remember=记住我 login.tip=请登录 login.username=用户名
OK,配置文件步骤搞定!
配置文件生效探究我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration
里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;
// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
// 设置国际化文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(
StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;
spring.messages.basename=i18n.login配置页面国际化值
去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。我们去页面测试下:
IDEA还有提示,非常智能的!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqiQr9mZ-1655994880270)(assets/640-1655955949360.jpg)]
我们可以去启动项目,访问一下,发现已经自动识别为中文的了!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FYeclSIj-1655994880270)(assets/1655955988242.png)]
但是我们想要更好!可以根据按钮自动切换中文英文!
配置国际化解析在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
// 容器中没有就自己配,有的话就用用户配置的
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
// 接收头国际化分解
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
AcceptHeaderLocaleResolver 这个类中有一个方法:
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
// 默认的就是根据请求头带来的区域信息获取Locale进行国际化
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!
我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!
修改一下前端页面的跳转连接:
中文 English
我们去写一个处理的组件类!
//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
//如果请求链接不为空
if (!StringUtils.isEmpty(language)){
//分割请求参数
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean;
//注册自定义的国际化组件
@Bean
public LocaleResolver localeResolver(){//注意:该bean名必须为localeResolver,否则不生效
return new MyLocaleResolver();
}
测试运行成功。
原理:该bean名必须为localeResolver 在WebMvcAutoConfig中:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
这里有一个注解:@ConditionalOnMissingBean
也就是说,如果容器中没有LocaleResolver 才从这里获取。
而我们自定义了LocaleResolver,假如名为MyLocaleResolver,目的是使用自定义的来替代。
而上面在注入容器时,bean的名称是myLocaleResolver,它不是LocaleResolver。所以并不会使用自定义的,所以无效。
修改index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XOuHqqnQ-1655994880271)(assets/1655957485560.png)]
编写Controller
@Controller
public class LoginController {
@RequestMapping("/user/login")
//@ResponseBody//保证返回的是字符串
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model){
//具体的业务
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
}else {
//告诉用户你登陆失败了
model.addAttribute("msg","用户名或密码错误!");
return "index";
}
}
}
启动测试。
失败:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4fWQrva-1655994880271)(assets/1655957829103.png)]
成功:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Ap4dR3j-1655994880272)(assets/1655957859797.png)]
但是此时url会有账号密码的显示,不安全:
优化:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/main.html").setViewName("dashboard");
}
//具体的业务
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
return "redirect:/main.html";
}else {
//告诉用户你登陆失败了
model.addAttribute("msg","用户名或密码错误!");
return "index";
}
重新启动,登录成功url则为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzAsNPXd-1655994880272)(assets/1655958187713.png)]
但此时直接访问main.html也能进入dashboard页面,所以需要配置拦截器。
10.5、拦截器自定义一个拦截器:
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功以后,应该有用户的session
Object loginUser = request.getSession().getAttribute("loginUser");
if (loginUser==null){//没有登录
request.setAttribute("msg","没有权限,请先登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}else {
return true;
}
}
}
在上述的LoginController里设定session,这样拦截器就可以获取到session
@Controller
public class LoginController {
@RequestMapping("/user/login")
//@ResponseBody//保证返回的是字符串
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session){
//具体的业务
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//return "dashboard";//当username不为空且密码为123456是跳转到dashboard.html页面
session.setAttribute("loginUser",username);
return "redirect:/main.html";
}else {
//告诉用户你登陆失败了
model.addAttribute("msg","用户名或密码错误!");
return "index";
}
}
}
在MyMvcConfig里配置拦截器,及重写addInterceptors方法
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHandlerInterceptor())
.addPathPatterns("/**")//表示所有路径都拦截
.excludePathPatterns("/index.html","/","/user/login",
"/css/**","/img/**","/js/**");//设置放行的路径
}
启动测试即可,此时直接访问http://localhost:8080/kuang/main.html会被拦截。
修改Dashboard的左上角账号显示:
修改前:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgeNqsf3-1655994880272)(assets/1655973712424.png)]
Company name 把Company name换成[[${session.loginUser}]] [[${session.loginUser}]]
修改后:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pl5HVoSr-1655994880273)(assets/1655973771322.png)]
10.6、CRUD 10.6.1、展示员工列表编写EmployeeController
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
@RequestMapping("/emps")
public String list(Model model){
Collection employees = employeeDao.getAll();//获取全部员工信息
model.addAttribute("emps",employees);//将employees返回到前端
return "emp/list";
}
}
将在templates下创建emp目录,将list.html移进emp中
将dashboard.html和list.html
全部修改为:
提取dashboard.html和list.html两个页面的公共部分
在templates创建commonscommons.html,将两者导航栏和侧边栏放进去
在两者侧边栏和导航栏相应位置加上公共部分的代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYp5lKqP-1655994880273)(assets/1655979087260.png)]
侧边栏点击哪个选项哪个选项就高亮设置:
commons.html
改为: 改为:
dashboard.html改为:
list.html改为:
Java相关栏目本月热门文章
- 1【Linux驱动开发】设备树详解(二)设备树语法详解
- 2别跟客户扯细节
- 3Springboot+RabbitMQ+ACK机制(生产方确认(全局、局部)、消费方确认)、知识盲区
- 4【Java】对象处理流(ObjectOutputStream和ObjectInputStream)
- 5【分页】常见两种SpringBoot项目中分页技巧
- 6一文带你搞懂OAuth2.0
- 7我要写整个中文互联网界最牛逼的JVM系列教程 | 「JVM与Java体系架构」章节:虚拟机与Java虚拟机介绍
- 8【Spring Cloud】新闻头条微服务项目:FreeMarker模板引擎实现文章静态页面生成
- 9JavaSE - 封装、static成员和内部类
- 10树莓派mjpg-streamer实现监控及拍照功能调试
- 11用c++写一个蓝屏代码
- 12从JDK8源码中看ArrayList和LinkedList的区别
- 13idea 1、报错java: 找不到符号 符号: 变量 log 2、转换成Maven项目
- 14在openwrt使用C语言增加ubus接口(包含C uci操作)
- 15Spring 解决循环依赖
- 16SpringMVC——基于MVC架构的Spring框架
- 17Andy‘s First Dictionary C++ STL set应用
- 18动态内存管理
- 19我的创作纪念日
- 20Docker自定义镜像-Dockerfile
热门相关搜索路由器设置 木托盘 宝塔面板 儿童python教程 心情低落 朋友圈 vim 双一流学科 专升本 我的学校 日记学校 西点培训学校 汽修学校 情书 化妆学校 塔沟武校 异形模板 西南大学排名 最精辟人生短句 6步教你追回被骗的钱 南昌大学排名 清朝十二帝 北京印刷学院排名 北方工业大学排名 北京航空航天大学排名 首都经济贸易大学排名 中国传媒大学排名 首都师范大学排名 中国地质大学(北京)排名 北京信息科技大学排名 中央民族大学排名 北京舞蹈学院排名 北京电影学院排名 中国戏曲学院排名 河北政法职业学院排名 河北经贸大学排名 天津中德应用技术大学排名 天津医学高等专科学校排名 天津美术学院排名 天津音乐学院排名 天津工业大学排名 北京工业大学耿丹学院排名 北京警察学院排名 天津科技大学排名 北京邮电大学(宏福校区)排名 北京网络职业学院排名 北京大学医学部排名 河北科技大学排名 河北地质大学排名 河北体育学院排名



