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

六、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 在应用程序开发中更加灵活和可扩展。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java文件上传解压

目录结构 工具类 枚举 定义文件类型 public enum FileType {// 未知UNKNOWN,// 压缩文件ZIP, RAR, _7Z, TAR, GZ, TAR_GZ, BZ2, TAR_BZ2,// 位图文件BMP, PNG, JPG, JPEG,// 矢量图文件SVG,// 影音文件AVI, MP4, MP3, AAR, OGG, WAV, WAVE}为了避免文件被修改后缀&#xff0…

CSRF保护--laravel进阶篇

laravel对csrf非常重视&#xff0c;专门针对csrf作出了很多的保护。如果您是刚刚接触laravel的路由不久&#xff0c;那么您可能对于web.php路由文件的post请求很疑惑&#xff0c;因为get请求很顺利&#xff0c;而post请求则可能会遭遇失败。其中一个失败的原因是由于laravel的c…

jupyter notebook的 markdown相关技巧

目录 1 先选择为markdown类型 2 开关技巧 2.1 运行markdown 2.2 退出markdown显示效果 2.3 注意点&#xff1a;一定要 先选择为markdown类型 3 一些设置技巧 3.1 数学公式 3.2 制表 3.3 目录和列表 3.4 设置各种字体效果&#xff1a;加粗&#xff0c;斜体&#x…

【GAT】 代码详解 (1) 运行方法【pytorch】可运行版本

GRAPH ATTENTION NETWORKS 代码详解 前言0.引言1. 环境配置2. 代码的运行2.1 报错处理2.2 运行结果展示 3.总结 前言 在前文中&#xff0c;我们已经深入探讨了图卷积神经网络和图注意力网络的理论基础。还没看的同学点这里补习下。接下来&#xff0c;将开启一个新的阶段&#…

redis工程实战介绍(含面试题)

文章目录 redis单线程VS多线程面试题**redis是多线程还是单线程,为什么是单线程****聊聊redis的多线程特性和IO多路复用****io多路复用模型****redis如此快的原因** BigKey大批量插入数据测试数据key面试题海量数据里查询某一固定前缀的key如果生产上限值keys * &#xff0c;fl…

神经网络问题之二:梯度爆炸(Gradient Explosion)

梯度爆炸&#xff08;Gradient Explosion&#xff09;是神经网络训练过程中常见的一个问题&#xff0c;它指的是在反向传播过程中&#xff0c;梯度值变得非常大&#xff0c;超出了网络的处理范围&#xff0c;从而导致权重更新变得不稳定甚至不收敛的现象。 一、产生原因 梯度爆…

小杨的N字矩阵c++

题目描述 小杨想要构造一个m*m 的 N 字矩阵&#xff08; m为奇数&#xff09;&#xff0c;这个矩阵的从左上角到右下角的对角线、第1 列和第m 列都 是半角加号 &#xff0c;其余都是半角减号 - 。例如&#xff0c;一个 5*5 的 N 字矩阵如下&#xff1a; --- -- -- -- --- 请…

2024 年企业中的生成式 AI 现状

2024: The State of Generative AI in the Enterprise - Menlo Ventures 企业 AI 格局正在被实时改写。随着试点&#xff08;Pilot&#xff09;让位于生产&#xff08;Production&#xff09;&#xff0c;我们对 600 名美国企业 IT 决策者进行了调查&#xff0c;以揭示新出现的…

Ubuntu24虚拟机-gnome-boxes

推荐使用gnome-boxes&#xff0c; virtualbox构建失败&#xff0c;multipass需要开启防火墙 sudo apt install gnome-boxes创建完毕&#xff5e;

Haystack 的开源开发 LLM 应用设计框架

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

leetcode 919.完全二叉树插入器

1.题目要求: 完全二叉树 是每一层&#xff08;除最后一层外&#xff09;都是完全填充&#xff08;即&#xff0c;节点数达到最大&#xff09;的&#xff0c;并且所有的节点都尽可能地集中在左侧。设计一种算法&#xff0c;将一个新节点插入到一棵完全二叉树中&#xff0c;并在…

提升性能测试效率与准确性:深入解析JMeter中的各类定时器

在软件性能测试领域&#xff0c;Apache JMeter是一款广泛使用的开源工具&#xff0c;它允许开发者模拟大量用户对应用程序进行并发访问&#xff0c;从而评估系统的性能和稳定性。在进行性能测试时&#xff0c;合理地设置请求之间的延迟时间对于模拟真实用户行为、避免服务器过载…

Python + 深度学习从 0 到 1(00 / 99)

希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【深度学习从 0 到 1】谢谢你的支持&#xff01; ⭐ 什么是深度学习&#xff1f; 人工智能、机器学习与…

亚信安全发布《2024年第三季度网络安全威胁报告》

《亚信安全2024年第三季度网络安全威胁报告》的发布旨在从一个全面的视角解析当前的网络安全威胁环境。此报告通过详尽梳理和总结2024年第三季度的网络攻击威胁&#xff0c;目的是提供一个准确和直观的终端威胁感知。帮助用户更好地识别网络安全风险&#xff0c;并采取有效的防…

ROS机器视觉入门:从基础到人脸识别与目标检测

前言 从本文开始&#xff0c;我们将开始学习ROS机器视觉处理&#xff0c;刚开始先学习一部分外围的知识&#xff0c;为后续的人脸识别、目标跟踪和YOLOV5目标检测做准备工作。我采用的笔记本是联想拯救者游戏本&#xff0c;系统采用Ubuntu20.04&#xff0c;ROS采用noetic。 颜…

利用c语言详细介绍下选择排序

选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。它是每次选出最小或者最大的元素放在开头或者结尾位置&#xff08;采用升序的方式&#xff09;&#xff0c;最终完成列表排序的算法。 一、图文介绍 我们还是使用数组【10&#xff0c;5&#xff0c;3…

candence: 非金属化孔制作

非金属化孔制作 以下面这个RJ45接口为例 1、打开pad designer 只需要设置开始、结束层即可。 保存 来直观看下非金属化孔和金属化孔的区别&#xff1a;

用宏实现简单的计算器

大家好&#xff0c;那么经过我们前面几期的学习&#xff0c;我们对宏有了一定的了解&#xff0c;那么我们今天就来试试实现一个简单的加减乘除运算。 我们的思路是使用三目操作符来分别进行加减和乘除的运算&#xff0c;然后用if判断来”进入相关的判断体进而来进行计算。当然…

Postman之newman

系列文章目录 1.Postman之安装及汉化基本使用介绍 2.Postman之变量操作 3.Postman之数据提取 4.Postman之pm.test断言操作 5.Postman之newman Postman之newman 1.基础环境node安装1.1.配置环境变量1.2.安装newman和html报告组件 2.newman运行 newman可以理解为&#xff0c;没有…

用python简单集成一个分词工具

本部分记录如何利用Python进行分词工具集成&#xff0c;集成工具可以实现运行无环境要求&#xff0c;同时也更方便。 该文章主要是记录&#xff0c;知识点不是特别多&#xff0c;欢迎访问个人博客&#xff1a;https://blog.jiumoz.top/archives/fen-ci-gong-ju-ji-cheng 成品展…