SpringBoot启动流程(简化)
public static void run(Class<?> primaryClass) {
// 1. 创建一个ApplicationContext实例,即我们常说的IoC容器
ApplicationContext context = createApplicationContext();
// 2. 将主类(primaryClass)注册到IoC容器中(简单但重要的第一-步)
loadSourceClass(context,primaryClass);
// 3. 递归加载并处理所有的配置类
processConfigurationClasses(context);
// 4. 实例化所有的单例Bean(Singleton Bean)
instantiateSingletonBeans(context);
// 5. 如果是web应用,则启动web服务器(例如Tomcat)
startWebServer(context);
}
上述步骤中递归处理的流程
SpringBoot自动配置(浅析)
@SpringBootApplication
:继承自@Configuration,支持JavaConfig的方式进行配置
@EnableAutoConfiguration
:开启自动配置@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
:返回需要导入的组件的全类名的数组
@ComponentScan
:自动扫描组件,默认扫描该类所在包及其子包下所有带有指定注解的类,将它们自动装配到bean容器中,会被自动装配的注解包括@Controller、@Service、@Component、@Repository等。也可以指定扫描路径
@Import
注解,通过AutoConfigurationImportSelector
类中的selectImports()
方法批量注入
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
查看getAutoConfigurationEntry()
方法,可以查看到所有的配置信息都是通过该方法获得
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 检查自动配置功能是否开启,默认开启
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 加载自动配置的元信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去掉重复的配置类
configurations = removeDuplicates(configurations);
// 获得注解中被exclude和excludeName排除的类的集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查被排除类是否可实例化、是否被自动注册配置所使用,不符合条件则抛出异常
checkExcludedClasses(configurations, exclusions);
// 从候选配置类中去除掉被排除的类
configurations.removeAll(exclusions);
// 过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 将配置类和排除类通过事件传入到监听器中
fireAutoConfigurationImportEvents(configurations, exclusions);
// 最终返回符合条件的自动配置类的全限定名数组
return new AutoConfigurationEntry(configurations, exclusions);
}
查看getCandidateConfigurations()
方法得知如何获取到候选的配置类
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
继续进入loadFactoryNames()
方法,可以发现一个获取资源的地址:FACTORIES_RESOURCE_LOCATION
,值为META-INF/spring.factories
下面是spring.factories文件的内容格式,根据它我们可以清晰地了解Map<String,List<String>>
中都存了些什么。其中Key值为:org.springframework.boot.autoconfigure.EnableAutoConfiguration
,Value值为后面的各种XXXAutoConfiguration
类
综上所述:getCandidateConfigurations()
方法通过SpringFactoriesLoader
加载器加载META-INF/spring.factories
文件,首先通过这个文件获取到每个配置类的url
,再通过这些url
将它们封装成Properties
对象,最后解析内容存于Map<String,List<String>>
中
备注:2.7版本之后,自动配置目录有所调整
目录调整为:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
每一行代表一个自动配置
但是为了向前兼容,仍然保留了META-INF/spring.factories
文件
SpringBoot接口
ApplicationRunner
SpringBoot项目启动时,若想在启动之后直接执行某一段代码,就可以实现ApplicationRunner
这个接口,并实现接口里面的run()
方法,方法中写上自己的想要的代码逻辑
如果有多段代码需要执行,可使用@Order
注解指定执行顺序
@Component
@Order(value = 1)
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("SpringBoot项目启动后执行的代码1......");
}
}
@Component
@Order(value = 2)
public class MyApplicationRunner2 implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("SpringBoot项目启动后执行的代码2......");
}
}
FactoryBean
FactoryBean
:工厂Bean,交给spring用来生产Bean到spring容器中,可以通过前缀&来获取工厂Bean本身。
public class Color {
}
public class ColorFactoryBean implements FactoryBean<Color> {
/**
* @return 返回一个Color对象,这个对象会添加到容器中
* @throws Exception 异常
*/
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean......");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
}
@Configuration
public class FactoryBeanConfig {
@Bean
public ColorFactoryBean colorFactoryBean() {
return new ColorFactoryBean();
}
}
public class FactoryBeanTest {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
Arrays.stream(beanDefinitionNames).forEach(System.out::println);
//工程Bean获取的是调用getObject创建的对象
Object colorFactoryBean = context.getBean("colorFactoryBean");
System.out.println("bean的类型:"+colorFactoryBean.getClass());
//要获取工厂Bean本身,我们要在给id前面加一个&
Object colorFactoryBean1 = context.getBean("&colorFactoryBean");
System.out.println("bean的类型:"+colorFactoryBean1.getClass());
}
}
// 结果如下:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
factoryBeanConfig
colorFactoryBean
ColorFactoryBean......
bean的类型:class cn.akangaroo.test.factoryBean.entity.Color
bean的类型:class cn.akangaroo.test.factoryBean.ColorFactoryBean
SpringBoot注解
自动装配注解
@Autowired
:自动注入
-
按照类型去容器中找对应的组件
-
按照属性名称去作为组件id去找对应的组件
@Qualifier
:指定默认的组件,结合@Autowried使用
@Primary
:Spring进行自动装配的时候,默认使用首选的Bean。也可以使用@Qualifier来指定需要装配的Bean的名字。
@Resource
(JSR250) :可以和@Autowired一样实现自动装配功能;默认是按照组件名称进行装配
@Inject
:和@Autowired的功能一样,但是比@Autowired少一个required=false的功能
@Configuration
@Configuration
:标识为配置类
@Bean
@Bean
:默认的beanId是函数名,可以用value属性进行指定,放在@Component
或者@Configuration
修饰的类中
@Scope
@Scope
:作用域 prototype(多例) singleton(单例)
@Lazy
@Lazy
:懒加载,单例Bean:默认在容器启动的时候创建对象,容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化
@Conditional
@Conditional()
按照一定的条件进行判断,满足条件给容器中注册Bean,传入Condition
数组,使用时需自己创建类继承Condition
然后重写match()
方法
注解 | 含义 |
---|---|
@ConditionalOnBean | 在容器中有指定Bean的条件下 |
@ConditionalOnMissingBean | 在容器中没有指定Bean的条件下 |
@ConditionalOnClass | 在classpath类路径下有指定类的条件下 |
@ConditionalOnMissingClass | 在classpath类路径下没有指定类的条件下 |
@ConditionalOnResource | 类路径是否有指定的值 |
@ConditionalOnWebApplication | 在项目是一个Web项目的条件下 |
@ConditionalOnProperty | 在指定的属性有指定的值条件下 |
自定义一个Condition
-
定义一个
HelloCondition
,默认返回truepublic class HelloCondition implements Condition { /** * @param conditionContext 判断条件能使用的上下文(环境) * @param annotatedTypeMetadata 注解信息 * @return */ @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return true; } }
-
定义一个实体类
@Data public class Dog { private String name ; private Integer age; }
-
配置类中注入Dog对象,并且指定加载条件
@Configuration @Conditional(HelloCondition.class) public class HelloConfig { @Bean public Dog dog() { Dog dog = new Dog(); dog.setName("woof"); dog.setAge(12); return dog; } }
-
测试(可将
HelloCondition
中的matach()
方法的返回值改为false)public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(HelloConfig.class); Map<String, Dog> beansOfType = applicationContext.getBeansOfType(Dog.class); System.out.println(beansOfType); } } // 结果如下 {dog=Dog(name=woof, age=12)}
@RquestMapping
@RquestMapping
属性
-
name:相当于方法的注释,使方法更易理解。
-
value & path:指定请求的实际地址,支持通配符匹配。
-
method:指定请求的method类型(GET,POST,PUT,DELETE)等。
-
consumes:指定处理请求的提交内容类型(Context-Type)。
-
produces:指定返回的内容类型,还可以设置返回值的字符编码。
-
params:指定request中必须包含某些参数值,才让该方法处理。
-
headers:指定request中必须包含某些指定的header值,才让该方法处理请求。
@RestController
public class TestController {
@RequestMapping(name = "测试",
value = "/test",
method = RequestMethod.POST,
// flag必须指定为true,content任意值
params = {"flag=true", "content"},
// 自定义请求头一般以"X-"开头
headers = "X-Custom-Header=MyCustom",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public Dict test() {
return Dict.create().set("who", "akangaroo");
}
}
@Value
基本数值:@Value("张三")
SpEl表达式: @Value(#{1 + 2})
配置文件:@Value(${os.name})
@PropertySource
使用@PropertySource
指定读取的配置文件路径
@Component
@PropertySource({"classpath:config/custom.properties"}) // 指定配置文件位置
@ConfigurationProperties(prefix = "my")
@Data
public class MyCustomProperties {
private String name;
private String sex;
}
在resources
目录下新建config
文件夹,新建custom.properties
文件
my.name=akangaroo
my.sex=male
@Import
@Import
它的作用是提供了一种显式地从其它地方加载配置类的方式,这样可以避免使用性能较差的组件扫描(@ComponentScan
)支持导入:
普通类
接口
ImportSelector
的实现类:返回需要导入的组件的全类名的数组SpringBoot自动配置的实现接口
ImportBeanDefinitionRegistrar
实现类:手动注册Bean
导入普通类
-
首先创建一个
ConfigA
配置类和A
类,ConfigA
不使用注解注入public class ConfigA { @Bean public A a() { return new A(); } } class A { }
-
创建一个配置类
ConfigB
,将上面的ConfigA
导入@Configuration @Import(ConfigA.class) public class ConfigB { }
-
测试
public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigB.class); ConfigA configA = context.getBean(ConfigA.class); A a = context.getBean(A.class); System.out.println(configA.getClass().getSimpleName()); System.out.println(a.getClass().getSimpleName()); } } // 结果如下 ConfigA A
导入选择器ImportSelector
可以通过@Import
导入一个接口ImportSelector
实现类。接口ImportSelector
中有个selectlmports
方法,它的返回值是一个字符串数组,数组中的每个元素分别代表一个将被导入的配置类的全限定名。
-
创建两个类,不加
@Component
相关的注解public class Blue { }
public class Red { }
-
创建一个类实现
ImportSelector
接口public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"cn.akangaroo.test.importAnnotation.importSelector.Red", "cn.akangaroo.test.importAnnotation.importSelector.Blue"}; } }
-
使用
@Import
导入ImportSelector
的实现类@Configuration @Import({MyImportSelector.class}) public class ImportTestConfig { }
-
测试
public class ImportAnnotationTest { @Test public void test() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportTestConfig.class); String[] beanDefinitionNames = context.getBeanDefinitionNames(); Arrays.stream(beanDefinitionNames).forEach(System.out::println); } } // 结果如下: org.springframework.context.annotation.internalConfigurationAnnotationProcessor org.springframework.context.annotation.internalAutowiredAnnotationProcessor org.springframework.context.annotation.internalCommonAnnotationProcessor org.springframework.context.event.internalEventListenerProcessor org.springframework.context.event.internalEventListenerFactory importTestConfig cn.akangaroo.test.importAnnotation.importSelector.Red cn.akangaroo.test.importAnnotation.importSelector.Blue
导入注册器ImportBeanDefinitionRegistrar
可以通过@lmport
导入一个接口ImportBeanDefinitionRegistrar
的实现类。通过它,我们可以手动将多个BeanDefinition
注册到IOC
容器中,从而实现个性化的定制。
-
创建普通类
Dog
public class Dog { }
-
创建
AnimalBeanDefinitionRegistrar
类实现ImportBeanDefinitionRegistrar
接口public class AnimalBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(Dog.class); registry.registerBeanDefinition("dog1", beanDefinition); } }
-
创建配置类
ConfigC
导入AnimalBeanDefinitionRegistrar
@Configuration @Import({AnimalBeanDefinitionRegistrar.class}) public class ConfigC { }
-
测试
public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigC.class); Dog dog1 = context.getBean("dog1", Dog.class); System.out.println(dog1.getClass().getSimpleName()); } } // 结果如下 Dog
Bean生命周期
创建 -> 初始化 -> 销毁
创建
单实例:在容器启动的时候创建对象
多实例:在每次获取的时候创建对象
初始化
对象创建完成,并赋值好,调用初始化方法
销毁
单实例:容器关闭的时候销毁Bean
多实例:容器不会管你这个Bean;容器不会调用销毁方法。需要手动销毁
初始化和销毁的三种方式
-
@Bean
注解的initMethod
、destroyMethod
两个属性配置bean的初始化和销毁方法 -
InitializingBean
接口的afterPropertiesSet()
实现初始化逻辑;DispoableBean
接口的destroy()
实现销毁逻辑; -
@PostConstrct
,标注在方法上,对象执行构造行数,创建并赋值之后执行;@PreDestroy
,标注在方法上,在容器移除对象之前执行
初始化顺序:constructor -> postConstruct -> InitializingBean-afterPropertiesSet() -> initMethod
销毁顺序:preDestroy -> destroy -> destroyMethod
public class Car implements InitializingBean, DisposableBean {
public Car() {
System.out.println("car...constructor");
}
public void initMethod() {
System.out.println("car...initMethod");
}
public void destroyMethod() {
System.out.println("car...destroyMethod");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("car...InitializingBean-afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("car...destroy");
}
@PostConstruct
public void postConstruct() {
System.out.println("car...postConstruct");
}
@PreDestroy
public void preDestroy() {
System.out.println("car...preDestroy");
}
}
@Configuration
public class Config {
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public Car car() {
return new Car();
}
}
public class CycleOfLifeTest {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.getBean(Car.class);
context.close();
}
}
// 结果如下:
car...constructor
car...postConstruct
car...InitializingBean-afterPropertiesSet
car...initMethod
00:36:07.585 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4ae3c1cd, started on Thu Dec 01 00:36:07 CST 2022
car...preDestroy
car...destroy
car...destroyMethod
BeanPostProcessor接口
/**
* 后置处理器:Bean的后置处理器,在Bean的初始化前后进行处理。
*
* @author : kangyw41748
* @since : 2022/12/4 16:03
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* Bean构造方法执行之后、初始化方法执行之前处理
* @param bean bean the new bean instance
* @param beanName beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one;
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization------" + beanName);
return bean;
}
/**
* Bean初始化之后处理
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization------" + beanName);
return bean;
}
}
在config
类上加上@ComponentScan
注解注入到容器进行测试
@Configuration
@ComponentScan("cn.akangaroo.test.bean")
public class Config {
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public Car car() {
return new Car();
}
}
public class CycleOfLifeTest {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.getBean(Car.class);
}
}
// 结果如下:
16:30:22.651 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
postProcessBeforeInitialization------config
postProcessAfterInitialization------config
16:30:22.651 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'car'
car...constructor
postProcessBeforeInitialization------car
car...postConstruct
car...InitializingBean-afterPropertiesSet
car...initMethod
postProcessAfterInitialization------car
执行过程:
AbstractAutowireCapableBeanFactory.doCreateBean
中调用popolulateBean
为bean填充属性,然后调用initializeBean
开始进行bean的初始化以及bean初始化的前后处理。
AbstractAutowireCapableBeanFactory.doCreateBean
调用initializeBean
方法:
- 该方法会先调用
applyBeanPostProcessorsBeforeInitialization(warppedBean, beanName)
;初始化前前置处理- 然后调用
invokeInitMethods
对bean进行初始化- 最后调用
applyBeanPostProcessorsAfterInitialization
,初始化后置处理。