1. SpringBoot 原理分析
1.1 Condition
Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操作。
使用时需要实现此接口进行匹配.通过注解Conditional加以判断.
思考:SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?
案例:需求
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
- 导入Jedis坐标后,加载该Bean,没导入,则不加载
- 新建maven工程,导入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-day02</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-day02</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
编写SpringApplication
package com.example.springbootday02; import com.example.springbootday02.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.data.redis.core.RedisTemplate; @SpringBootApplication public class SpringbootDay02Application { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02Application.class, args); // RedisTemplate redisTemplate = (RedisTemplate) run.getBean("redisTemplate"); // System.out.println(redisTemplate); User user = (User) run.getBean("user"); System.out.println(user); } }
- 编写ClassCondition
package com.example.springbootday02.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import redis.clients.jedis.Jedis; public class ClassCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { try { Class.forName("redis.clients.jedis.Jedis"); } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } return true; } }
- 编写UserConfig类和User类
package com.example.springbootday02.domain; public class User { }
@Conditional的使用
作用:根据条件,决定类是否加载到Spring Ioc容器中,在SpringBoot中有大量的运用
应用场景:在一些需要条件满足才是实例化的类中,使用此注解,我曾经在项目中需要根据不同的场景使用不同的mq中间件的时候使用过,在mq的实例化bean上,加上此注解,根据配置文件的不同,来决定这个bean是否加载至ioc容器中。
使用方法
实现Conditional接口, 实现matches方法,看类是否被加载.
package com.example.springbootday02.config; import com.example.springbootday02.condition.ClassCondition; import com.example.springbootday02.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfig { @Bean("user") @Conditional(ClassCondition.class) public User getUser(){ return new User(); } }
案例:需求
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
- 导入Jedis坐标后,加载该Bean,没导入,则不加载
- 将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定。
- 定义注解MyConditionOnClass
package com.example.springbootday02.condition; import org.springframework.context.annotation.Conditional; import java.lang.annotation.*; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(ClassCondition.class) public @interface MyConditionOnClass { String[] value(); }
package com.example.springbootday02.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.util.MultiValueMap; import java.util.Map; public class ClassCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { // try { // Class.forName("redis.clients.jedis.Jedis"); // } catch (ClassNotFoundException e) { // e.printStackTrace(); // return false; // } // return true; Map<String, Object> myConditionOnClass = annotatedTypeMetadata.getAnnotationAttributes(MyConditionOnClass.class.getName()); try { String[] value = (String[]) myConditionOnClass.get("value"); for (String classes : value){ Class.forName(classes); } } catch (ClassNotFoundException e) { e.printStackTrace(); return false; } return true; } }
- UserConfig 使用 MyConditionOnClass注解
package com.example.springbootday02.config; import com.example.springbootday02.condition.ClassCondition; import com.example.springbootday02.condition.MyConditionOnClass; import com.example.springbootday02.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfig { @Bean("user") // @Conditional(ClassCondition.class) @MyConditionOnClass("redis.clients.jedis.Jedis") public User getUser(){ return new User(); } }
使用springboot提供的注解 实现条件判断
@Bean("user2") @ConditionalOnProperty(name = "itoldlu",havingValue = "oldlu") public User getUser2(){ return new User(); }
itoldlu=oldlu
1.2 Condition 小结
- 自定义条件:
- 定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回 boolean值 。 matches 方法两个参数:
- context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
- metadata:元数据对象,用于获取注解属性。
- 判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解
SpringBoot 提供的常用条件注解:
- ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
- ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
- ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean
1.3 切换内置web服务器
SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4中内置服务器供我们选择,我们可 以很方便的进行切换。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-tomcat</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>
1.4 @Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注 解导入一些配置类,实现Bean的动态加载。
思考:SpringBoot 工程是否可以直接获取jar包中定义的Bean?
package com.example.springbootday02enable; import com.example.springbootday02enableother.config.EnableUser; import com.example.springbootday02enableother.config.UserConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component; /** * @ComponentScan 扫描范围:当前引导类所在包及其子包 * * com.itoldlu.springbootenable * com.itoldlu.config * //1.使用@ComponentScan扫描com.itoldlu.config包 * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器 * //3.可以对Import注解进行封装。 */ @SpringBootApplication //@ComponentScan(basePackages="com.example.springbootday02enableother") //@Import(UserConfig.class) @EnableUser public class SpringbootDay02EnableApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02EnableApplication.class, args); Object user = run.getBean("user"); System.out.println(user); } }
package com.example.springbootday02enableother.config; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(UserConfig.class) public @interface EnableUser { }
package com.example.springbootday02enableother.config; import com.example.springbootday02enableother.domain.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class UserConfig { @Bean("user") public User getUser(){ return new User(); } }
package com.example.springbootday02enableother.domain; public class User { }
1.5 @Import注解
@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用 法:
- 导入Bean
- 导入配置类
- 导入 ImportSelector 实现类。一般用于加载配置文件中的类
- 导入 ImportBeanDefinitionRegistrar 实现类。
package com.example.springbootday02enable; import com.example.springbootday02enableother.config.EnableUser; import com.example.springbootday02enableother.config.MyImportBeanDefinitionRegistrar; import com.example.springbootday02enableother.config.MyImportSelector; import com.example.springbootday02enableother.config.UserConfig; import com.example.springbootday02enableother.domain.User; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.stereotype.Component; /** * @ComponentScan 扫描范围:当前引导类所在包及其子包 * * com.itoldlu.springbootenable * com.itoldlu.config * //1.使用@ComponentScan扫描com.itoldlu.config包 * //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器 * //3.可以对Import注解进行封装。 */ /** * Import4中用法: * 1. 导入Bean * 2. 导入配置类 * 3. 导入ImportSelector的实现类。 * 4. 导入ImportBeanDefinitionRegistrar实现类 */ @SpringBootApplication //@ComponentScan(basePackages="com.example.springbootday02enableother") //@Import(UserConfig.class) //@EnableUser //@Import(User.class) //@Import(UserConfig.class) //@Import(MyImportSelector.class) @Import(MyImportBeanDefinitionRegistrar.class) public class SpringbootDay02EnableApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(SpringbootDay02EnableApplication.class, args); Object user = run.getBean(User.class); System.out.println(user); Object user1 = run.getBean("user"); System.out.println(user1); } }
package com.example.springbootday02enableother.config; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"com.example.springbootday02enableother.domain.User"}; } }
package com.example.springbootday02enableother.config; import com.example.springbootday02enableother.domain.User; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition(); registry.registerBeanDefinition("user",beanDefinition); } }
1.6 面试题:@EnableAutoConfiguration注解
1.@EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。(AutoConfigurationImportSelector实现类的目的就是扫描下面的配置文件)
2.配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载 这些配置类,初始化Bean
3.并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean





