文章目录
微服务
-
什么是微服务
- 微服务是一种架构风格,它要求我们在开发一个应用的时候,这个应用必须构建成―系列小服务的组合;可以通过http的方式进行互通。要说微服务架构,先得说说过去我们的单体应用架构
-
单体应用架构
- 所谓单体应用架构(all in one)是指,我们将一个应用的中的所有应用服务都封装在一个应用中
- 无论是ERP、CRM或是其他什么系统,你都把数据库访问,web访问,等等各个功能放到一个war
- 这样做的好处是,易于开发和测试;也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了
- 单体应用架构的缺点是,哪怕我要修改一个非常小的地方,我都需要停掉整个服务,重新打包、部署这个应用war包。特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题
-
微服务架构
- all in one的架构方式,我们把所有的功能单元放在一个应用里面。然后我们把整个应用部署到服务器上。如果负载能力不行,我们将整个应用进行水平复制,进行扩展,然后在负载均衡
- 所谓微服务架构,就是打破之前all in one的架构方式,把每个功能元素独立出来。把独立出来的功能元素的动态组合,需要的功能元素才去拿来组合,需要多一些时可以整合多个功能元素。所以微服务架构是对功能元素进行复制,而没有对整个应用进行复制
- 这样做的好处是:
- 节省了调用资源
- 每个功能元素的服务都是一个可替换的、可独立升级的软件代码
第一个SpringBoot程序
-
创建一个springBoot项目,会自动生成以下文件
- 程序的主程序类
- 一个
application.properties
配置文件 - 一个测试类
-
生成的
XXApplication
和测试包下的XXXApplicationTests
类都可以直接运行来启动当前创建的项目 -
pom.xml
<!--有一个父项目--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.study</groupId> <artifactId>springboot</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--web依赖:tomcat,dispatcherServlet,xml--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-starter:所有的springboot依赖都是使用这个开头的--> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <!--打jar包插件--> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
- 项目元数据信息:创建时候输入的Project Metadata部分,也就是Maven项目的基本元素,包括: groupId、artifactId、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来直接运行
-
建相关包(必须建在
XXApplication
同级目录下) -
直接写一个controller类
@RestController public class HelloController { @RequestMapping("/test") public String hello(){ return "hello,world"; } }
-
直接运行,访问http://localhost:8080/test查看结果
-
用Lifecycle里的package可以实现对当前项目打包
原理初探
自动配置
pom.xml
-
spring-boot-dependencies:核心依赖在父工程中
-
我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,因为有这些版本仓库
-
对默认依赖版本不满意,可以加入自定义版本
<properties> <mysql.version>5.1.46</mysql.version> </properties>
启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
- 启动器:是springboot的启动场景
- starter可以理解成pom配置了一堆jar组合的空maven项目,用来简化maven依赖配置,starter可以继承也可以依赖于别的starter
- 比如spring-boot-starter-web,它就会帮我们自动导入web环境的所有依赖
- 见到 *-spring-boot-starter:第三方为我们提供的简化开发的场景启动器
- springboot会将所有的功能场景,都变成一个个的启动器
- 要使用什么功能,只需要找到对应的启动器就可以了
starter
主程序
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
-
主程序所在包及其下面的所有包里面的组件都会被默认扫描
- 想要改变扫描路径
@SpringBootApplication(scanBasePackages="com.xxx")
- 或者指定扫描:
@ComponentScan("com.xxx")
指定扫描路径
- 想要改变扫描路径
-
@SpringBootApplication
:标注这个类是一个springboot的应用- 等同于
@SpringBootConfiguration
+@EnableAutoConfiguration
+ComponentScan("com.xxx")
- 等同于
-
SpringApplication.run
:将springboot应用启动 -
关于@Configuration注解类需要被CGLIB代理的理解
@Configuration public class Test() { @Bean public Person p() { return new Person(); } @Bean public A a() { p(); return new A(); } }
- 首先有一个问题;如果先调用方法P再调用方法A,在不生成代理对象的时候,此时会创建两次Person对象?那么创建两次对象,即在spring里非单例对象,这样的话无法保证spring中配置类的属性单例原则
- 那么有没有一种可能,通过代理方法来管理person对象的创建,如果调用方法p()会交由代理去判断person对象是否已经被创建成功,如果是那么则交由代理对象的proxy.invokeSuper的方法调用父类去创建;如果没有则通过代理类$$BeanFactory.getBean的方法创建对象person,从而保证对象只被创建一次,即为单例
-
注解:
@SpringBootConfiguration
:springboot的配置@Configuration
:spring配置类,参数proxyBeanMethods默认为true,表示获取的对象为代理对象,多次调用,对象是同一个,springboot总会检查这个组件是否在容器中存在–保持组件单例。调成false,获取的对象不为代理对象,多次调用,对象不是同一个。两种配置:Full(全配置),Lite(轻量级)分别表示proxyBeanMethods设置为true和false。Lite模式下,配置类内部不能通过方法调用来处理依赖,否则每次生成的都是一个新实例而并非IoC容器内的单例,而且该模式下,配置类就是一普通类,所以@Bean方法可以使用private/final等进行修饰(static自然也是可以的)@Component
:说明配置类也是一个spring的组件
@EnableAutoConfiguration
:自动配置@AutoConfigurationPackage
:自动配置包@Import(AutoConfigurationPackages.Registrar.class)
:自动配置包注册,利用Registrar给容器中导入一系列组件,将指定的一个包下的所有组件导入进来:SpringbootApplication所在的包下
@Import(AutoConfigurationImportSelector.class)
:给容器中批量导入包@Import
作用:用来向容器中导入配置类或者一些需要前置加载的类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
:获取所有需要导入到容器中的配置类- 利用工厂加载
Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);
得到所有的组件,该方法是从META-INF/spring.factories位置来加载一个文件的,默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件 - 文件里面写死了spring-boot一启动就要给容器中加载的所有配置类
- 利用工厂加载
-
META-INF/spring.factories:自动配置的核心文件
-
结论:springboot所有自动配置都是在启动的时候扫描并加载:spring.factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功
-
步骤:
- springboot在启动的时候,从类路径下/META-INF/spring.factories获取指定的值
- 将这些自动配置的类导入容器,自动配置就会生效,帮我进行自动配置
- 以前我们需要手动配置的东西,现在springboot帮我们做了
- 它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器
- 整合javaEE,解决方案和自动配置的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下
- 容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件;并自动配置,@Configuration , JavaConfig
- 有了自动配置类,免去了我们手动编写配置文件的工作
-
SpringApplication.run分析
- 会开启一个进程
- 分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行
-
SpringApplication这个类主要做了以下四件事情
- 推断应用的类型是普通的项目还是Web项目
- 查找并加载所有可用初始化器,设置到initializers属性中
- 找出所有的应用程序监听器,设置到listeners属性中
- 推断并设置main方法的定义类,找到运行的主类
关于SpringBoot,谈谈你的理解
- 自动装配
- run()方法
- 判断是web项目还是普通项目
- 推断主类
- 开启监听器获取上下文处理bean
SpringBoot配置
配置文件
- SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的
- application.properties
- 语法结构 :key=value
- application.properties
- application.yml
- 语法结构 :key:空格 value
- **配置文件的作用 :**修改SpringBoot自动配置的默认值,因为默认值SpringBoot在底层都给我们自动配置好了
- 各种配置拥有默认值
- 默认配置最终都会映射到对象中
- 配置文件的值最终会绑定到每个类上,这个类会在容器中创建对象
yaml概述
-
YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)
-
这种语言以数据作为中心,而不是以标记语言为重点
-
yaml可以直接给实体类赋值
-
以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml
-
传统xml配置:
<server> <port>8081<port> </server>
-
yaml配置:
server: port: 8080
yaml基础语法
-
说明:语法要求严格!
- 空格不能省略,对空格(缩进)的要求很严格(python风格)
- 以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的
- 属性和值的大小写都是十分敏感的
# 普通key-value server: port: 8081 # 对象 student: name: cbc age: 3 # 行内写法 student: {name: cbc, age: 3} # 数组 pets: - cat - dog - pig pets: [cat, dog, pig]
注入配置文件
-
yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!
-
在springboot项目中的resources目录下新建一个文件 application.yml
-
编写一个实体类 Dog
-
原来我们是使用@value注解给属性注值
-
现在试着使用yaml配置方式
@Component //需要加上该注解,prefix值为yaml文件里的对象名称 @ConfigurationProperties(prefix = "dog") public class Dog { private String name; private int age;
# 对应prefix的值 dog: {name: cbc, age: 3}
-
pom里加上依赖就不会爆红,加不加不影响程序运行
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
-
-
也可以使用properties配置文件给对象赋值
name=cbc age=3
-
取值
@PropertySource(value = "classpath:cbc.properties") public class Dog { //SPEL表达式取出配置文件的值 @Value("${name}") private String name; @Value("${age}") private int age;
-
yaml取随机数
dog: name: ${random.uuid} age: ${random.int}
-
yaml默认值,存在取存在值,不存在取默认值
dog: name: ${dog.sex:hello} age: ${dog.sex:hello}_30
-
小结
- cp只需要写一次即可, value则需要每个字段都添加
- 松散绑定:比如yml中写的last-name,在类里属性名可以叫lastName,-后面跟着的字母默认是大写的。这就是松散绑定
- JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性
- 复杂类型封装,yml中可以封装对象,使用@value就不支持
- 配置yml和配置properties都可以获取到值 , 强烈推荐 yml
- 如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value
- 如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫
JSR303数据校验
-
如何使用
-
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式
@Component //注册bean @ConfigurationProperties(prefix = "person") @Validated //数据校验 public class Person { @Email(message="邮箱格式错误") //name必须是邮箱格式 private String name; }
-
-
常见参数
@NotNull(message="名字不能为空") private String userName; @Max(value=120,message="年龄最大不能查过120") private int age; @Email(message="邮箱格式错误") private String email; 空检查 @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=) string is between min and max included. 日期检查 @Past 验证 Date 和 Calendar 对象是否在当前时间之前 @Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则
多环境切换
-
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境
-
多配置文件
-
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本
-
例如:
-
application-test.properties 代表测试环境配置
-
application-dev.properties 代表开发环境配置
-
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件
-
-
我们需要通过一个配置来选择需要激活的环境:
# 比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试 # 我们启动SpringBoot,就可以看到已经切换到dev下的配置了 spring.profiles.active=dev
-
-
多文档块
-
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了
server: port: 8081 # 选择要激活哪个环境块 spring: profiles: active: prod #使用环境名为prod的配置 --- server: port: 8083 spring: profiles: dev # 配置环境的名称 --- server: port: 8084 spring: profiles: prod # 配置环境的名称
-
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件的!
-
-
配置文件加载位置
- 外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置
- file:./config/
- file:./
- classpath:/config/
- classpath:/
- springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
- 优先级1:项目路径下的config文件夹配置文件
- 优先级2:项目路径下配置文件
- 优先级3:资源路径下的config文件夹配置文件
- 优先级4:资源路径下配置文件
- 优先级由高到底,高优先级的配置会覆盖低优先级的配置
- SpringBoot会从这四个位置全部加载主配置文件;互补配置
- 外部加载配置文件的方式十分多,我们选择最常用的即可,在开发的资源文件中进行配置
-
拓展,运维小技巧
-
指定位置加载配置文件,我们还可以通过spring.config.location来改变默认的配置文件位置。项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=F:/application.properties
-