SpringBoot特性

236 阅读5分钟

SpringBoot特性

1.SpringBoot的自动装配

1.1.Spring中如何使用注解注入Bean

首先,咱们看看Spring中有哪些注解可以注入Bean。
为了方便下面注解的测试,这里创建一个SpringBoot工程,并将SpringBoot启动类单独放在一个包(SpringbootDemo)里面。(放在项目的根目录下,SpringBoot正常进行自动装载,无法测试)

image.png

  • 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自动注入的关键。
  1. @Import({class列表})
public class TestA {
}
public class TestB {
}
//这里添加@Configuration注解,是为了让Spring加载当前类,才能扫描到@Import注解,@Import注解才能起作用
@Configuration  
@Import({TestA.class, TestB.class})
public class importTest {
}
  1. @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应用程序。

    1. ...,现在开始从META-INF/spring.factories文件中读取出需要注入类的全类路径,果不其然,找到了RedisAutoConfiguration的全类路径。

image.png

  • 2.下面对RedisAutoConfiguration文件中的@Bean进行装载,发现文件中的类名为红色,也就是在当前程序中,加载不到这些类。尝试引入spring-boot-starter-data-redis这个jar包。

image.png

  • 3.仔细看,这个类的注解中,有个@ConditionalOnClass(RedisOperations.class),其意思为,如果存在RedisOperations这个类,就对当前文件中的bean进行装载。
  • 4.RedisOperations这个类所在的文件为,

image.png

  • 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自动注入的原理,下面幅图总结一下,

image.png

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 "测试连接";
    }
}

image.png

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());
        }
    }
}

输出结果:

image.png

ServiceLoader在加载一个接口类时,会加载所有的META-INF/接口全类路径文件中的实现全类路径。这是SpringBoot中约定俗成的东西,也是SpringBoot约定大于配置的一种情况。

SpringFactoriesLoader:
1.创建SpringBoot工程,
2.创建实体类TestA,TestB
3.创建META-INF/spring.factories文件,将TestA,TestB的全类路径写入。

image.png

2.编写测试
@RestController
public class TestController {


    @Autowired
    private TestA testA;

    @Autowired
    private TestB testB;

    @RequestMapping("/test")
    public String test() {
        return testA.toString() + testB.toString();
    }
}

输出结果:

image.png

SpringFactoriesLoader,会加载所有的META-INF/spring.factories文件中,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的value值,这也是SpringBoot中约定俗成的东西,也是SpringBoot约定大于配置的一种情况。

3.2.手写starter

  • 1.创建redisson-spring-boot-starter maven工程 目录结构如下:

image.png

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包有关
image.png

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();

}

测试结果:

image.png

4.Actuator(监控)

待补充