文章目录
- SpringBoot
- 系统配置要求
- 第一个Hello
- 1. 版本继承父工程
- 2. 添加web相关依赖
- 3. 编写所需要的主程序controller
- 4. 打包
- 5. 部署
- 容器功能
- 组件添加
- 1、@Configuration& @Bean
- 2、proxyBeanMethods的差别
- 3、使用@Import(Class<?>[])
- 4、条件装配@Conditional
- 5、@ImportResource迁移在xml配置的bean
- 配置绑定
- 方式一、@Component & @ConfigurationProperties
- 方式二、@EnableConfigurationProperties
- 自动配置原理
- @SpringBootConfiguration
- @ComponentScan
- @EnableAutoConfiguration
- AopAutoConfiguration自动配置分析
- DispatcherServletAutoConfiguration自动配置分析
- 总结
微服务是一种架构风格,将一个应用拆分为一组小型服务,每个服务运行在自己的进程里,也就是可独立部署和升级,且去中心化,服务自治也就是不同的服务可以用不同的语言和不同的存储技术。
springboot 写一个微服务
sprngcloud 将很多个微服务整合起来
spring data flow 负责传输微服务之间的数据信息
云原生 cloud native
- 服务自愈:服务器在炸掉的时候,自动部署另外一台
- 弹性伸缩: 需求增多的时候,部署的服务器自动扩充,当需求变少,再自动恢复原来的部署情况
- 服务隔离:一台服务器上有许多的服务,但是A服务发生故障不要影响其他的服务
- 自动化部署:在云上自动部署
- 灰度发布:当新版本不稳定,先在一个服务器上部署,如果确定了api都没啥问题,再在提供相同服务的服务器上逐渐部署
- 流量治理:由于服务器性能不一样,可以限制被访问的流量,以及监测
SpringBoot
springboot1 jdk7以下
springboo2 jdk8以上
- 支持jdk8的新特性
- lambda表达式
- stream api
- 函数式接口
- …
- 响应式编程
- HTTP2
- …
因此需要时刻关注springboot版本的变化
系统配置要求
- Java8+
- Maven 3.3+
- 尽量配好aliyun镜像
- jdk1.8版本
- idea 2019.1.2
检查环境 mvn -v
第一个Hello
1. 版本继承父工程
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
原理:
可以看到,我们自己所创建的项目是继承了父项目spring-boot-starter-parent
spring-boot-starter-parent 中又继承了spring-boot-dependencies
而在spring-boot-dependencies里包含了几乎所有的常用jar包版本号以及springboot当前版本号所依赖的jar包
spring-boot-starter-* ,其中*代表某个场景,代表只要引入starter,这个场景所需要的依赖就会被引用
见到 *-spring-boot-starter,就是第三方为我们提供的简化开发的场景启动器
因此在依赖管理部分,
引入依赖默认都可以不写版本,
引入非版本仲裁的jar,要写版本号,也就是自动版本仲裁机制,但是如果想使用特定的版本驱动,就在pom.xml声明即可
所有的springboot的自动配置功能都在这个包里面
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.4</version>
<scope>compile</scope>
</dependency>
2. 添加web相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3. 编写所需要的主程序controller
//主程序类,表示这事一个springboot应用
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
@RestController
public class HelloController {
@RequestMapping("/hello")
public String handle01() {
return "Hello,Spring Boot 2!";
}
}
4. 打包
到这里已经成功运行了,通过spring提供的方法,创建一个可执行的jar包,其中包含了整个运行环境,此时这个spring提供的方法需要添加一个插件
注意:添加一个version与自己的SpringBoot的版本号要一致,我这里是2.5.4
<build>
<plugins>
<plugin>
<version>2.5.4</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在maven管理的插件使用clear+package进行打包
5. 部署
可以看到打好的jar包
然后就可以通过该命令,进行部署
java -jar jar包名
容器功能
组件添加
使用到的注释
@Configuration(proxyBeanMethods = false)
@Bean("id")
@Import(Class<?>[])
@Conditional
1、@Configuration& @Bean
对比Spring和SpringBoot注册组件的差别
- 使用Spring配置文件配置
- 创建beans.xml的Spring配置文件
- 在该文件下注册
<bean id="user01" class="com.lee.boot.bean.User">
<property name="name" value="zhangsan"></property>
<property name="age" value="18"></property>
</bean>
<bean id="cat" class="com.lee.boot.bean.Pet">
<property name="name" value="tomcat"></property>
</bean>
- 使用@Configurationg告知这是个配置文件
- 添加@Configuration
- 配置bean
- 返回类型 = 上面的class = “com.lee.boot.bean.Pet”
- tomcatPet = 上面的id = “cat” ,这里还可以在@Bean中自定以bean的名,优先于方法名
- 返回值就是组件在容器中的实例
@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
因此在SpringBoot中创建一个MyConfig类来管理组件
2、proxyBeanMethods的差别
- @configuration告诉SpringBoot当前类是配置文件,可以在里面通过@Bean配置组件
- 全配置Full
- @Configuration(proxyBeanMethods = true)
- 从容器中通通过代理类去找组件,因此已经保存了,不用再通过bean去new一个新对象
- 轻量级配置Lite
- @Configuration(proxyBeanMethods = true)
- 容器不会再去保留代理对象,每一次的调用都会产生一个新的代理,新的代理去找组件的时候又产生一个新方法,新方法又返回new
来一个测试对比区别
@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(name);
}
System.out.println("==============测试用run.getBean来调用注册的bean============");
Pet tom1 = run.getBean("tom", Pet.class);
Pet tom2 = run.getBean("tom", Pet.class);
System.out.println("是否相同!"+ (tom1==tom2));
System.out.println("==============测试用myConfig来调用注册的bean============");
//如果proxybeanmehtods = true 那么获取的代理对象,调用方法,如果有就直接拿,如果没有再注册
//反正就是springboot总会检查这个组件在容器中的实例,本质是因为是通过代理对象去调用方法,如果不用代理对象的话,就会每次都去创建一个
MyConfig myConfig = run.getBean("myConfig", MyConfig.class);
Pet pet1 = myConfig.tomcatPet();
Pet pet2 = myConfig.tomcatPet();
System.out.println("是否相同!"+(pet1==pet2));
}
}
总结:
false:就代表不会在容器中进行组件检查,整个springboot启动就会变的非常的快,因此如果不存在组件依赖的情况,一般都调成false,对应的就是lite轻量级配置
true:就代表每一次调用都需要在容器中找,是不是有这个组件,保证依赖的组件就是容器中的组件这样启动的慢,因此如果存在这个组件还要被别的使用,就要开启代理这样对应的就是full全配置
在我理解就是一个是单例模式,一个是原型模式
3、使用@Import(Class<?>[])
在配置文件使用@Import可以向容器中创建指定类型的组件
@Import({User.class,Pet.class})
测试在容器中是否存在该类型的bean
String[] userTypes = run.getBeanNamesForType(User.class);
for (String type : userTypes) {
System.out.println(type);
}
String[] petTypes = run.getBeanNamesForType(Pet.class);
for (String petType : petTypes) {
System.out.println(petType);
}
4、条件装配@Conditional
作用:spring4加载的特性,按照一定的条件进行判断,满足条件就给容器注册bean
应用背景:
- 对于组件依赖的问题,就比如在这上面的例子中,程序中都没有user,那么就user自然也不需要一个pet,因此就需要用到条件装配
- 反之,如果pet都没有,这个bean没有怎么能注入到user里呢,如果真发生了,在之前还会空指针,其实也就是springboot自动装配的原理
@ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean
使用@conditional,介个写的非常详细
Spring @Conditional注解 详细讲解及示例
- 标记在方法上
一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入 - 标记在类上
一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。
5、@ImportResource迁移在xml配置的bean
在会被SpringBoot扫描到配置文件的开头加入@ImportResource
@ImportResource("classpath:xxxxxxxbeans.xml")
配置绑定
方式一、@Component & @ConfigurationProperties
意思就是在连接数据库的时候,一般会写一个.properties存放连接数据库所需要的信息。
如果想让.properties中文件的内容与bean的属性进行绑定,那么就可以通过以下方式
首先一个来配置信息的文件
mycar.brand=BYD
mycar.price=10000
再来一个Car类
@lombok
@Component
@ConfigurationProperties(prefix = "mycar")
//这里就会自动扫描配置文件的前缀为mycar的,然后进行属性匹配
public class Car {
private String brand;
private Integer price;
}
方式二、@EnableConfigurationProperties
应用背景:当需要引用第三方的jar包的时候,不能把别人的加入一个@component,那么就在自己定义的配置文件类MyConfig中将需要把配置属性的功能打开。
//1.开启car的属性配置功能 = @ConfigurationProperties
//2.MyConfig自身就是一个组件 = @Component
//3.把这个Car自动注册到容器中
@EnableConfigurationProperties(Car.class)
自动配置原理
涉及到的注解
@SpringBootApplication
实则是由下面三个组合在一起的
@ComponentScan(“com.lee.boot”)
@EnableAutoConfiguration
@SpringBootConfiguration
那么这三个注解分别做了啥呢?
@SpringBootConfiguration
源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
可以看到里面有一个configuration,说明这是会被SpringBoot扫描成为一个配置类
@ComponentScan
没啥好说的貌似
@EnableAutoConfiguration
源码
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到这个注解是由@AutoConfigurationPackage @Import组合成的
- 来看自动配置包@AutoConfigurationPackage
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
能够看到自动配置包里有个@import,而import就是在容器中创建一个指定类型的bean,因此就是利用这个Registrar给容器中自动一系列组件
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));
}
}
得到主类的元信息,得到主类的包名,将指定的一个包下的组件导入进来,而这个指定的包就是Main程序,为啥是Main程序,是因为在头上加了EnableAutoConfiguration
- @Import({AutoConfigurationImportSelector.class})
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
实际位置是spring-boot-autoconfigure-2.5.4 jar包下的META-INF/spring.factories中的写死的127个需要加载的组件
虽然127个场景的所有自动配置在默认启动的全部默认自动加载,但是最终会按需配置因此就会用到之前提到的@ConditionalOnClass,就能够解决不同场景需要加载啥。
可能不太清楚具体流程,那么来看个例子8
AopAutoConfiguration自动配置分析
例子:分析一个aop是否能够在自动配置生效
1、首先能看到在AopAuto这个类下
- 首先有个注释@configuration,代表是一个配置类
- 其次@ConditionalOnPropert后面那部分就是是否存在一个为spring.aop.auto的配置为真,matchIfMissing代表从application.properties中读取某个属性值,如果该值为空,默认值为true
- 最后还有个proxyBeanMethods,其属性默认值是true,也就是说该配置类会被代理。如果true了就会被代理,其实是为了启动变得更快,false的时候不进容器,true的时候进容器,并且进容器前还需要经过一层代理,导致启动比false的时候慢了。因此设置为false的目的,就是容器启动更快了,对于那些不常用的bean完全可以设置为false。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"auto"},
havingValue = "true",
matchIfMissing = true
)
public class AopAutoConfiguration{
public AopAutoConfiguration() {
}
}
2、内部类AspectJAutoProxyingConfiguration
同样代表一个配置文件,且需要检查是否存在Advice.class这个类,然而这里的advice
不在,因此该配置不生效。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Advice.class})
static class AspectJAutoProxyingConfiguration {
AspectJAutoProxyingConfiguration() {
}
}
3、ClassProxyingConfiguration
- @ConditionalOnMissingClass({“org.aspectj.weaver.Advice”})也就是当系统中没有weaver.Advice这个类的时候就生效
- @ConditionalOnProperty 看配置文件中是否前缀为spring-aop,属性名为"proxy-target-class",的值为true,如果为true就生效。
在我这里是,
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "true",
matchIfMissing = true
)
static class ClassProxyingConfiguration {
ClassProxyingConfiguration() {
}
}
DispatcherServletAutoConfiguration自动配置分析
1、
1.配置优先级的
@AutoConfigureOrder(-2147483648)
2.代表是个配置文件
@Configuration(
proxyBeanMethods = false
)
3.如果存在servlet才生效,因为还可能遇到基于flux的web应用,到这里也生效
@ConditionalOnWebApplication(
type = Type.SERVLET
)
4.再看系统中有没有这个类型的DispatcherServlet,
@ConditionalOnClass({DispatcherServlet.class})
5.最后需要在ServletWebServerFactoryAutoConfiguration这个类配好了,
系统才会配置DispatcherServletAutoConfiguration,
也很好理解,web服务器都没有,你配置servlet也没用啊
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration{}
紧接着里边分别有两个类
2、DispatcherServletConfiguration
1.标明配置类
@Configuration(proxyBeanMethods = false)
2.要求DefaultDispatcherServletCondition生效
@Conditional({DispatcherServletAutoConfiguration.DefaultDispatcherServletCondition.class})
3.系统中有没有ServletRegistration的组件
@ConditionalOnClass({ServletRegistration.class})
4.这个enable有两个功能,一是配置绑定prefix = "spring.mvc"所有的配置文件
二放到容器中就会存在有这个WebMvcProperties的组件
@EnableConfigurationProperties({WebMvcProperties.class})
protected static class DispatcherServletConfiguration{
5.
在这里就能看到,他注册了一个叫dispatcherServlet的bean,
在第4步进行了配置文件绑定的那些信息,就被传入到了这个bean中,
最后再返回了一个dispatcherServlet,而这个就是spring mvc的核心
这不就是平时我们需要在xml中写入的dispatcher配置,然后放入容器中
但是在springboot的底层中,就把这个过程自动给你装配了。
@Bean(name = {"dispatcherServlet"})
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
同理,和配置上述的一样,配置了一个文件上传解析器
@Bean
1.系统中是否存在这个MultipartResolver类型的组件,这里是肯定有的
@ConditionalOnBean({MultipartResolver.class})
2.但是系统中可能并没有配置这个bean叫做“multipartResolver”的组件,这里如果也为真,继续执行
@ConditionalOnMissingBean(
name = {"multipartResolver"}
)
3.给@bean标注的方法传入了参数,这个参数的值就会从容器中找,就在容器中找一个类型为MultipartResolver的解析器
然后再直接返回给你,相当于使得避免了一些开发的不规范会导致的问题
public MultipartResolver multipartResolver(MultipartResolver resolver) {
return resolver;
}
总结
在springboot的自动配置中,如果用户没配比如servlet,resolver,或者characterEncoding之类的组件的话,框架自动给你配好
但是如果用户自定义配了,那么以用户的优先,这也就是那个conditional的特色之处。
- SpringBoot先加载所有的配置类 xxxxAutoConfiguration
- 配置类按照条件进行生效,默认都会绑定配置文件的值 xxxxProperties
- 生效的话就会配置很多组件
- 只要容器中有这些组件,就相当于这些功能就有了
- 只要有用户自己配置的,就以用户优先
- 用户直接自己@Bean替换底层组件
- 用户去看组件获取的配置文件中属性的什么值,那么就去修改即可
- 要么查官方文档
- 要么就去看底层信息
在缓存中,和这个CacheProperties进行了绑定
进入CacheProperties类定义中,发现他和spring.cache开头的配置文件属性名进行了绑定!
因此,俺们就可以在application.properties中开始以spring.cache开头的属性名进行配置了!