《剖析 Spring 原理:深入源码的旅程(二)》

news2025/6/3 23:12:06

六、Spring 的 Bean 注入与装配

Spring 的 Bean 注入与装配的方式有很多种,可以通过 xml、get set 方式、构造函数或者注解等。简单易用的方式就是使用 Spring 的注解,Spring 提供了大量的注解方式,如 @Autowired、@Qualifier 等。Spring 还支持多种 BeanFactory 和 ApplicationContext,它们提供了不同的方式来实现 Bean 的注入与装配。

Spring 的 Bean 注入与装配的方式主要有以下几种:

  1. 使用 XML 配置文件进行显式配置:
    • 在 XML 文件中,可以通过<bean>标签来定义 Bean,并使用<property>标签或<constructor-arg>标签进行属性注入或构造器注入。例如:
 
<bean id="studentDao" class="com.it.dao.impl.StudentDaoImpl"/>

<bean id="studentService" class="com.it.service.impl.StudentServiceImpl">

<constructor-arg ref="studentDao"/>

</bean>
  1. 在 Java 代码中进行显式配置:
    • 使用@Configuration注解标记配置类,通过@Bean注解定义 Bean。例如:
 
@Configuration

@ComponentScan

public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean

    public Gson gson() {
    
        return new Gson();

    }

}
  1. 自动化装配:
    • 通过扫描包中的注解(如@Component)生成 Bean,使用注解@Autowired自动满足 Bean 之间的依赖。例如:
 
@Service

public class StudentServiceImpl implements StudentService {

private StudentDao studentDao;

    @Override

    public void add() {

        studentDao.add();

    }

    @Override

    public void del() {

        studentDao.del();

    }

}
  • 自动装配可以使用@ComponentScan注解进行组件扫描,也可以在 XML 配置文件中使用<context:component-scan>元素进行扫描。

Bean 注入的方式主要有以下几种:

  1. 属性注入,也就是 setter 注入:
    • 需要生成属性的 set 方法,在 XML 配置文件中使用<property>标签进行属性赋值。例如:
 
<bean id="student" class="com.it.entity.Student">

<property name="id" value="1001"/>

<property name="name" value="sam"/>

<property name="age" value="25"/>

</bean>
  1. 构造器注入:
    • 在类的构造函数中接收依赖对象,在 XML 配置文件中使用<constructor-arg>标签进行构造器参数的注入。例如:
 
<bean id="school" class="com.it.entity.School">

<constructor-arg ref="student"/>

</bean>
  1. 工厂方法注入:包括静态工厂和实例工厂。
    • 静态工厂:通过调用静态工厂的方法来获取对象,在 XML 配置文件中使用<bean>标签的factory-method属性指定静态工厂方法。例如:
 
<bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"/>
  • 实例工厂:先创建工厂类的实例,再调用普通的实例方法获取对象。在 XML 配置文件中,首先定义工厂类的 Bean,然后在需要注入的 Bean 中使用<property>标签或<constructor-arg>标签引用工厂类的 Bean,并指定工厂方法。例如:
 
<bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"/>

<bean name="springAction" class="com.bless.springdemo.action.SpringAction">

<property name="factoryDao" ref="daoFactory" factory-method="getFactoryDaoImpl"/>

</bean>

Spring 还提供了注解方式进行 Bean 注入,主要有以下几种:

  1. @Autowired注解:
    • 自动装配对象,在 Spring 容器中查找相应的对象并将其注入到需要使用的地方。可以用于构造函数、属性、Setter 方法和方法参数上。
    • 默认优先按照类型取 IOC 容器中寻找对应的组件,如果有多个相同类型的组件,再将属性的名称作为组件的 id 去容器中查找。
    • 可以配合@Primary使用,当使用@Autowired自动装配时,默认优先选择被注解@Primary标注的组件;也可以配合@Qualifier使用,使用注解@Qualifier可以指定需要装配组件的 id。
  1. @Qualifier注解:
    • 用于解决依赖注入中的歧义问题。当 Spring 容器中存在多个相同类型的 Bean 时,它允许你指定具体要注入哪一个 Bean。
    • 可以与@Autowired注解一起使用,以实现更精确的依赖注入。

Spring 支持多种 BeanFactory 和 ApplicationContext,它们也提供了不同的方式来实现 Bean 的注入与装配。例如:

  1. BeanFactory:
    • 是 Spring bean 容器的根接口,提供获取 bean、判断 bean 是否存在、判断 bean 是否为单例或原型等方法。
    • 按需加载 bean,是轻量级的容器。
  1. ApplicationContext:
    • 是 BeanFactory 的子接口,提供了更完整的功能,如对国际化的支持、统一的资源文件访问方式、提供在监听器中注册 bean 的事件等。
    • 在启动时加载所有 bean,启动过程可能比较慢,但后续的执行比较快。

