SpringBoot特性
1.SpringBoot的自动装配
1.1.Spring中如何使用注解注入Bean
首先,咱们看看Spring中有哪些注解可以注入Bean。
为了方便下面注解的测试,这里创建一个SpringBoot工程,并将SpringBoot启动类单独放在一个包(SpringbootDemo)里面。(放在项目的根目录下,SpringBoot正常进行自动装载,无法测试)
- 1.@Configuration + @Bean
public class UserBean {
}
@Configuration
public class ConfigurationTest {
@Bean
public UserBean userBean() {
return new UserBean();
}
}
- 2.@ComponentScan + @Component
@Component
public class ComponentTest {
}
@SpringBootApplication
@ComponentScan(basePackages = "com.shenlong")
public class springbootdemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(springbootdemoApplication.class, args);
ComponentTest bean = run.getBean(ComponentTest.class);
System.out.println(bean);
}
}
- 3.@Import 这里介绍两种@Import注解的使用方法,第二种方法尤为重要,是SprignBoot自动注入的关键。
- @Import({class列表})
public class TestA {
}
public class TestB {
}
//这里添加@Configuration注解,是为了让Spring加载当前类,才能扫描到@Import注解,@Import注解才能起作用
@Configuration
@Import({TestA.class, TestB.class})
public class importTest {
}
- @Import(实现ImportSelector接口的类)
public class TestC {
}
public class TestD {
}
public class ImportSelectorDemo implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.shenlong.importSelectorBean.TestC","com.shenlong.importSelectorBean.TestD"};
}
}
@Configuration
@Import(ImportSelectorDemo.class)
public class ImportDemo {
}
1.2.SpringBoot自动装配的原理
下面说说我理解的SpringBoot自动装配原理。
- 1.启动当前SpringBoot工程
- 2.加载@SpringBootApplication注解
- 3.@SpringBootApplication中包含了
- 1.@SpringBootConfiguration注解,相当于@Configuration注解
- 2.@EnableAutoConfiguration 自动注入的关键注解
- 3.@ComponentScan 扫描当前包以及子包中的注解,进行Bean的注入。
- 4.@EnableAutoConfiguration注解中包含了@Import(AutoConfigurationImportSelector.class), 仔细观察,不难发现,AutoConfigurationImportSelector实现了ImportSelector接口。也就是将selectorImports方法中返回的字符串数组的全类路径加载到IOC容器中。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
*** AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
- 5.getAutoConfigurationEntry()方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
*** List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
- 6.getCandidateConfigurations()方法
获取 META-INF/spring.factories文件中的以org.springframework.boot.autoconfigure.EnableAutoConfiguration为key的所有value的值。
这块内容涉及到SPI,后面会说。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
到此,需要自动加载的类知道了。
难道这些类都要一股脑注入到IOC容器中嘛,哪些类需要注入,哪些类不需要注入呢?
下面说说条件控制。
2.SpringBoot中如何通过条件控制Bean的注入
SpringBoot中的条件控制Bean注入使用过@ConditionalOnxxx这个注解实现的
下面以 SpringBoot中RedisTemplate类的注入说明条件控制。
现在我们启动了一个SpringBoot应用程序。
-
- ...,现在开始从META-INF/spring.factories文件中读取出需要注入类的全类路径,果不其然,找到了RedisAutoConfiguration的全类路径。
- 2.下面对RedisAutoConfiguration文件中的@Bean进行装载,发现文件中的类名为红色,也就是在当前程序中,加载不到这些类。尝试引入spring-boot-starter-data-redis这个jar包。
- 3.仔细看,这个类的注解中,有个@ConditionalOnClass(RedisOperations.class),其意思为,如果存在RedisOperations这个类,就对当前文件中的bean进行装载。
- 4.RedisOperations这个类所在的文件为,
- 6.所以,尽管SpringBoot项目在启动时,就想对redis进行自动装载操作,但是,如果没有引入spring-boot-starter-data-redis,不满足@ConditonOnClass(RedisOperatios.class)这个装载条件,无法进行装载。
问:有没有注意到,redis的jar包是:spring-boot-starter-data-redis,而mybatis的jar包却是 mybatis-spring-boot-starter,而者有何区别?
- 1.我们知道,redis是springBoot自己人,而mybatis是第三方。所以redis的jar包以spring-boot-starter开头,mybatis的jar包以spring-boot-starter结尾
- 2.对于自动注入而言,redis的RedisAutoConguration,是在spring-boot-autoconfiguration的spring.factories中的,而mybatis的MybatisAutoConfiguration,是在mybatis-spring-boot-autoconfiguration中的spring.factories文件中的。
OK,至此,讲完了SpringBoot自动注入的原理,下面幅图总结一下,
3.SpringBoot中starter的原理
3.1.SpringBoot中的SPI(Service Provider Interface)
问:你怎么就知道要读取META-INF/saring.factories文件? 下面写两个例子,一个是ServiceLoader,一个是SpringFactoriesLoader
ServiceLoader:
1.创建工程serviceloaderinterface,创建需要被实现的接口:
public interface serviceloaderinterface {
public String connection();
}
2.依赖上个工程,创建实现类,并创建META-INF/services/接口全类路径 文件,文件内容是 实现类的全类路径
public class serviceloaderImpl implements serviceloaderinterface {
public String connection() {
return "测试连接";
}
}
3.编写测试类
public class test {
public static void main(String[] args) {
ServiceLoader<serviceloaderinterface> serviceLoader = ServiceLoader.load(serviceloaderinterface.class);
for (serviceloaderinterface serviceloaderinterface : serviceLoader) {
System.out.println(serviceloaderinterface.connection());
}
}
}
输出结果:
ServiceLoader在加载一个接口类时,会加载所有的META-INF/接口全类路径文件中的实现全类路径。这是SpringBoot中约定俗成的东西,也是SpringBoot约定大于配置的一种情况。
SpringFactoriesLoader:
1.创建SpringBoot工程,
2.创建实体类TestA,TestB
3.创建META-INF/spring.factories文件,将TestA,TestB的全类路径写入。
2.编写测试
@RestController
public class TestController {
@Autowired
private TestA testA;
@Autowired
private TestB testB;
@RequestMapping("/test")
public String test() {
return testA.toString() + testB.toString();
}
}
输出结果:
SpringFactoriesLoader,会加载所有的META-INF/spring.factories文件中,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value值,这也是SpringBoot中约定俗成的东西,也是SpringBoot约定大于配置的一种情况。
3.2.手写starter
- 1.创建redisson-spring-boot-starter maven工程 目录结构如下:
pom.xml中引入的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId> //RedissonClient @Redisson
<version>3.15.1</version>
</dependency>
//自动在application.properties文件中添加提示信息
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.6.2</version>
</dependency>
</dependencies>
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.shenlong.RedissonAutoConfiguration
RedissonAutoConfiguration.java
@Configuration
@ConditionalOnClass(Redisson.class) //条件装配
@EnableConfigurationProperties(RedissonProperties.class) //开启配置类注解,在RedissonProperties中使用
public class RedissonAutoConfiguration {
@Bean
public RedissonClient redissonClient(RedissonProperties redissonProperties){
Config config = new Config();
String prefix = "redis://";
if (redissonProperties.isSsl()) {
prefix = "rediss://";
}
config.useSingleServer()
.setAddress(prefix + redissonProperties.getHost() + ":" + redissonProperties.getPort())
.setConnectTimeout(redissonProperties.getTimeout());
return Redisson.create(config);
}
}
RedissonProperties.java
@ConfigurationProperties(prefix = "com.shenlong") //读取配置文件中以 com.shenlong开头的配置信息
public class RedissonProperties {
private String host = "localhost";
private int port = 6379;
private int timeout;
private boolean ssl;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public boolean isSsl() {
return ssl;
}
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
}
- 2.将依赖添加到springbootdemo中进行测试 pom.xml
<dependency>
<groupId>com.shenlong</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
application.properties
配置了默认信息,这里不需要添加新的配置,如果redis的服务不在本地,需要更改ip的配置
这里的自动信息提示,与上文的spring-boot-configuration-processor jar包有关
Testcontroller:
@Autowired
private RedissonClient redissonClient;
@GetMapping("/redissonTest")
public String redissonTest() {
RBucket<Object> bucket = redissonClient.getBucket("name");
if (bucket.get() == null) {
bucket.set("haha");
}
return bucket.get().toString();
}
测试结果: