Spring 第一讲 Spring的使用
xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.laoshiren</groupId>
<artifactId>spring-bean-init</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>
</dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
自动配置
Bean 加载方式(复习)
xml配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- xml的方式声明自己开发的Bean-->
<bean id="cat" class="com.laoshiren.bean.Cat"/>
<!-- 不指定 ID 配置Bean-->
<bean class="com.laoshiren.bean.Dog"/>
<!-- 多次定义Bean 抛出NoUniqueBeanDefinitionException-->
<!-- <bean class="com.laoshiren.bean.Dog"/>-->
</beans>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
java
package com.laoshiren.app;
import com.laoshiren.bean.Dog;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @date: 2023/1/4 10:43
* @author: lasohiren
*/
public class App1 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
Object cat = ctx.getBean("cat");
System.out.println(cat);
Dog bean = ctx.getBean(Dog.class);
System.out.println(bean);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
// cat
// com.laoshiren.bean.Dog#0 全路径的类名
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
注解的方式
加上指定的注解@Compoment
,@Service
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 制定加载Bean的位置-->
<context:component-scan base-package="com.laoshiren.bean"/>
</beans>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
java
package com.laoshiren.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @date: 2023/1/4 10:43
* @author: lasohiren
*/
public class App2 {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注解方式声明配置类
java
package com.laoshiren.config;
import org.springframework.context.annotation.ComponentScan;
/**
* @date: 2023/1/4 13:55
* @author: lasohiren
*/
@ComponentScan({"com.laoshiren.bean"})
public class SpringConfig3 {
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
FactoryBean
初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作
java
package com.laoshiren.bean;
import org.springframework.beans.factory.FactoryBean;
/**
* @date: 2023/1/4 14:01
* @author: lasohiren
*/
public class DogFactoryBean implements FactoryBean<Dog> {
@Override
public Dog getObject() throws Exception {
// 对这个Dog 对象的初始化 都放到这里
return new Dog();
}
@Override
public Class<?> getObjectType() {
return Dog.class;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java
package com.laoshiren.config;
import com.laoshiren.bean.DogFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
/**
* @date: 2023/1/4 13:55
* @author: lasohiren
*/
@ComponentScan({"com.laoshiren.bean"})
public class SpringConfig3 {
@Bean
public DogFactoryBean dog(){
// 创建的不是DogFatcoryBean对象 而是他的泛型定义的对象
return new DogFactoryBean();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
加载配置类并加载配置文件(系统迁移) 使用@ImportResource("applicationContext1.xml")
注解加载配置文件
使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的。
java
package com.laoshiren.config;
import com.laoshiren.bean.DogFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true)
// 默认就是True
public class SpringConfig33 {
@Bean
public DogFactoryBean dog(){
return new DogFactoryBean();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
使用@Import
注解导入配置类,被导入的bean无需使用注解声明为bean,此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用。
java
@Import(Dog.class)
public class SpringConfig4 {
}
1
2
3
2
3
使用上下文对象在容器初始化完毕后注入bean
java
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class);
// 注入Bean
ctx.register(Dog.class);
ctx.registerBean("cat", Cat.class,1);
// 会覆盖之前的Cat
ctx.registerBean("cat", Cat.class,2);
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
导入实现了ImportSelector接口的类,实现对导入源的编程式处理
java
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
System.out.println("metadata: "+metadata.getClassName());
System.out.println("metadata: "+metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
System.out.println("metadata: "+metadata.getAllAnnotationAttributes("org.springframework.context.annotation.ComponentScan"));
return new String[]{"com.laoshiren.bean.Dog"};
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
importingClassMetadata
描述的是SpringConfig6这个类的元数据信息。
导入实现了ImportBeanDefinitionRegistrar
接口的类,通过BeanDefinition
的注册器注册实名bean,实现对 容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果
java
package com.laoshiren.bean;
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;
/**
* @Author laoshiren
* @Date 13:45 2023/1/5
*/
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("cat",
BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
导入实现了BeanDefinitionRegistryPostProcessor
接口的类,通过BeanDefinition的注册器注册实名bean, 实现对容器中bean的最终裁定
java
package com.laoshiren.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
/**
* @Author laoshiren
* @Date 13:56 2023/1/5
*/
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Cat.class)
.getBeanDefinition();
registry.registerBeanDefinition("cat", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Bean 加载控制(复习)
@Conditional
java
package com.laoshiren.config;
import com.laoshiren.bean.Cat;
import com.laoshiren.bean.Dog;
import com.laoshiren.bean.Mouse;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
/**
* @Author laoshiren
* @Date 14:17 2023/1/5
*/
public class SpringConfig {
/**
* 按类加载
*/
@Bean
// 有mouse 这个类就加载
// @ConditionalOnClass(Mouse.class)
// 没有这个Wolf类就加载
@ConditionalOnMissingClass("com.laoshiren.bean.Wolf")
public Cat tom(){
return new Cat();
}
/**
* 按Bean 加载
*/
@Bean
// 有tom猫的话 就没有jerry
@ConditionalOnMissingBean(name = "tom")
public Mouse jerry(){
return new Mouse();
}
@Bean
// 有猫 还想养条狗
@ConditionalOnBean(Cat.class)
public Dog dog(){
return new Dog();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
使用@ConditionalOn***注解为bean的加载设置条件
Bean 依赖属性配置
将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息
java
@Data
@ConfigurationProperties(prefix = "cartoon")
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
1
2
3
4
5
6
2
3
4
5
6
yaml
cartoon:
cat:
name: tom
age: 4
mouse:
name: jerry
age: 3
1
2
3
4
5
6
7
2
3
4
5
6
7
java
@Component
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {
private Cat cat;
private Mouse mouse;
public CartoonCatAndMouse(CartoonProperties cartoonProperties) {
cat = new Cat();
cat.setName(cartoonProperties.getCat()!=null &&
StringUtils.hasText(cartoonProperties.getCat().getName())?
cartoonProperties.getCat().getName():"tom");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 业务bean的属性可以为其设定默认值
- 当需要设置时通过配置文件传递属性
- 业务bean应尽量避免设置强制加载,而是根据需要导入后加载,降低spring容器管理bean的强度
自动配置原理
java
@SpringBootConfiguration
@Configuration
@Component
@Indexed
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class)
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
最重要的就是@Import(AutoConfigurationPackages.Registrar.class)
和 @Import(AutoConfigurationImportSelector.class)
首先看第一个Import
- 先开发若干种技术的标准实现
- SpringBoot启动时加载所有的技术实现对应的自动配置类
- 检测每个配置类的加载条件是否满足并进行对应的初始化
- 切记是先加载所有的外部资源,然后根据外部资源进行条件比对
变更自动配置
自定义自动配置(META-INF/spring.factories)
shell
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.bean.CartoonCatAndMouse
1
2
2
控制SpringBoot内置自动配置类加载
yaml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
1
2
3
4
5
2
3
4
5
@EnableAutoConfiguration(excludeName = "",exclude = {})
排除输入(全类名)的Bean
- 通过配置文件exclude属性排除自动配置
- 通过注解@EnableAutoConfiguration属性排除自动配置项
- 启用自动配置只需要满足自动配置条件即可
- 可以根据需求开发自定义自动配置项
核心原理
- 初始化各种属性,加载成对象
- 读取环境属性(Environment)
- 系统配置(spring.factories)
- 参数(Arguments、application.properties)
- 创建Spring容器对象ApplicationContext,加载各种配置
- 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
- 容器初始化过程中追加各种功能,例如统计时间、输出日志等
java
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// 计时开始
long startTime = System.nanoTime();
// 创建系统引导类 系统引导信息对应的上下文对象
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
// java.awt.headless=true
configureHeadlessProperty();
// 获取当前注册的所有监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 监听器执行了对应的操作步骤
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 获取参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 将前期读取的数据加载成了一个环境对象,用来描述信息
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 做了一个配置,备用
configureIgnoreBeanInfo(environment);
// 初始化logo 不重要
Banner printedBanner = printBanner(environment);
// 创建容器对象,根据前期配置的容器类型进行判定并创建 (AnnotationConfigurationApplicationContext)
context = createApplicationContext();
// 设置启动模式 默认启动模式
context.setApplicationStartup(this.applicationStartup);
// 对容器进行设置,参数来源于前期的设定
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器环境
refreshContext(context);
// 刷新完毕后做后处理
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
// 创建日志对应的对象,输出日志信息,包含启动时间
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), timeTakenToStartup);
}
// 监听器执行了对应的操作步骤
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
// 监听器执行了对应的操作步骤
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59