SpringBoot中的概念
依赖管理
SpringBoot项目自动引入依赖的原因是引用了一个父项目和starter场景启动器
场景启动器
依赖场景启动器才会将某个场景下的所有依赖引入,SpringBoot官方提供的启动器可从SpringBoot官方网站中查到,通常以spring-boot-starter-*
开头的都是官方提供的场景启动器,使用*-spring-boot-starter
结尾的都是第三方提供的场景启动器
版本控制
父项目又依赖与一个版本控制(spring-boot-dependencies
)的父项目,该项目中声明了几乎所有常用的jar包的版本号,所以官方场景启动器中无需声明依赖版本,对于非官方场景启动器一般要声明依赖版本
若想在当前项目中更改某个依赖的版本,覆盖版本控制的父项目配置即可,具体如下:
<properties>
<mysql.version>5.1.38</mysql.version>
</properties>
自动配置
在某个场景启动器下自动引入的依赖由SpringBoot自动配置该依赖,若想改变这些自动配置的默认值,就可以在SpringBoot的配置文件appllication.properties
中进行修改,并且这些配置都是按需加载的,只有引入某个场景启动器后配置才会生效
比如Web场景下就配置了如下:
- Tomcat:整合SpringMVC的中央调度器,字符编码过滤器等
- Spring注解扫描规则:默认是主程序所在的包以及所有子包
- SpringMVC全套常用功能:JSON解析等
- ...
配置文件的转变
配置类
使用配置类和常规配置文件代替Spring的XML配置,使用配置类代替XML的优势在于,配置更加灵活,可通过代码有选择的进行配置,以下这些注解其实都是SpringFramework中的注解
- 配置类就是使用
@Configuration
注解标注的类,对应XML配置文件中beans
标签,配置类本身也会被加入到IOC容器中 - 配置类中的方法使用
@Bean
注解标注,对应XML配置文件中的bean
标签
package com.config;
import com.entity.Student;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //标注该类为配置类
public class MyConfiguration {
@Value("${student.name}") //用于注入application.properties配置文件中内容的表达式语法
private String studentName;
@Bean //向IOC容器中添加的beanID默认是方法名,bean是该方法返回的对象
public Student student() { //该Bean在IOC容器中的ID就是该方法名
return new Student(this.studentName);
}
}
对应application.properties
文件中的内容如下
student.name=zhangsan
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
Student student = (Student) run.getBean("student");
System.out.println(student);
}
}
调用配置类的Bean方法
从容器中获取的配置类实例,默认模式(lite模式)下调用其中返回Bean方法是单例的,这种模式下在每次调用Bean方法时都会检查该容器中是否已经存在了该类型的Bean,可使用@Configuration
注解中的proxyBeanMethods
属性对该行为进行控制
@Configuration(proxyBeanMethods = false) //使用full模式
public class MyConfiguration {
@Bean
public Student student() {
return new Student();
}
}
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
MyConfiguration myConfiguration = (MyConfiguration) run.getBean("myConfiguration");
Student student1 = myConfiguration.student();
Student student2 = myConfiguration.student();
System.out.println(student1 == student2); //false
}
}
自动配置底层注解
配置绑定
使用@ConfigurationProperties
注解指定前缀进行配置绑定,对应appllication.properties
中定义的配置进行自动注入,使用@PropertySource
注解可指定其他非appllication.properties
名的文件,使用setter方法进行的注入,所以要保证该属性有setter方法
主动注册
将普通Bean加入到容器即可
@Component //也可以使用其他加入容器的注解
@ConfigurationProperties("student") //指定前缀
public class Student {
private String name;
//setter、getter和toString方法省
}
对应application.properties
文件中的内容如下
student.name=zhangsan
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
Student student = (Student) run.getBean("student");
System.out.println(student);
}
}
被动注册
普通Bean未加入到容器中,使用配置类将其加入到容器中
@ConfigurationProperties("student") //指定前缀,当前未加入容器
public class Student {
private String name;
//setter、getter和toString方法省
}
配置类如下
@Configuration
@EnableConfigurationProperties(Student.class) //若加入多个可使用数组方式
//该注解只能在配置类中使用,并且加入的Bean中必须有@ConfigurationProperties注解
public class MyConfiguration {
}
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
Student student = run.getBean(Student.class); //获取该类的全类名
System.out.println(student);
}
}
批量注册
将普通Bean加入到容器,属性是Map集合
@Component //也可以使用其他加入容器的注解
@ConfigurationProperties("student") //指定前缀
public class Student {
private Map map;
}
对应application.properties
文件中的内容如下
student.map[key1] = value1
student.map[1] = 1
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
Student student = (Student) run.getBean("student");
System.out.println(student);
}
}
配置提示
默认情况下,自定义配置绑定在IDEA中是没有代码提示的,若想要代码提示,首先将下面依赖加入到pom.xml
中,重启程序,版本由SpringBoot控制即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <!--保证optional为true才能开启-->
</dependency>
再将下面配置加入到SpringBoot打包插件中,打包时将该工具排除在外
<configuration>
<excludes>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</excludes>
</configuration>
配置导入
XML配置导入
使用@ImportResource
注解可向配置类中导入之前使用XML方式的配置文件的配置,比如
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class MyConfiguration {
}
Bean直接导入
使用@Import
注解可向配置类中导入一些Bean,使用该方式导入的BeanID默认是这些Bean的全类名,调用的是无参构造方法,所以要保证该类有无参构造
@Configuration
@Import(Student.class) //若导入多个可使用数组方式
public class MyConfiguration {
}
Bean批量导入
使用@Import
注解可向配置类中导入一个实现了ImportSelector
接口的类,就会调用其中selectImports()
方法返回的全类名数组,批量注册这些Bean
class MyImportSelector implements ImportSelector { //实现ImportSelector接口中的selectImports()方法
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这之中就可以从配置文件中读取到要导入的某些Bean的全类名
return new String[]{Student.class.getName()}; //返回要导入Bean的全类名
}
}
@Configuration
@Import(MyImportSelector.class) //根据MyImportSelector中selectImports()方法返回的Bean的全类名数组进行批量注入
public class MyConfiguration {
}
通用测试代码
在入口方法中进行如下测试
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args); //该方法会返回SpringBean工厂对象
Student student = (Student) run.getBean(Student.class.getName()); //获取该类的全类名
System.out.println(student);
}
}
条件注入
内置条件注解
条件注解 | 描述 |
---|---|
@ConditionalOnProperty |
当配置文件中有指定的配置时注入,name 属性指定key,havingValue 属性指定value,matchIfMissing 属性指定当无配置时自动注入 |
@ConditionalOnBean |
当IOC容器内存在指定的Bean时注入 |
@ConditionalOnMissingBean |
当IOC容器内不存在指定的Bean时注入 |
@ConditionalOnWebApplication |
当前项目是Web项目时注入 |
@AutoConfigureAfter |
当前Bean需要在该注解指定的Bean注入后才能注入 |
@AutoConfigureBefore |
当前Bean需要在该注解指定的Bean注入前才能注入 |
@AutoConfigureOrder |
指定Bean注入的顺序 |
@ConditionalOnClass |
当前classpath下存在某个类时注入 |
@ConditionalOnMissingClass |
当前classpath不存在某个类时注入 |
@ConditionalOnExpression |
基于SpELl表达式的判断条件 |
自定义条件注解
使用@Conditional
注解和Condition
接口配合生成自定义条件注解,具体如下
class MyCondition implements Condition { //实现Condition接口的matches方法
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//可根据context获取容器对象进行逻辑判断,常用的有如下两条:
// context.getEnvironment()获取application.properties配置文件,再调用getProperty()方法即可获取具体某个配置
// context.getBeanFactory()获取Bean工厂
//也可根据注解的获取到信息进行逻辑判断
return true; //返回true才会被注入到IOC容器中
}
}
@Configuration
public class MyConfiguration {
@Bean
@Conditional(MyCondition.class) //将条件类设置到条件注解上,只有当MyCondition类中的matches方法返回true才会被注入
public Student student() { //该Bean在IOC容器中的ID就是该方法名
return new Student("zhangsan");
}
}
自动配置原理
自动配置就是为了让第三方的配置类加载到IOC容器中,从而得到了自动配置的效果
- 这些配置类都是以用户的配置类优先的,也就是说用户配置了底层的自动配置类就会失效
- 这些配置类大部分都绑定了配置文件,也可以通过修改配置文件进行配置的修改
基于源码的理解
- 从入口类的
main()
方法开始,进入SpringApplication.run()
方法中(2.4.1版本进入三层)调用了this.refreshContext(context)
方法用来刷新容器,从而开始扫描入口类上的@SpringBootApplication
注解
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
- 扫描到
@SpringBootApplication
注解,该注解中包含下面三个主要注解
//省略...
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )}
)
public @interface SpringBootApplication {
//省略...
}
-
@SpringBootConfiguration
:表明该类是一个配置类,进入该注解可以看到,该注解其实就是@Configuration
注解 -
@ComponentScan
:指定包扫描的注解,只是扫描,下面默认两个扫描器,默认扫描的是主程序所在的包以及所有子包 -
@EnableAutoConfiguration
:进入该注解可以看到,又包含下面两个注解//省略... @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { //省略... }
-
@AutoConfigurationPackage
:进入该注解可以看到,又包含注解@Import(AutoConfigurationPackages.Registrar.class)
,进入AutoConfigurationPackages.Registrar.class
类中可以看到实现了registerBeanDefinitions()
方法//省略... @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); //该方法表示导入主程序所在的包以及所有子包中所有的Bean } //省略...
-
@Import(AutoConfigurationImportSelector.class)
:进入AutoConfigurationImportSelector
类中可以看到该类实现了selectImports()
方法//省略... @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //省略...
selectImports()
方法又调用getAutoConfigurationEntry()
方法获取自动配置类getAutoConfigurationEntry()
方法中又调用了getCandidateConfigurations()
方法获取所有候选配置类,进行处理返回需要加载的自动配置类getCandidateConfigurations()
方法又调用了SpringFactoriesLoader.loadFactoryNames()
方法获取候选配置的List集合SpringFactoriesLoader.loadFactoryNames()
中又调用了loadSpringFactories()
方法加载配置文件loadSpringFactories()
中又调用了classLoader.getResources(FACTORIES_RESOURCE_LOCATION)
方法加载配置文件FACTORIES_RESOURCE_LOCATION
常量值为META-INF/spring.factories
配置文件META-INF/spring.factories
配置文件出现在spring-boot-autoconfigure-xxx.jar
中,这之中内置了大部分场景的自动配置类的全类名;当然若引入其他场景也有对应的spring-boot-xxx-autoconfigure-xxx.jar
包,其中的META-INF/spring.factories
配置文件和自动配置的注解
-
手动实现自动配置
假设Student
是自动配置类
class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//这里可以从文件中读取配置类的全类名
return new String[]{Student.class.getName()};
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class) //使用@Import注解批量导入Bean
@interface MyEnableAutoConfiguration { //自定义@MyEnableAutoConfiguration注解导入配置类
}
@MyEnableAutoConfiguration
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = new SpringApplicationBuilder(Main.class)
.web(WebApplicationType.NONE) //不启动web服务器
.run(args);
Student student = (Student) run.getBean(Student.class.getName());
System.out.println(student);
}
}
Comments NOTHING