likes
comments
collection
share

Spring核心编程思想

作者站长头像
站长
· 阅读数 18

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

Spring核心编程思想

通过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实例化方式

Spring核心编程思想

初始化 bean

Spring核心编程思想 执行顺序依次由上往下。

延迟初始化 bean

可以通过@Lazy注解或者在XML定义bean时使用lazy-init=true来使用延迟初始化,默认是非延迟的。非延迟初始化是Spring上下文初始化时就已经触发了bean的初始化。延迟初始化是依赖查找时才触发初始化,开始只会返回其代理对象。

销毁 bean

Spring核心编程思想 在关闭Spring应用上下文时调用,如果三个方法都提供了实现,调用顺序依次是由上往下。

Spring bean IoC依赖查找

单一类型查找

Spring核心编程思想

集合类型依赖查找

Spring核心编程思想 ListableBeanFactory可以通过某个类型去查找一个集合的列表,集合列表可能有两种情况,一种是查询Bean的名称、还有一种是查询Bean的实例。推荐使用Bean的名称来判断Bean是否存在,这种方式可以避免提早初始化Bean,产生一些不确定的因素。

  1. 当你使用Bean的名称来查找时,你实际上是在询问容器是否知道特定名称的Bean。这通常通过getBeanDefinitionNames()或containsBean(String name)方法实现。这种查询方式不会立即创建或初始化Bean,而只是检查配置中是否有这样的Bean定义。这种方式的优点在于:
  • 延迟初始化:避免了不必要的Bean初始化,因为某些Bean可能在整个应用运行期间都不会被用到。
  • 减少不确定因素:由于不立即创建Bean,因此可以避免因依赖关系未准备好或其他初始化问题导致的错误。
  1. 查询Bean的实例 另一种方式是直接获取Bean的实例,通常通过getBean(String name, Class requiredType)或getBeansOfType(Class type)等方法。这种方法会立即创建并返回Bean实例,这意味着如果Bean的初始化过程中有任何问题,如循环依赖、配置错误等,这些问题会在第一次尝试获取Bean时立即暴露出来。

推荐做法 推荐使用Bean的名称来判断Bean是否存在,而不是直接获取Bean实例。这样做可以保持应用的轻量级和响应速度,同时避免不必要的资源消耗。例如,在应用启动阶段,你可以先检查所需Bean是否存在于容器中,只有当确实需要使用到该Bean时,才通过其名称获取其实例。 这种方式遵循了懒加载原则,有助于提高应用的性能和稳定性。在实际开发中,合理地选择查询方式,可以有效地管理和优化Spring应用的启动时间和运行效率。

层次查找

Spring核心编程思想

依赖查找异常

Spring核心编程思想

IoC 依赖注入

自动绑定

通过注解@Autowired或者XML定义bean时使用autowire进行绑定,byName, byType等方式。

Spring核心编程思想 byType时,如果有多个,可以给需要注入的bean标上注解@Primary。

Setter注入

分为自动和手动注入,其中手动注入分为Xml注入、注解注入、API注入,自动注入分为byName和ByType。

  1. Xml注入,UserHolder类的user属性通过Setter注入的方式的实现。
<bean class="UserHolder">  
<property name="user" ref="superUser" />  
</bean>
  1. 注解注入,在构造userHolder时,会从上下文中寻找user进行注入。
@Bean  
public UserHolder userHolder(User user) {  
UserHolder userHolder = new UserHolder();  
userHolder.setUser(user);  
return userHolder;  
}
  1. 通过API注入,自动在Spring应用上下文寻找bean name为superuser的bean注入到user。
private static BeanDefinition createUserHolderBeanDefinition() {  
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);  
definitionBuilder.addPropertyReference("user", "superUser");  
return definitionBuilder.getBeanDefinition();  
}

构造器注入

  1. 通过Xml注入,使用constructor-arg标签,会自动寻找bean为其构造参数注入。
<bean class="UserHolder">  
<constructor-arg name="user" ref="superUser" />  
</bean>
  1. 注解注入
@Bean  
public UserHolder userHolder(User user) { // superUser -> primary = true  
UserHolder userHolder = new UserHolder(user);    
return userHolder;  
}
  1. API注入
private static BeanDefinition createUserHolderBeanDefinition() {  
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserHolder.class);  
definitionBuilder.addConstructorArgReference("superUser");  
return definitionBuilder.getBeanDefinition();  
}
  1. 自动注入
<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作用域

Spring核心编程思想

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信息。

实例化前阶段

Spring核心编程思想 通过实现InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation方法,可以在创建bean实例时,调用resolveBeforeInstantiation时会先调用我们实现的postProcessBeforeInstantiation方法,在这个过程中我们可以创建返回实例对象(图中2处返回),这样框架就不会执行创建bean的过程(图中3)。用于生成代理对象替换掉IoC容器生成的bean。

