Spring核心编程思想
IoC容器概述
依赖查找
根据名称查找分为实时查找和延迟查找,实时查找是通过beanFactory.getBean("beanName")直接查找,延迟查找是先返回bean的代理,比如ObjectFactoryCreatingFactoryBean,再调用代理类的获取bean的方法,这个时候才会真正的实例化需要获取的bean。还可以根据注解查找单个bean或者集合bean、根据类型查找单个bean或者集合bean。
/**
* 依赖查找示例
* 1. 通过名称的方式来查找
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @since
*/
public class DependencyLookupDemo {
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup-context.xml");
// 按照类型查找
lookupByType(beanFactory);
// 按照类型查找结合对象
lookupCollectionByType(beanFactory);
// 通过注解查找对象
lookupByAnnotationType(beanFactory);
// lookupInRealTime(beanFactory);
// lookupInLazy(beanFactory);
}
private static void lookupByAnnotationType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
System.out.println("查找标注 @Super 所有的 User 集合对象:" + users);
}
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合对象:" + users);
}
}
private static void lookupByType(BeanFactory beanFactory) {
User user = beanFactory.getBean(User.class);
System.out.println("实时查找:" + user);
}
private static void lookupInLazy(BeanFactory beanFactory) {
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
System.out.println("延迟查找:" + user);
}
private static void lookupInRealTime(BeanFactory beanFactory) {
User user = (User) beanFactory.getBean("user");
System.out.println("实时查找:" + user);
}
}
依赖注入
在进行依赖注入时,可以通过名称注入单个bean,也可以通过类型注入单个或者集合类型bean,注入内建类型bean,注入非bean(依赖)对象,根据类型进行实时注入或者延迟注入。
依赖来源
- 自定义的bean;
- 内建的bean,例如Environment.class
- bean内建的依赖, 例如BeanFactory.class。
都可以进行注入。
BeanFactory和ApplicationContext有何区别?
- ApplicationContext是BeanFactory的子接口;
- BeanFactory是一个底层的IOC容器,提供了IOC容器的基本实现,而ApplicationContext则是BeanFactory的超集提供了丰富的企业级特性。
- ApplicationContext是委托DefaultListableBeanFactory来实现Bean的依赖查找和依赖注入。
Spring bean基础
bean definition
通过BeanDefinitionBuilder构建bean definition:
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
// 通过属性设置
beanDefinitionBuilder
.addPropertyValue("id", 1)
// 获取 BeanDefinition 实例
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
通过 AbstractBeanDefinition 以及派生类:
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
// 设置 Bean 类型
genericBeanDefinition.setBeanClass(User.class);
// 通过 MutablePropertyValues 批量操作属性
MutablePropertyValues propertyValues = new MutablePropertyValues();
// propertyValues.addPropertyValue("id", 1);
propertyValues
.add("id", 1)
.add("name", "小马哥");
// 通过 set MutablePropertyValues 批量操作属性
genericBeanDefinition.setPropertyValues(propertyValues);
bean 的命名
每个bean的名称是在其容器中唯一的,并非整个应用唯一。对bean 名称没有特别规定,任意字符皆可。在定义bean时,id和名称并不是必须的,可以留空,bean容器会自动生成名称。Spring容器提供了两种生成bean name的方式。
默认实现: DefaultBeanNameGenerator,如果是一个内部bean,bean name 是在类名+#+此bean的hashCode; 如果是顶级bean,类名为MyService,为了防止命名冲突,生成的bean name 是myService#0,数字以此类推。
注解实现: 实现类:AnnotationBeanNameGenerator,在标记@Component注解或以之为元注解的注解(@Service、@controller)等,如果没有提供命名,会自动为其生成bean name。在获取bean name时,首先会通过获取注解中提供的value值作为命名,如果没有提供,发现类的前两个字符都是大写,则直接返回类名,MYService 否则将类名的第一个字母转成小写,然后返回 MyService -> myService
注册Spring bean
可以通过XML方式、注解方式(@Import, @Component, @ Bean), 也可以通过Bean Defination API 。
Spring bean实例化方式
初始化 bean
执行顺序依次由上往下。
延迟初始化 bean
可以通过@Lazy注解或者在XML定义bean时使用lazy-init=true来使用延迟初始化,默认是非延迟的。非延迟初始化是Spring上下文初始化时就已经触发了bean的初始化。延迟初始化是依赖查找时才触发初始化,开始只会返回其代理对象。
销毁 bean
在关闭Spring应用上下文时调用,如果三个方法都提供了实现,调用顺序依次是由上往下。
Spring bean IoC依赖查找
单一类型查找
集合类型依赖查找
ListableBeanFactory可以通过某个类型去查找一个集合的列表,集合列表可能有两种情况,一种是查询Bean的名称、还有一种是查询Bean的实例。推荐使用Bean的名称来判断Bean是否存在,这种方式可以避免提早初始化Bean,产生一些不确定的因素。
- 当你使用Bean的名称来查找时,你实际上是在询问容器是否知道特定名称的Bean。这通常通过getBeanDefinitionNames()或containsBean(String name)方法实现。这种查询方式不会立即创建或初始化Bean,而只是检查配置中是否有这样的Bean定义。这种方式的优点在于:
- 延迟初始化:避免了不必要的Bean初始化,因为某些Bean可能在整个应用运行期间都不会被用到。
- 减少不确定因素:由于不立即创建Bean,因此可以避免因依赖关系未准备好或其他初始化问题导致的错误。
- 查询Bean的实例 另一种方式是直接获取Bean的实例,通常通过getBean(String name, Class requiredType)或getBeansOfType(Class type)等方法。这种方法会立即创建并返回Bean实例,这意味着如果Bean的初始化过程中有任何问题,如循环依赖、配置错误等,这些问题会在第一次尝试获取Bean时立即暴露出来。
推荐做法 推荐使用Bean的名称来判断Bean是否存在,而不是直接获取Bean实例。这样做可以保持应用的轻量级和响应速度,同时避免不必要的资源消耗。例如,在应用启动阶段,你可以先检查所需Bean是否存在于容器中,只有当确实需要使用到该Bean时,才通过其名称获取其实例。 这种方式遵循了懒加载原则,有助于提高应用的性能和稳定性。在实际开发中,合理地选择查询方式,可以有效地管理和优化Spring应用的启动时间和运行效率。
层次查找
依赖查找异常
IoC 依赖注入
自动绑定
通过注解@Autowired或者XML定义bean时使用autowire进行绑定,byName, byType等方式。
byType时,如果有多个,可以给需要注入的bean标上注解@Primary。
Setter注入
分为自动和手动注入,其中手动注入分为Xml注入、注解注入、API注入,自动注入分为byName和ByType。
- Xml注入,UserHolder类的user属性通过Setter注入的方式的实现。
<bean class="UserHolder">
<property name="user" ref="superUser" />
</bean>
- 注解注入,在构造userHolder时,会从上下文中寻找user进行注入。
@Bean
public UserHolder userHolder(User user) {
UserHolder userHolder = new UserHolder();
userHolder.setUser(user);
return userHolder;
}
- 通过API注入,自动在Spring应用上下文寻找bean name为superuser的bean注入到user。
private static BeanDefinition createUserHolderBeanDefinition() {
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);
definitionBuilder.addPropertyReference("user", "superUser");
return definitionBuilder.getBeanDefinition();
}
构造器注入
- 通过Xml注入,使用constructor-arg标签,会自动寻找bean为其构造参数注入。
<bean class="UserHolder">
<constructor-arg name="user" ref="superUser" />
</bean>
- 注解注入
@Bean
public UserHolder userHolder(User user) { // superUser -> primary = true
UserHolder userHolder = new UserHolder(user);
return userHolder;
}
- API注入
private static BeanDefinition createUserHolderBeanDefinition() {
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);
definitionBuilder.addConstructorArgReference("superUser");
return definitionBuilder.getBeanDefinition();
}
- 自动注入
<bean class="UserHolder" autowire="constructor">
</bean>
自动注入
可以通过@Autowired, @Resource, @Inject进行注入,@Autowired默认忽略static字段。@Autowired 通过类型进行自动装配;@Resource 默认按照名称进行装配(也可以指定名称);@Inject 类似于 @Autowired,也是通过类型进行装配。
方法注入
可以通过@Autowired, @Resource, @Inject, @Bean等注解,标注在方法上,与方法名称无关,只有Spring 应用上下文中能找到方法参数类型的bean,即可实现注入。
@Autowired
public void init1(UserHolder userHolder) {
this.userHolder = userHolder;
}
@Resource
public void init2(UserHolder userHolder2) {
this.userHolder2 = userHolder2;
}
@Bean
public UserHolder userHolder(User user) {
return new UserHolder(user);
}
几种注入方式的选择
- 构造器注入,适用于低依赖情况;
- Setter方法注入,多依赖的情况,但是注入时机没有先后顺序,如果依赖之间存在依赖,可能会有问题;
- 字段注入,比较便利,但趋于淘汰
- 方法注入,使用@Bean进行注入。
原生类型也能注入bean属性,这算依赖注入吗
在Java中,原生类型(例如int、double等)是无法直接进行依赖注入的。依赖注入通常是指将一个对象注入到另一个对象中,而原生类型并不是对象,因此不能算作依赖注入。
然而,在Spring框架中,可以通过使用@Value注解来实现对原生类型的属性进行注入。@Value 注解可以用于将属性值从外部配置文件或其他来源直接注入到bean的属性中,包括基本数据类型和字符串等。
虽然@Value 注解能够实现对原生类型属性的值的注入,但严格意义上讲不属于传统意义上的依赖注入。传统意义上的依赖注入更多是针对对象之间的关系进行管理和维护。
因此,尽管可以使用@Value 注解实现对原生类型属性值的设置和获取,在严格意义上并不能算作传统意义上的依赖注入。
限定注入@Qualifier
当Spring应用上下文中有多个同类型的bean并且没有对bean指定@Primary时,如果通过@Autowired进行注入,会报出异常,此时可以使用Qualifier声明需要注入的bean。
@Autowired
@Qualifier("user") // 指定 Bean 名称或 ID
private User namedUser;
@Qualifier不仅仅可以标注在需要进行依赖注入的字段上面,也可以标注在提供依赖的方法上面,对标注了@Qualifier的bean,会对其进行分组,在进行注入时,与没有标注该注解的bean区分开。
@Bean
@Qualifier // 进行逻辑分组
public User user1() {
return createUser(7L);
}
@Bean
@Qualifier // 进行逻辑分组
public static User user2() {
return createUser(8L);
}
延迟依赖注入
通过ObjectProvider或者ObjectFactory可以实现延迟注入。
@Autowired
@Qualifier("user")
private User user; // 实时注入
@Autowired
private ObjectProvider<User> userObjectProvider; // 延迟注入
@Autowired
private ObjectFactory<Set<User>> usersObjectFactory; //延迟注入
Spring bean作用域
Singleton
默认的作用域,在一定的范围内,多次注入一个bean,注入的都是同一个对象。对呀一个bean是不是singleton的,在其BeanDefinition中进行定义。
原型bean
单例bean Spring会维护bean的整个生命周期,原型bean Spring不会维护bean的完整生命周期,在使用原型的bean时,要注意原型bean的销毁工作,使用不当甚至会导致OOM
Spring bean生命周期
Spring bean definition元信息配置🆚解析阶段
通过配置文件或者配置资源对对bean definition进行配置。
-
通过XML文件配置
-
通过property文件配置
user.(class) = org.geekbang.thinking.in.spring.ioc.overview.domain.User
user.id = 001
user.name = someone
user.city = HANGZHOU
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 实例化基于 Properties 资源 BeanDefinitionReader
PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory);
String location = "META-INF/user.properties";
// 基于 ClassPath 加载 properties 资源
Resource resource = new ClassPathResource(location);
// 指定字符编码 UTF-8
EncodedResource encodedResource = new EncodedResource(resource, "UTF-8");
int beanNumbers = beanDefinitionReader.loadBeanDefinitions(encodedResource);
System.out.println("已加载 BeanDefinition 数量:" + beanNumbers);
// 通过 Bean Id 和类型进行依赖查找
User user = beanFactory.getBean("user", User.class);
- 通过注解配置
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 基于 Java 注解的 AnnotatedBeanDefinitionReader 的实现
AnnotatedBeanDefinitionReader beanDefinitionReader = new AnnotatedBeanDefinitionReader(beanFactory);
int beanDefinitionCountBefore = beanFactory.getBeanDefinitionCount();
// 注册当前类(非 @Component class)
beanDefinitionReader.register(AnnotatedBeanDefinitionParsingDemo.class);
int beanDefinitionCountAfter = beanFactory.getBeanDefinitionCount();
int beanDefinitionCount = beanDefinitionCountAfter - beanDefinitionCountBefore;
System.out.println("已加载 BeanDefinition 数量:" + beanDefinitionCount);
// 普通的 Class 作为 Component 注册到 Spring IoC 容器后,通常 Bean 名称为 annotatedBeanDefinitionParsingDemo
// Bean 名称生成来自于 BeanNameGenerator,注解实现 AnnotationBeanNameGenerator
AnnotatedBeanDefinitionParsingDemo demo = beanFactory.getBean("annotatedBeanDefinitionParsingDemo",
AnnotatedBeanDefinitionParsingDemo.class);
Spring bean注册阶段
BeanDefinition注册接口:BeanDefinitionRegistry,核心实现:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
Spring BeanDefinition合并阶段
bean的DeanDefinition一开始都是geniticDeanDefinition,没有继承的Bean定义最终会生成RootBeanDefinition,这种BeanDefinition不需要合并,禁止对setParentName方法设置值。而存在parent的Bean定义则是由普通的GenericBeanDefinition,需要合并parent的BeanDefinition属性,成为RootBeanDefinition,保存到mergedBeanDefinitions。合并操作可以建立bean的配置。
Spring Bean Class加载
从BeanDefinition信息中,利用传统的类加载技术,得到Class信息。
实例化前阶段
通过实现InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation方法,可以在创建bean实例时,调用resolveBeforeInstantiation时会先调用我们实现的postProcessBeforeInstantiation方法,在这个过程中我们可以创建返回实例对象(图中2处返回),这样框架就不会执行创建bean的过程(图中3)。用于生成代理对象替换掉IoC容器生成的bean。
实例化阶段
在实例化阶段寻找构造器参数时,按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者参数名称)。
实例化后阶段
在实例化完成bean以后,需要进行属性填充。可以实现InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation方法,此方法返回false时,不会根据BeanDefinition中对属性的配置进行填充熟悉,我们可以自行在此方法中填充bean的熟悉。
属性赋值前操作
我们可以通过InstantiationAwareBeanPostProcessor#postProcessProperties和InstantiationAwareBeanPostProcessor#postProcessPropertyValues方法,在属性填充之前修改需要填充给bean的属性,前者优先级更高,如果实现了前者,后者就不会执行。
Aware接口回调
在填充完成属性以后,开始进入回调接口阶段。如果是普通BeanFactory回调只有三个:
如果是ApplicationContext,回调会更多。
Spring bean初始化
bean在初始化时回调,顺序由上往下。@PostConstruct依赖注解驱动,BeanFactory需要添加CommonAnnotationBeanPostProcessor。
Spring Bean初始化后阶段
可以实现此方法BeanPostProcessor#postProcessAfterInitialization对初始化后的bean进一步修改。
初始化后阶段
我们可以实现SmartInitializingSingleton#afterSingletonsInstantiated方法,此方法会在bean都已经完成初始化后回调。
销毁前阶段
我们可以实现DestructionAwareBeanPostProcessor#postProcessBeforeDestruction方法,在消化bean前做一些动作,比如释放资源。当Spring上下文关闭或者我们显式调用此方法时,方法被调用。
销毁阶段
- @PreDestroy
- 实现DisposableBean#destroy方法
- 自定义销毁方法
Spring 注解
核心注解场景分类
- 模式注解
@Repository, @Service, @Controller, @Component, 前三者都是由@Component派生而来,可以更明确的告诉我们领域划分。@Configuration注解,表明这是一个配置类。
- 装配注解
@Import 导入Configuration类 @ComponentScan 扫码指定package下标注模式注解的类。
- 依赖注入注解
@Autowired bean依赖注入,支持多种依赖查找模式。
@Qualifier 细粒度的@Autowired依赖查找。
元注解
标注在注解之上的注解,@Target, @Document, @Rentation, @Inherited。
模式注解
@Repository, @Service, @Controller等注解都是由@Component注解派生而来,效果等同于@Component注解。在Spring应用进行包扫描时,标注了@Component的类都会被注册为一个bean。Spring支持派生,我们可以通过现有的注解定义我们的注解。
组合注解
组合多个注解,形成新的注解,这个注解具有组合者的语意。
注解属性别名
显式别名,basePackages和value互为别名,效果等价。
隐式别名,
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
注解属性覆盖
一个注解如果与元注解具有同名属性,会覆盖元注解的属性的内容。
Spring应用上下文生命周期
启动准备阶段
- 记录启动时间;
- 设置close和active状态标识;
- 初始化property Source;
- 检验Environment中的属性;
- 初始化事件监听器集合;
- 初始化早期Spring事件集合;
BeanFactory创建阶段
- 销毁或者关闭已经存在的BeanFactory;
- 创建BeanFactory,通常是DefaultListableBeanFactory;
- 设置BeanFactory id;
- 设置BeanDefinition是否允许重复定义和是否允许循环引用;
- 加载BeanDefinition;
- 将BeanFactory关联到应用上下文;
- 返回应用上下文的BeanFactory;
BeanFactory 准备阶段
添加类加载器、注册Environment, System properties等bean;
BeanFactory 后置处理
包括两个方法,AbstractApplicationContext#postProcessBeanFactory和AbstractApplicationContext#invokeBeanFactoryPostProcessors。AbstractApplicationContext#postProcessBeanFactory(ConfigurableListableBeanFactory) 方法,交由子类实现,例如 AbstractRefreshableApplicationContext 的实现中会添加 ServletContext 相关内容。
AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) 方法,主要是执行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的处理,会做以下事情:
- 如果当前 Spring 应用上下文是 BeanDefinitionRegistry 类型,则执行当前 Spring 应用上下文中所有 BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor 的处理,以及底层 BeanFactory 容器中 BeanDefinitionRegistryPostProcessor 的处理,处理顺序如下: 1. 当前 Spring 应用上下文中所有 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry 2. 底层 BeanFactory 容器中所有 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(优先级:PriorityOrdered > Ordered > 无) 3. 当前 Spring 应用上下文和底层 BeanFactory 容器中所有 BeanDefinitionRegistryPostProcessor#postProcessBeanFactory 4. 当前 Spring 应用上下文中所有 BeanFactoryPostProcessor#postProcessBeanFactory。
- 否则,执行当前 Spring 应用上下文中所有 BeanFactoryPostProcessor#postProcessBeanFactory 3. 执行底层 BeanFactory 容器中所有 BeanFactoryPostProcessor#postProcessBeanFactory,上面已经处理过的会跳过,执行顺序和上面一样:PriorityOrdered > Ordered > 无。
BeanFactory注册BeanPostProcessors
、
先注册实现了PriorityOrdered接口的,其次是Ordered,其次是普通的BeanPostProcessor,再是MergedBeanDefinitionPostProcessor,最后注册ApplicationListenerDetector。
初始化内建MessageSource
初始化Spring事件广播器
上下文刷新
由具体的ApplicationContext实现。
事件监听器注册
- 添加当前应用上下文所关联的ApplicationListener;
- 添加ApplicaticationListenerBean;
- 广播早期Spring事件,回放早期事件给准备好的EventProcess;
BeanFactory初始化完成
在此过程中,会初始化非延迟加载的单例bean。
完成刷新阶段
- 获取LifecycleProcessor Bean并调用onRefresh方法;
- 发布ContextRefreshedEvent事件。
- registerApplicationContext。
应用上下文启动
应用上下文停止
应用上下文关闭
- 通过CAS的方式设置close为false;
- 取消注册ApplicationContext bean;
- 发布ContextClosedEvent事件;
- 回调LifecycleProcessor#onClose;
- 销毁单例bean,可以被GC。
- 将BeanFactory置null,可以被GC; 7.清空ApplicationListeners; 8.将active设置为false。
转载自:https://juejin.cn/post/7390934048258703371