Spring Boot 的自动配置是其核心特性之一,旨在简化配置过程。它会根据项目中依赖的库和类路径中存在的类,自动为你配置 Spring 应用的相关组件。
一、Condition
Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。
思考:SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate 的?(通过条件来判断是否创建bean。)
1、Condition功能的具体实现步骤:
(1)导入依赖坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)创建条件类,如果有这个条件类的时候我们再去创建对象。
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
这里首先实现了Condition接口来控制 Bean 的创建。具体来说,它判断当前环境中是否存在 Jedis
类,从而决定是否创建与之相关的 Bean。Condition 接口用于定义条件,Spring 在决定是否加载某些配置或 Bean 时会使用它。
matches 方法是 Condition 接口中的唯一方法。matches 方法返回一个布尔值,它的返回值决定了某些配置或 Bean 是否应该被加载(创建Bean)。
ConditionContext context:提供了上下文信息,可以用来获取 Spring 环境、IOC 容器、ClassLoader 等。
AnnotatedTypeMetadata metadata:提供了注解的元数据,可以用来获取注解的属性值。
Class.forName("redis.clients.jedis.Jedis"):尝试通过类的全限定名加载 Jedis
类。
如果该类存在,Class.forName
会成功执行并返回 Class<?>
对象,flag
变量保持为 true
。
如果类不存在,ClassNotFoundException
会被捕获,flag
变量被设置为 false
。
(3)创建一个自定义注解,通过一个自定义注解来接收测试类中传进来的依赖类来完成判断。
自定义注解ConditionOnClass的代码:(用于在特定条件下决定某个类或方法是否应当被加载或执行)
// 指定了这个注解可以应用的目标。ElementType.TYPE 表示注解可以用在类、接口或枚举上,ElementType.METHOD 表示注解可以用在方法上。
@Target({ElementType.TYPE,ElementType.METHOD}) // 可以修饰在类与方法上
// 指定了注解的保留策略。RUNTIME 表示注解会在运行时保留,可以通过反射机制读取。
@Retention(RetentionPolicy.RUNTIME) // 注解生效节点runtime
@Documented //生成工具文档化
// 表示这个注解的作用是条件性的,只有当 ClassCondition 条件满足时,注解修饰的类或方法才会生效。ClassCondition 是一个实现了 Condition 接口的类,用来定义条件逻辑。
@Conditional(value = ClassCondition.class)
public @interface ConditionOnClass {
String[] value(); // 设置此注解的属性redis.clients.jedis.Jedis
}
这段代码里面@Target定义了这个自定义注解可以应用的目标。ElementType.TYPE是可以用在类、接口或枚举上,ElementType.METHOD是可以用在方法上。
@Retention注解指定了自定义注解的保留策略。
RetentionPolicy.RUNTIME:表示该注解会在运行时保留,这意味着可以通过反射在运行时读取这个注解的信息。这对于像条件装配(Condition-based configuration)这样的场景非常关键,因为条件的判断需要在运行时执行。
@Documented 表示这个注解会包含在生成的 JavaDoc 文档中。换句话说,使用这个注解的类或方法,在生成文档时会将这个注解的信息包含进去,增强了注解的可见性和文档性。
@Conditional 注解用于指定条件,这里引用了 ClassCondition.class
。
ClassCondition
是一个实现了 Condition
接口的类,它的 matches
方法定义了条件逻辑。如果 ClassCondition
的 matches
方法返回 true
,则被 @ConditionOnClass
修饰的类或方法才会生效。
ClassCondition
通常用来检查某个类(如 redis.clients.jedis.Jedis
)是否存在于类路径中。
@interface ConditionOnClass定义了一个新的注解@ConditionOnClass
String[] value()定义了这个注解的一个属性 value。
它是一个字符串数组用户可以在使用 @ConditionOnClass
注解时,指定要检查的类的全限定名(例如 "redis.clients.jedis.Jedis"
)。这个属性的值会传递给 ClassCondition
,用于判断条件是否成立。
(4)配置类,它通过刚才的ClassCondition条件类和ConditionOnClass自定义配置来帮助它判断是否要注册加载当前的bean。下面是配置类UserConfig类的代码:
@Configuration
public class UserConfig {
// 情况1
@Bean
// 该注解的作用是通过条件类 ClassCondition 来决定 user() 方法是否应该被注册为一个 Bean。
@ConditionOnClass(value={"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis"})
public User user(){
return new User();
}
// 情况2
@Bean
// 当容器中有一个key=k1且value=v1的时候user2才会注入
// 在application.properties文件中条件k1=v1
@ConditionalOnProperty(name = "k1",havingValue = "v1")
public User user2(){
return new User();
}
}
@Configuration注解标识这个类是一个配置类,Spring 会将其作为@Bean定义的来源之一。通过这个类,Spring 可以扫描和创建其中定义的 Bean。
情况1: user()
方法
@ConditionOnClass是一个自定义注解,它的作用是基于指定类的存在性来决定是否注册这个 Bean。
- value={"com.alibaba.fastjson.JSON", "redis.clients.jedis.Jedis"}表示只有在类路径中存在com.alibaba.fastjson.JSON和redis.clients.jedis.Jedis这两个类时,user()方法才会被执行,从而将User对象注册为一个 Bean。
- 如果其中一个类不存在,则user()方法不会被调用,UserBean 也不会被注入。
情况2: user2()
方法
@ConditionalOnProperty注解决定了user2()方法是否应该执行,这个决定基于 Spring 应用的属性配置。
application.properties:
k1=v1
- name = "k1", havingValue = "v1表示只有当应用程序的配置文件中存在k1=v1这一对属性时,user2()方法才会执行,从而将User对象注册为一个 Bean。
- 如果application.properties文件中k1的值不是v1或者k1不存在,那么user2()方法不会被执行,也就不会有User Bean 注入。
(5)在测试类主入口中去通过bean的id创建一个bean对象,看是否被注册加载:
@SpringBootApplication
public class SpringbootCondition02Application {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootCondition02Application.class, args);
/********************获取容器中user********************/
// Object user1 = context.getBean("user");
// System.out.println(user1);
Object user2 = context.getBean("user");
System.out.println(user2);
}
}
2.Condition – 小结
(1)自定义条件
① 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判 断,返回boolean值。matches 方法两个参数:
- context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
- metadata:元数据对象,用于获取注解属性。
② 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解。
(2)SpringBoot 提供的常用条件注解
以下注解在springBoot-autoconfigure的condition包下:
- ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
- ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
- ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
- ConditionalOnBean:判断环境中有对应Bean才初始化Bean
二、@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。
思考 SpringBoot 工程是否可以直接获取jar包中定义的Bean?
这里我们就要@Import注解来解决这个问题了:
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
- ① 导入Bean
- ② 导入配置类
- ③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
- ④ 导入 ImportBeanDefinitionRegistrar 实现类。
演示示例(1):
就比如说我们要在一个项目子模块中去配置一些依赖坐标,并去对其进行自定义注解的创建,再通过主项目模块来调用,那该怎么实现呢?
首先是子项目模块:
子项目模块配置类config中的EnableUser(自定义配置类):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}
定义了一个自定义的注解 @EnableUser
,其作用是自动导入 UserConfig
配置类,从而将 UserConfig
中定义的 Bean 和配置注册到 Spring 容器中。
@Target(ElementType.TYPE)指定了这个自定义注解@EnableUser可以应用的目标类型。ElementType.TYPE表示该注解可以用于类、接口(包括注解类型)或枚举上。
@Retention(RetentionPolicy.RUNTIME)指定了注解的保留策略。RetentionPolicy.RUNTIME
表示这个注解会在运行时保留,并且可以通过反射机制读取。
@Documented表示使用该注解的元素在生成 Javadoc 等文档时,会包含这个注解的信息。
@Import(UserConfig.class)是这个注解的核心部分。它的作用是将UserConfig配置类导入到 Spring 容器中。当某个类被@EnableUser注解标记时,Spring 会自动加载UserConfig类,并将其中定义的 Bean 注册到容器中。
@Import通常用于在自定义的启用注解中,用来将特定的配置类或组件注册到应用程序的上下文中。这种机制允许你通过简单的注解来激活复杂的配置。
模块配置类config中的UserConfig配置类:
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
这里定义了user()方法用来封装名为user的bean。
这里子模块项目实现了相关代码,呢么主模块就进行导入子模块的坐标,来获取他的方法和配置类等:
<dependency>
<groupId>com.apesource</groupId>
<artifactId>springboot-enable_other-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
再通过测试主入口进行调用:
@SpringBootApplication
//@ComponentScan("com.apesource.config")
//@Import(User.class)//导入javaBean
//@Import(UserConfig.class)
@EnableUser
//@EnableScheduling
//@EnableCaching
public class SpringbootEnable03Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnable03Application.class, args);
/**
* @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包
* 当前引导类所在包com.apesource.springbootenable03
* 注入user类所在包com.apesource.springbootenable_other04.config
* 因此扫描不到,所以容器中没有user
* 解决方案:
* 1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包
* 2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
* 3.可以对Import注解进行封装。
*
*/
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
如果注解的是@ComponentScan("com.apesource.config"),那么就是通过扫描指定包名com.apesource.config来注册bean。
如果是@Import(User.class),那么直接将 User 类导入到 Spring 容器中,使得 User 成为一个 Bean。
如果是@Import(UserConfig.class),那么就是导入导入 UserConfig 配置类,也会自动注册bean。
如果是@EnableUser,那么就是用咱们刚才自定义的注解来完成注册bean。
@EnableScheduling,启用 Spring 的任务调度功能,如果需要使用定时任务,可以启用此注解。
@EnableCaching,启用 Spring 的缓存功能,允许使用注解来缓存方法的返回结果。
这几种方式都可以去注册bean。
演示示例(2):
刚才只是演示了最基础的调用Config配置类,再提供两种可以调用的方法:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//AnnotationMetadata注解
//BeanDefinitionRegistry向spring容器中注入
//1.获取user的definition对象
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象
registry.registerBeanDefinition("user", beanDefinition);
}
}
ImportBeanDefinitionRegistrar是 Spring 的一个接口,允许开发者在 Spring 的启动过程中动态地注册 Bean 定义。
通过实现这个接口,可以在容器启动时根据需要有选择地注册 Bean,而不是依赖于配置类或组件扫描。
registerBeanDefinitions用于注册 Bean 定义。AnnotationMetadata importingClassMetadata参数提供了当前被注解类的元数据信息,可以通过这个对象获取有关注解和类的信息。 BeanDefinitionRegistry registry是 Bean 定义注册表,提供了向 Spring 容器中注册 Bean 定义的能力。
BeanDefinition是 Spring 中用于描述 Bean 的元数据对象,包含了 Bean 的类型、作用域、初始化方法、销毁方法等信息。
最后通过通过beanDefinition属性信息将 User Bean定义注册到Spring容器中。
还有一种可以解决写死配置类和组件的情况,用于动态选择要导入的配置类或组件:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
return new String[]{"com.apesource.domain.User","com.apesource.domain.Student"};
}
}
这里面实现了ImportSelector接口,用于动态选择要导入的配置类或组件,重写selectImports方法来实现返回一个字符串数组,返回值就为已经需要导入的类的名称,代表将"字符串数组"中的的类,全部导入spring容器。
然后同样的道理在主项目里导子模块项目的包,进行调用测试:
@SpringBootApplication
//@Import(User.class)
//@Import(UserConfig.class)
//@Import(MyImportSelector.class)
@Import({MyImportBeanDefinitionRegistrar.class})
//@EnableCaching
//@EnableAsync
public class SpringbootEnableMain05Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableMain05Application.class, args);
/**
* Import4中用法:
* 1. 导入Bean
* 2. 导入配置类
* 3. 导入ImportSelector的实现类
* 查看ImportSelector接口源码
* String[] selectImports(AnnotationMetadata importingClassMetadata);
* 代表将“字符串数组”中的的类,全部导入spring容器
* 4. 导入ImportBeanDefinitionRegistrar实现类
*
*/
// User user = context.getBean(User.class);
// System.out.println(user);
//
// Student student = context.getBean(Student.class);
// System.out.println(student);
User user = (User) context.getBean("user");
System.out.println(user);
}
}
这里多个一个注释,@EnableAsync是启用 Spring 的异步方法执行功能。
三、@EnableAutoConfiguration 注解和自动配置
这个注释主要是用来帮助我们配置,首先它的自动配置是从主启动类中启动时通过注解@SpringBootApplication里的@EnableAutoConfiguration来实现:
//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
//以为是启动了一个方法,没想到启动了一个服务
SpringApplication.run(SpringbootApplication.class, args);
}
}
当我们进去到 @SpringBootApplication 注解的源码当中,可以发现它是一个复合注解,它是由 @SpringBootConfiguration + @EnableAutoConfiguration+ @ComponentScan 这三个注解组成。
1、@EnableAutoConfiguration的三个注解属性
@SpringBootConfiguration:在@SpringBootConfiguration 源码中可以发现有 @Configuration,代表是一个配置类,说明主程序类也是一个配置类。
//@SpringBootConfiguration注解内部 //这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件; @Configuration public @interface SpringBootConfiguration {} //里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用 @Component public @interface Configuration {}
@ComponentScan:这个注解在Spring中很重要 ,它对应XML配置中的元素,刚才也讲到过,是自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中。
@EnableAutoConfiguration:以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration 告诉SpringBoot开启自动配置功能,这样自动配置才能生效;
@AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { }
2、自动配置过程
@AutoConfigurationPackage:自动配置包,将指定的一个包下的所有组件导入到容器当中,在@AutoConfigurationPackage 注解中存在一个 @Import({Registrar.class}) 注解,自动配置包就是通过这个 Registrar 类的方法来完成的。
//AutoConfigurationPackage的子注解
//Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}
由于@AutoConfigurationPackage 是在 @EnableAutoConfiguration 中,所以@AutoConfigurationPackage 是标识在主程序类上,所以 metadata为主程序类。
因为在AutoConfigurationPackage注解中用import导入了Registrar类:
@AutoConfigurationPackage:将主程序类所在包及所有子包下的组件扫描到Spring容器中。
@Import:
① 导入Bean
② 导入配置类
③ 导入 ImportSelector 实现类。一般用于加载配置文件中的类
④ 导入 ImportBeanDefinitionRegistrar 实现类。
观察@EnableAutoConfiguration的源码可知,实现自动配置主要是通过AutoConfigurationImportSelector,实现类来加载配置文件按的
AutoConfigurationImportSelector实现类的源码解读
观察源码可知AutoConfigurationImportSelector是 Spring Boot 自动配置机制的核心组件之一,它通过动态选择并导入自动配置类来简化 Spring 应用的配置过程 ,在AutoConfigurationImportSelector 类中我们会发现它实现了ImportSelector接口,也就实现了这个接口中的 selectImports 方法,该方法主要用于获取所有符合条件的类的全限定类名,并以字符串数组返回,这些类需要被加载到 IoC 容器中。
该方法主要通过调用getAutoConfigurationEntry()方法获取AutoConfigurationEntry对象,这个方法主要负责加载自动配置类的。
getAutoConfigurationEntry()源码解读
观察源码可知,这个方法是 Spring Boot 自动配置机制的核心之一,它负责根据应用的配置和依赖情况来确定哪些自动配置类应该被加载到 Spring 应用上下文中。通过这个过程,Spring Boot 能够提供“开箱即用”的配置,同时允许开发者通过排除特定的自动配置来定制其行为。
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes)就是获取到所有需要导入到容器当中的组件,利用工厂加载。
这个类实现了DeferredImportSelector等接口
在DeferredImportSelector接口中有一个selectImports,里面规定了哪些Bean需要被自动装配,根据里面的方法:this.getCandidateConfigurations方法进入
观察源码可知该类的 getCandidateConfigurations 方法中调用了 SpringFactoriesLoader类的 loadFactoryNames 方法获取所有自动转配类名,loadSpringFactories() 方法从META-INF/spring.factories加载自动装配类。该方法进入了一个META-INF/spring的目录,文件后缀是.imports
最后按照条件装配@Conditional最终会按需配置。
加载 spring.factories 中的配置,但不是每次启动都会加载其中的所有配置,会有一个筛选的过程,去掉重复的配置。
可以在左侧的依赖里面进行查看,这里面有133个Bean,可以被自动装配,也就是常用的自动装配的哪些Bean。
Spring Boot项目中@SpringBootApplication注解实现自动装配,这个注解是对三个注解进行了封装:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan,
其中@EnableAutoConfiguration是实现自动化配置的核心注解。
该注解通过@Import注解导入AutoConfigurationImportSelector,这个类实现了一个导入器接口ImportSelector。在该接口中重写了一个方法selectImports。
selectImports方法的返回值是一个数组,数组中存储的就是要被导入到spring容器中的类的全限定名。在AutoConfigurationImportSelector类中重写了这个方法。
该方法内部就是读取了项目的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。
在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中
四、自定义启动器
通过刚才的自动配置我们可以发现我们可以自己配置一个spring.factories来完成自定义的启动器。
首先我们要创建一个子模块项目用来定义自动配置的信息:
RedisAutoconfiguration配置类:
@Configuration
// RedisProperties 是一个包含 Redis 配置参数的类
// 通常使用 @ConfigurationProperties 注解标注,定义了 Redis 的主机、端口等属性。
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoconfiguration {
// 注入jedis
@Bean
// 由于上面使用了 @EnableConfigurationProperties 注解,Spring 会自动将 RedisProperties 实例注入到这个方法中。
public Jedis jedis(RedisProperties redisProperties){
// 主机/端口
// 这个 Jedis 实例就是 Redis 的客户端,用于连接和操作 Redis 数据库。
return new Jedis(redisProperties.getHost(),redisProperties.getPort());
}
}
加了@EnableConfigurationProperties(RedisProperties.class)注解用于对对 RedisProperties 配置属性类的支持。RedisProperties 是一个包含了 Redis 配置参数的类,通常包括主机名、端口等。通过这个注解,Spring Boot 可以将外部配置文件(如 application.properties 或 application.yml)中的相关配置映射到 RedisProperties 类的属性中,并自动实例化一个 RedisProperties 对象。
其中RedisProperties的代码为:
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
private String host="localhost";
private int port=6379;
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;
}
}
这里面@ConfigurationProperties(prefix = "spring.redis")用于将外部配置文件,就比如 application.properties 或 application.yml中的配置参数绑定到这个类的字段上。
剩下的host是Redis服务器的主机名,port是于存储 Redis 服务器的端口号。
然后是创建了一个Jedis实例的方法, 由于使用了 @EnableConfigurationProperties(RedisProperties.class),Spring 会自动将 RedisProperties 实例作为参数注入到这个方法中。后面的getHost()和getPort()是刚才在RedisProperties中的主机名和端口号,获取到Jedis实例中并创建,从而连接到 Redis 服务器。
其中根据springboot自动配置的过程,我们也手动的配置了一个spring.factories用来设置自动加载类是谁:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.apesource.RedisAutoconfiguration
这里我们设置了RedisAutoconfiguration为自动加载类,当Spring Boot 会在启动时自动加载并应用这个配置类。
然后我们在redis-spring-boot-starter子模块项目中导入刚才的子模块项目的坐标:
<!-- 引入自定义的redis-spring-boot-autoconfigure-->
<dependency>
<groupId>com.apesource</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
最后我们在主项目中先导入redis-spring-boot-starter子模块项目的坐标:
<!--导入坐标-->
<dependency>
<groupId>com.apesource</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然后我们配置一下application.yml中的Redis 的连接参数的信息,用于刚才RedisProperties中的@ConfigurationProperties的获取:
spring:
redis:
port: 6060
host: 127.0.0.1
最后的最后我们去测试类主入口进行测试:
@SpringBootApplication
public class SpringbootStarter04Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootStarter04Application.class, args);
Jedis bean1 = context.getBean(Jedis.class);
System.out.println(bean1);
}
}
这就是自定义启动器的全部过程了。