七、Spring 的创建 Bean 的流程

Spring 创建一个 Bean 的流程包括根据类的构造方法实例化得到一个对象、进行依赖注入、Aware 回调、初始化前、初始化和初始化后等步骤。如果需要进行 AOP,则会进行动态代理并生成一个代理对象做为 Bean。

一、实例化对象

在 Spring 中,Bean 的创建顺序是由 Bean 的依赖关系决定的。当容器启动时,它会根据 Bean 的定义和依赖关系创建和初始化 Bean。如果一个 Bean 依赖于其他 Bean,那么它会在依赖的 Bean 之后被创建。

Spring 会通过反射方式拿到 Bean 的构造方法,再通过构造方法去创建一个普通对象。具体通过哪一个构造器去创建对象分以下情况:

  1. 类中只有一个构造器,就使用这个构造器。
  1. 类中有多个构造器:
    • 优先选择被@Autowried指定的构造器。
    • 否则,Spring 默认使用无参构造器。
    • 若没有无参构造器,使用类中唯一的构造器。如果既没有为 Spring 指定需要使用的构造器,也没有无参构造器且有多个构造器,程序将报错。

二、依赖注入

依赖注入是一种软件设计模式,用于解耦组件之间的依赖关系。在 Spring 中,依赖注入可以理解为对上个阶段实例化出来的对象填充属性。被@Autowired修饰的属性,会从 Spring 容器中获取对应的值进行注入。如果找不到对应的 Bean,程序会报错。一旦注入成功,普通对象就变成了一个完整的 Bean。

三、Aware 回调

对于 bean 中非依赖注入的属性,Spring 中提供了 Aware 回调接口,用于在 bean 生命周期中依赖注入之后设置其他属性值。例如BeanNameAware接口,实现该接口的 Bean 可以获取到 Spring 容器中自己的 Bean 名称。在 Spring 框架中,Bean 之间的依赖关系通常是通过依赖注入(DI)来实现的。即在 Bean 定义中指定依赖的 Bean 名称或类型,然后由 Spring 容器自动将对应的 Bean 注入到当前 Bean 中。但是,在某些情况下,Bean 需要获取 Spring 容器的一些其他资源或上下文信息,例如获取ApplicationContext对象、获取BeanFactory对象等。这时候,就可以使用 Aware 接口回调来实现。

四、初始化前

在这个阶段,Spring 会判断 Bean 是否实现了一些特定的接口,如BeanPostProcessorAware接口等。如果实现了这些接口,Spring 会执行相应的回调方法,为 Bean 提供一些额外的功能或信息。

五、初始化

初始化就是对 Bean 中属性的进一步加工处理。主要包括以下几个方面:

  1. 调用各种 Aware 接口的回调方法,如BeanNameAware、BeanFactoryAware等,让 Bean 获取到 Spring 容器的相关信息。
  1. 调用BeanPostProcessor的postProcessBeforeInitialization方法,这个方法可以在 Bean 初始化之前对 Bean 进行一些处理。
  1. 调用初始化方法,主要包括InitializingBean的afterPropertiesSet和用户自定义的初始化方法。这些初始化方法可以对 Bean 的属性进行进一步的设置或执行一些特定的逻辑。

六、初始化后(AOP)

如果需要进行 AOP,则会在这个阶段进行动态代理并生成一个代理对象做为 Bean。Spring 在创建一个 Bean 的过程中,最后一步会判断当前正在创建的这个 Bean 是不是需要进行 AOP。如果需要,则会进行以下步骤:

  1. 找出所有的切面 Bean。
  1. 遍历切面中的每个方法,看是否写了@Before、@After等注解。
  1. 如果写了,则判断所对应的Pointcut是否和当前 Bean 对象的类是否匹配。
  1. 如果匹配则表示当前 Bean 对象有匹配的的Pointcut,表示需要进行 AOP。

利用 CGLIB 进行 AOP 的大致流程:以UserService作为被代理类举例说明。

  1. 生成代理类UserServiceProxy,代理类继承UserService。
  1. 代理类中重写了父类的方法,比如UserService中的test()方法。
  1. 代理类中还会有一个target属性,该属性的值为被代理对象(也就是通过UserService类推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)。
  1. 代理类中的test()方法被执行时的逻辑如下:
    • 执行切面逻辑(@Before)。
    • 调用target.test()。

当我们从 Spring 容器得到UserService的 Bean 对象时,拿到的就是UserServiceProxy所生成的对象,也就是代理对象。当执行test方法时,实际上执行的是代理对象的test方法。UserService代理对象.test() -> 执行切面逻辑 -> target.test(),注意target对象不是代理对象,而是被代理对象。

八、Spring 的自定义标签和扩展功能

Spring 支持自定义标签,可以通过定义 XSD 文件、实现 BeanDefinitionParser 接口、创建 NamespaceHandler 文件等方式来实现自定义标签的解析和注册。Spring 还提供了扩展功能,如通过继承 ClassPathXmlApplicationContext 来实现自定义的初始化要求,加载 BeanFactory、注册监听器等。

一、自定义标签的实现方式

1. 定义 XSD 文件

XSD(XML Schema Definition)文件用于描述自定义标签的结构和属性。通过定义 XSD 文件,可以为自定义标签提供明确的规范,使得 Spring 在解析 XML 配置文件时能够正确识别和处理自定义标签。

例如,可以创建一个名为mycustom.xsd的文件,定义一个名为mycustomtag的元素,具有attr1和attr2两个属性:

 
<?xml version="1.0" encoding="UTF-8"?>

<xsd:schema xmlns="http://www.example.com/schema/mycustom"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

targetNamespace="http://www.example.com/schema/mycustom"

elementFormDefault="qualified">

<xsd:element name="mycustomtag">

<xsd:complexType>

<xsd:attribute name="attr1" type="xsd:string"/>

<xsd:attribute name="attr2" type="xsd:int"/>

</xsd:complexType>

</xsd:element>

</xsd:schema>
2. 实现 BeanDefinitionParser 接口

BeanDefinitionParser 接口用于解析自定义标签,并将其转换为 Spring 的 BeanDefinition 对象。通过实现这个接口,可以自定义标签的解析逻辑,将标签中的属性和子元素映射到 BeanDefinition 中。

例如,可以创建一个名为MyCustomTagParser的类,实现 BeanDefinitionParser 接口:

 
import org.springframework.beans.factory.support.BeanDefinitionBuilder;

import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;

import org.springframework.util.StringUtils;

import org.w3c.dom.Element;

public class MyCustomTagParser extends AbstractSingleBeanDefinitionParser {

@Override

protected Class<?> getBeanClass(Element element) {

// 返回自定义标签对应的Bean类

return MyCustomBean.class;

}

@Override

protected void doParse(Element element, BeanDefinitionBuilder builder) {

String attr1 = element.getAttribute("attr1");

int attr2 = Integer.parseInt(element.getAttribute("attr2"));

if (StringUtils.hasText(attr1)) {

builder.addPropertyValue("attr1", attr1);

}

builder.addPropertyValue("attr2", attr2);

}

}
3. 创建 NamespaceHandler 文件

NamespaceHandler 文件用于注册自定义标签的解析器。通过创建 NamespaceHandler 文件,可以将自定义标签与对应的解析器关联起来,使得 Spring 在遇到自定义标签时能够调用正确的解析器进行处理。

例如,可以创建一个名为MyCustomNamespaceHandler的类,继承NamespaceHandlerSupport类:

 
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyCustomNamespaceHandler extends NamespaceHandlerSupport {

public void init() {

registerBeanDefinitionParser("mycustomtag", new MyCustomTagParser());

}

}
4. 修改 spring.handlers 和 spring.schemas 文件

spring.handlers 文件用于将命名空间与 NamespaceHandler 关联起来,spring.schemas 文件用于指定命名空间对应的 XSD 文件路径。通过修改这两个文件,可以让 Spring 在解析 XML 配置文件时能够找到自定义标签的解析器和 XSD 文件。

例如,可以在META-INF目录下创建spring.handlers和spring.schemas文件:

spring.handlers文件内容:

 

http://www.example.com/schema/mycustom = com.example.MyCustomNamespaceHandler

spring.schemas文件内容:

 

http://www.example.com/schema/mycustom/mycustom.xsd = META-INF/mycustom.xsd

二、扩展功能的实现方式

Spring 提供了多种扩展功能,如通过继承 ClassPathXmlApplicationContext 来实现自定义的初始化要求,加载 BeanFactory、注册监听器等。

1. 继承 ClassPathXmlApplicationContext

可以通过继承ClassPathXmlApplicationContext类来实现自定义的初始化要求。在子类中,可以重写postProcessBeanFactory方法,在该方法中进行自定义的初始化操作。

例如,可以创建一个名为MyCustomApplicationContext的类,继承ClassPathXmlApplicationContext类:

 
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {

    public MyCustomApplicationContext(String... configLocations) {

        super(configLocations);

    }

    @Override

    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

        // 在这里进行自定义的初始化操作

    }

}
2. 加载 BeanFactory

可以在自定义的ApplicationContext子类中,通过调用loadBeanDefinitions方法来加载 BeanFactory。在加载 BeanFactory 的过程中,可以使用自定义的BeanDefinitionReader来解析 XML 配置文件中的自定义标签。

例如,可以在MyCustomApplicationContext类的构造方法中加载 BeanFactory:

 
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {

    public MyCustomApplicationContext(String... configLocations) {

        super(configLocations);

        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(getBeanFactory());

        reader.loadBeanDefinitions(configLocations);

    }

    @Override

    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

        // 在这里进行自定义的初始化操作

    }

}
3. 注册监听器

可以在自定义的ApplicationContext子类中,通过调用addApplicationListener方法来注册监听器。监听器可以在应用程序的不同阶段接收事件通知,并进行相应的处理。

例如,可以在MyCustomApplicationContext类的构造方法中注册监听器:

 
import org.springframework.context.ApplicationEvent;

import org.springframework.context.ApplicationListener;

import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class MyCustomApplicationContext extends ClassPathXmlApplicationContext {

    public MyCustomApplicationContext(String... configLocations) {

        super(configLocations);

    addApplicationListener(new ApplicationListener<ApplicationEvent>() {

    @Override

    public void onApplicationEvent(ApplicationEvent event) {

        // 在这里处理应用程序事件

    }

    });

    }

    @Override

    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

        // 在这里进行自定义的初始化操作

    }

}

通过以上方式,可以实现 Spring 的自定义标签和扩展功能,使得 Spring 在应用程序开发中更加灵活和可扩展。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2247388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git的使用_仓库管理_CI/CD介绍

文章目录 一、Git的基础知识一-1、什么是GitLinux命令行的git的简易安装Git项目的组成Git的基本工作流程Git文件的三种状态 一-2、存储库远程存储库与本地存储库创建存储库git init命令的使用方法1. 初始化一个新的 Git 仓库2. 在指定目录初始化一个新的 Git 仓库3. 初始化一个…

Android 实现悬浮球的功能

Android 实现悬浮球的功能 在 Android 中&#xff0c;实现悬浮球可以通过以下方式实现&#xff0c;常见的方法是使用 WindowManager 创建一个悬浮窗口。以下是具体的实现步骤&#xff1a; 1. 配置权限 在 AndroidManifest.xml 中添加悬浮窗权限&#xff1a; <uses-permis…

C语言数据结构学习:循环队列

C语言 数据结构学习 汇总入口&#xff1a; C语言数据结构学习&#xff1a;[汇总] 1. 循环队列 队列的博客&#xff1a;C语言数据结构学习&#xff1a;队列 循环队列会预先定义最大队列空间&#xff0c;然后定义一个数组&#xff0c;通过队列头和队列尾指针分别指向开头和结尾&…

Java教程:SE进阶【十万字详解】(下)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

37_U-Net网络详解

1.U-Net 网络概述 U-Net 是一种深度学习模型&#xff0c;广泛用于图像的语义分割任务。U-Net 网络的结构特别适合医学影像分割&#xff0c;尤其在少量训练数据的情况下表现优异。该网络由一个编码器-解码器架构组成&#xff0c;具有对称的“U”形结构&#xff0c;因此得名为 U…

mysql-分析MVCC原理

一、MVCC简介 MVCC是一种用来解决读写冲读的无锁并发控制&#xff0c;也就是为事务分配单增长的时间戳&#xff0c;为每个修改保存一个版本&#xff0c;版本与事务时间戳关联&#xff0c;读操作只读该事务开始前的数据库的快照&#xff0c;所以MVCC可以为数据库解决一些问题。…

【CSP CCF记录】201812-2第15次认证 小明放学

题目 样例1输入 30 3 30 8 0 10 1 5 0 11 2 2 0 6 0 3 3 10 0 3 样例1输出 30 3 30 8 0 10 1 5 0 11 2 2 0 6 0 3 3 10 0 3 思路 参考&#xff1a;CCF小白刷题之路---201812-2 小明放学&#xff08;C/C 100分&#xff09;_小明放学测试数据-CSDN博客 我们使用一个for循环计算…

Kafka 分区分配及再平衡策略深度解析与消费者事务和数据积压的简单介绍

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…

计算机网络-VPN虚拟专用网络概述

前面我们学习了在企业内部的二层交换机网络、三层路由网络包括静态路由、OSPF、IS-IS、NAT等&#xff0c;现在开始学习下VPN&#xff08;Virtual Private Network&#xff0c;虚拟专用网络&#xff09;&#xff0c;其实VPN可能很多人听到第一反应就是梯子&#xff0c;但是其实这…

《第十部分》1.STM32之通信接口《精讲》之IIC通信---介绍

经过近一周的USART学习&#xff0c;我深刻体会到通信对单片机的重要性。它就像人类的手脚和大脑&#xff0c;只有掌握了通信技术&#xff0c;单片机才能与外界交互&#xff0c;展现出丰富多彩的功能&#xff0c;变得更加强大和实用。 单片机最基础的“语言”是二进制。可惜&am…

【蓝桥杯C/C++】深入解析I/O高效性能优化:std::ios::sync_with_stdio(false)

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 蓝桥杯C/C 文章目录 &#x1f4af;前言&#x1f4af;C 语言与 C 语言的输入输出对比1.1 C 语言的输入输出1.2 C 语言的输入输出 &#x1f4af; std::ios::sync_with_stdio(false) 的作用与意义2.1 什么是 std::ios::s…

初识Linux—— 基本指令(下)

前言&#xff1a; 本篇继续来学习Linux的基础指令&#xff0c;继续加油&#xff01;&#xff01;&#xff01; 本篇文章对于图片即内容详解&#xff0c;已同步到本人gitee&#xff1a;Linux学习: Linux学习与知识讲解 Linux指令 1、查看文件内容的指令 cat ​ cat 查看文件…

VM虚拟机装MAC后无法联网,如何解决?

✨在vm虚拟机上&#xff0c;给虚拟机MacOS设置网络适配器。选择NAT模式用于共享主机的IP地址 ✨在MacOS设置中设置网络 以太网 使用DHCP ✨回到本地电脑上&#xff0c;打开 服务&#xff0c;找到VMware DHCP和VMware NAT&#xff0c;把这两个服务打开&#xff0c;专一般问题就…

MCGSMCGS昆仑通态触摸屏

MCGS昆仑通态触摸屏应用实例详解 1目录设置 本案例讲了两个窗口的互相调用 创建工程 首先创建一个新工程 打开软件 McgsPro组态软件 菜单栏&#xff1a;文件&#xff1a;新建工程 打开工程设置窗口 HMI配置中应该是对应的不同型号的触摸屏&#xff0c; 选择一个类型&#x…

aws ses生产环境申请

* aws ses生产环境申请经验&#xff1a; 要有域名邮箱作为反馈联系邮箱 且有收发记录 最好使用aws的WorkMail要说明清晰的使用用途、预估量、如何处理退信和投诉、防spam策略 等内容&#xff0c;这里可以先问问AI&#xff08;比如&#xff1a;如何处理退信和投诉&#xff1f;…

MongoDB相关问题

视频教程 【GeekHour】20分钟掌握MongoDB Complete MongoDB Tutorial by Net Ninja MongoDB开机后调用缓慢的原因及解决方法 问题分析&#xff1a; MongoDB开机后调用缓慢&#xff0c;通常是由于以下原因导致&#xff1a; 索引重建&#xff1a; MongoDB在启动时会重建索引…

pytest日志总结

pytest日志分为两类&#xff1a; 一、终端&#xff08;控制台&#xff09;打印的日志 1、指定-s&#xff0c;脚本中print打印出的信息会显示在终端&#xff1b; 2、pytest打印的summary信息&#xff0c;这部分是pytest 的默认输出&#xff08;例如测试结果PASSED, FAILED, S…

mysql系列1—mysql架构和协议介绍

背景&#xff1a; 本文开始整理mysql相关的文章&#xff0c;用于收集数据库相关内容&#xff1b;包括mysql架构和存储方式、索引结构和查询优化、数据库锁等内容。思考如何根据具体的业务给出最优的分表规划和表设计、字段选择和索引设计、优化的SQL语句&#xff0c;以及数据库…

Opencv+ROS实现摄像头读取处理画面信息

一、工具 ubuntu18.04 ROSopencv2 编译器&#xff1a;Visual Studio Code 二、原理 图像信息 ROS数据形式&#xff1a;sensor_msgs::Image OpenCV数据形式&#xff1a;cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…

docker容器化部署springboot项目

前言 docker安装 下载官网 选择自己的系统 然后安装文档内给的命令按顺序执行即可。设置仓库&#xff0c;安装docker. 一、更换镜像源 一般情况下,docker原本自带的镜像网站不一定连的上,就很容易导致下载镜像失败,因此需要换源. 创建/etc/docker/daemon.json并填入数据…