实例化阶段

在实例化阶段寻找构造器参数时,按类型找->通过限定符@Qualifier过滤->@Primary->@Priority->根据名称找(字段名称或者参数名称)。

实例化后阶段

在实例化完成bean以后,需要进行属性填充。可以实现InstantiationAwareBeanPostProcessor接口的postProcessBeforeInstantiation方法,此方法返回false时,不会根据BeanDefinition中对属性的配置进行填充熟悉,我们可以自行在此方法中填充bean的熟悉。

Spring核心编程思想

属性赋值前操作

我们可以通过InstantiationAwareBeanPostProcessor#postProcessProperties和InstantiationAwareBeanPostProcessor#postProcessPropertyValues方法,在属性填充之前修改需要填充给bean的属性,前者优先级更高,如果实现了前者,后者就不会执行。

Aware接口回调

在填充完成属性以后,开始进入回调接口阶段。如果是普通BeanFactory回调只有三个: Spring核心编程思想 如果是ApplicationContext,回调会更多。 Spring核心编程思想

Spring bean初始化

Spring核心编程思想 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支持派生,我们可以通过现有的注解定义我们的注解。

组合注解

组合多个注解,形成新的注解,这个注解具有组合者的语意。

Spring核心编程思想

注解属性别名

显式别名,basePackages和value互为别名,效果等价。

Spring核心编程思想 隐式别名,

@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")  
String[] scanBasePackages() default {};

注解属性覆盖

一个注解如果与元注解具有同名属性,会覆盖元注解的属性的内容。

Spring应用上下文生命周期

启动准备阶段

Spring核心编程思想

  1. 记录启动时间;
  2. 设置close和active状态标识;
  3. 初始化property Source;
  4. 检验Environment中的属性;
  5. 初始化事件监听器集合;
  6. 初始化早期Spring事件集合;

BeanFactory创建阶段

Spring核心编程思想

Spring核心编程思想

  1. 销毁或者关闭已经存在的BeanFactory;
  2. 创建BeanFactory,通常是DefaultListableBeanFactory;
  3. 设置BeanFactory id;
  4. 设置BeanDefinition是否允许重复定义和是否允许循环引用;
  5. 加载BeanDefinition;
  6. 将BeanFactory关联到应用上下文;
  7. 返回应用上下文的BeanFactory;

BeanFactory 准备阶段

添加类加载器、注册Environment, System properties等bean;

BeanFactory 后置处理

包括两个方法,AbstractApplicationContext#postProcessBeanFactory和AbstractApplicationContext#invokeBeanFactoryPostProcessors。AbstractApplicationContext#postProcessBeanFactory(ConfigurableListableBeanFactory) 方法,交由子类实现,例如 AbstractRefreshableApplicationContext 的实现中会添加 ServletContext 相关内容。

AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) 方法,主要是执行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的处理,会做以下事情:

  1. 如果当前 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。
  2. 否则,执行当前 Spring 应用上下文中所有 BeanFactoryPostProcessor#postProcessBeanFactory 3. 执行底层 BeanFactory 容器中所有 BeanFactoryPostProcessor#postProcessBeanFactory,上面已经处理过的会跳过,执行顺序和上面一样:PriorityOrdered > Ordered > 无。

BeanFactory注册BeanPostProcessors

Spring核心编程思想、 先注册实现了PriorityOrdered接口的,其次是Ordered,其次是普通的BeanPostProcessor,再是MergedBeanDefinitionPostProcessor,最后注册ApplicationListenerDetector。

初始化内建MessageSource

Spring核心编程思想

初始化Spring事件广播器

Spring核心编程思想

上下文刷新

由具体的ApplicationContext实现。

事件监听器注册

Spring核心编程思想

  1. 添加当前应用上下文所关联的ApplicationListener;
  2. 添加ApplicaticationListenerBean;
  3. 广播早期Spring事件,回放早期事件给准备好的EventProcess;

BeanFactory初始化完成

Spring核心编程思想 在此过程中,会初始化非延迟加载的单例bean。

完成刷新阶段

Spring核心编程思想

  1. 获取LifecycleProcessor Bean并调用onRefresh方法;
  2. 发布ContextRefreshedEvent事件。
  3. registerApplicationContext。

应用上下文启动

Spring核心编程思想

应用上下文停止

Spring核心编程思想

应用上下文关闭

Spring核心编程思想

Spring核心编程思想

  1. 通过CAS的方式设置close为false;
  2. 取消注册ApplicationContext bean;
  3. 发布ContextClosedEvent事件;
  4. 回调LifecycleProcessor#onClose;
  5. 销毁单例bean,可以被GC。
  6. 将BeanFactory置null,可以被GC; 7.清空ApplicationListeners; 8.将active设置为false。
转载自:https://juejin.cn/post/7390934048258703371
评论
请登录