Spring IOC(二)

news2024/11/26 23:44:47

1. Bean的定义与获取

1.1 定义Bean

        在Spring 中定义Bean的方式主要有三种:

        1、基于XML配置文件的方式(了解):通常会在配置文件中使用<bean>标签来定义Bean,并设置Bean的属性、依赖关系等信息。

        2、基于注解的方式:在Java代码中使用注解来标识Bean,并指定Bean的属性、依赖关系等信息。常用的注解有@Component、@Controller、@Service等。

  • @Component用于标识一个普通的 Bean 类,没有任何特殊的业务含义
  • @Controller 用于标识一个控制器类、@Service 用于标识一个服务类,通常用于业务处理层、@Repository 注解用于标识一个数据访问类
  • 除了这些区别,它们的使用方式是一样的,本质上都是将被注解的类交由 Spring 容器管理,供其他类去使用

        3、基于Java Config的方式,在Java配置类中使用@Bean注解来定义Bean。当开发者想要使用的Bean是第三方组件时,不能在源码上标注@Component,可以使用@Bean注解的方式。

  • @Bean注解通常放在方法上,用于指示该方法返回的实例应该被注册为Spring容器中的一个Bean
  • 在方法上使用@Bean注解时,Spring框架会根据该方法的返回类型来自动创建并配置该Bean,同时也会根据方法的名称来为该Bean指定一个默认的名称
  • 可以通过为@Bean添加属性值的方式为Bean定义一个新的名称

        总的来说,不同的方式适用于不同的场景。在实际使用过程中,我们可以根据业务需求来选择最适合的方式来定义Bean,也可以兼用多种方式来定义Bean,这些方式也是可以共存的。        

1.2 定义Bean示例

        首先,在cn.obj包下声明BeanDefineConfig类,并在其中使用@Bean注解方式声明Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;

@Configuration
@ComponentScan("cn.highedu")
public class BeanDefineConfig {

    /**
     * 使用@Bean 显示声明Java Bean 组件
     * Bean ID 为 names
     * @return 创建的JavaBean
     */
    @Bean
    public ArrayList<String> names(){
        ArrayList<String> names = new ArrayList<>();
        names.add("Tom");
        names.add("Jerry");
        return names;
    }

    /**
     * 使用 @Bean的属性设置BeanID
     * @return 编程语言列表
     */
    @Bean("languages")
    public ArrayList<String> list(){
        ArrayList<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Java Script");
        return list;
    }

    /**
     * 使用@Bean 还可以在方法参数中引入其他的Bean
     * 本例中引入了名为languages的Bean
     * @param languages
     * @return
     */
    @Bean
    public ArrayList<String> moreLanguages(ArrayList<String> languages){
        ArrayList moreLanguages = new ArrayList(languages);
        moreLanguages.add("Python");
        moreLanguages.add("SQL");
        return moreLanguages;
    }
}

        然后,在Application类中测试Bean的创建效果:

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(BeanDefineConfig.class);
        // 通过Spring IOC容器对象获取Bean的对象
        ArrayList<String> names = (ArrayList<String>) context.getBean("names");
        names.forEach(System.out::println);
        System.out.println();
        // 通过Spring IOC容器对象获取Bean的对象
        ArrayList<String> moreCities = (ArrayList<String>) context.getBean("moreCities");
        moreCities.forEach(System.out::println);
    }
}

1.3 Spring框架中@Bean 和 @Component 的区别

        在Spring框架中,@Bean注解和@Component注解均用于定义Bean,但是添加的位置和应用的场景不同。

        1、@Bean注解主要用于在配置类中显式配置Bean。

  • 可以添加在方法和注解前
  • 将声明集中在一个(或几个)地方,方便统一管理Bean
  • 可用于所有类(包括第三方工具包中的类)

        2、@Component注解主要用于在类前隐式的配置Bean。

  • 主要添加在类前
  • 位置分散,不便于统一管理
  • 适合非常快速的开发

        3、实际应用中,通常是两种方式混合使用。

1.4 Spring IoC获取对象的方式

        在Spring IoC容器中获取Bean可以通过以下几种方式:

        1、context.getBean(Class<T> requiredType):根据类型获取Bean。其中:

  • requiredType为要获取bean的类型
  • 如果容器中有多个实现该类型的Bean,但是没有配置优先使用哪个Bean,则会抛出NoUniqueBeanDefinitionException异常

        2、context.getBean(String name):根据名称获取Bean。其中:

  • name为Bean在Spring IoC容器中的名称
  • 默认情况下,Bean的名称是类名进行了首字母小写的处理后的结果
  • 可以通过在Spring注解中设置名称来设置Bean的名称,如:@Component("myBean")

        3、context.getBean(String name, Class<T> requiredType):根据Bean名称和类型获取Bean。

  • 该方法相当于在getBean(String name)方法的基础上添加了Bean类型的验证
  • 如果获取的Bean对象与requiredType指定的类型不同,将抛出BeanNotOfRequiredTypeException

        4、context.getBeansOfType(Class<T> type):根据类型获取容器中所有实现该类型的Bean。

  • type为要获取bean的类型
  • 该方法返回Map类型,其中key为Bean在容器中的名称,value为Bean实例

1.5 IoC获取对象示例

        在Application类中,测试IoC获取对象的方式:

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(BeanDefineConfig.class);
        // IoC容器中存在多个相同类型的Bean时,
        // 使用.class获取会抛出异常:NoUniqueBeanDefinitionException
        // ArrayList<String> names = (ArrayList<String>) context.getBean(ArrayList.class);
        // 使用name+type的方式,不需要再进行强制类型转换
        ArrayList<String> moreLanguages = context.getBean("moreLanguages", ArrayList.class);
        moreLanguages.forEach(System.out::println);
        // getBeansOfType方法返回的类型为Map
        context.getBeansOfType(ArrayList.class).forEach((name, bean) -> {
            System.out.println(name + " : " + bean);
        });
    }
}

2. Bean的作用域

2.1 Bean的作用域概述

        Bean的作用域就是指Spring中Java Bean 有效范围,其中最为常用的作用域有2种:

  • 单例作用域(singleton):Bean的默认作用域,任何时候获得的Bean对象都是同一个实例
  • 原型作用域(prototype):每次引到bean时都会创建新的实例

        可以通过@Scope注解来设置Bean的作用域,该注解可以添加在类前(与@Component注解搭配使用),也可以添加在方法前(与@Bean注解搭配使用)。

        Bean的其他类型的作用域如下表所示(了解即可):

2.2 Bean的作用域示例

        在com.obj包下新建SingletonBean:

import org.springframework.stereotype.Component;
@Component
public class SingletonBean {
    @Override
    public String toString() {
        return "SingletonBean";
    }
}

        在com.obj.spring包下新建PrototypeBean:

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class PrototypeBean {
    @Override
    public String toString() {
        return "PrototypeBean";
    }
}

        在com.obj包下新建ScopeTest类,对Bean的作用域进行测试:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ScopeTest {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(ContextConfig.class);
        // 测试默认的Scope
        SingletonBean bean1 = context.getBean(SingletonBean.class);
        SingletonBean bean2 = context.getBean(SingletonBean.class);
        System.out.println("bean1 和 bean2 是否是同一个对象:" + (bean1 == bean2));
        // 测试prototype
        PrototypeBean bean3 = context.getBean(PrototypeBean.class);
        PrototypeBean bean4 = context.getBean(PrototypeBean.class);
        System.out.println("bean3 和 bean4 是否是同一个对象:" + (bean3 == bean4));
    }
}

3. 依赖注入进阶

3.1 Spring支持的依赖注入方式

        Spring框架主要支持三种注入方式:字段注入、构造器注入、setter方法注入。

        1、字段注入(Field Injection):通过直接在类的字段上添加注解来注入依赖。

  • 字段注入在实际使用中比较简便,但也可能降低代码的可测试性和清晰度,因为字段通常是私有的。

        2、构造器注入(Constructor Injection):通过类的构造方法来注入依赖。

  • 在创建对象实例时,容器会通过构造器参数来解析和注入依赖项。
  • 构造器注入强制要求依赖在对象创建时就需要提供,适用于必须的依赖关系。

        3、setter方法注入(Setter Injection):通过类的setter方法来注入依赖。

  • 容器会调用目标类的setter方法,将依赖项注入到目标类的属性中。
  • setter方法注入适用于可选性依赖或需要在对象创建后才能设置的情况。

3.2 构造注入示例

        修改Student类的代码,增加ArrayList类型的属性、带参构造器及toString方法:

@Component
public class Student {
    @Autowired
    private Computer computer;
    private ArrayList<String> languages;
    public Student(){
        System.out.println("Student无参构造方法被调用");
    }

    @Autowired
    public Student(ArrayList<String> moreLanguages){
        System.out.println("Student带参构造方法被调用");
        this.languages = moreLanguages;
    }

    @Override
    public String toString() {
        return "Student{computer=" + computer + ", languages=" + languages + '}';
    }
    public void use(){
        System.out.println("使用电脑:"+computer);
    }
}

        在Application类中获取Student类的对象并输出。注意,本例中的配置类需要使用BeanDefineConfig。

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(BeanDefineConfig.class);
        Student student = context.getBean(Student.class);
        System.out.println(student);
    }
}

        控制台输出如下:

        从输出结果可知,Spring框架调用了Student类的带参构造器来创建Student对象,并且自动从容器中查询到名为moreLanguages的Bean对象,注入到该构造器中。

        接下来做一个实验,将Student类中带参构造器的参数名改为test。

        再次运行Application类的main方法,程序抛出NoUniqueBeanDefinitionException异常。该异常的原因是Spring无法通过test这个名称从容器中找到Bean对象,所以改用ArrayList类型进行查找,找到了不止一个Bean对象,所以抛出异常。

        此处可以看到,采用构造器注入的方式,如果构造器中要求注入的Bean存在问题,则会导致Student对象构造出错。因此,构造器注入适用于必须的依赖关系。

3.3 setter方法注入示例

        修改Student类的代码,增加setLanguages方法,并删除带参构造器前的@Autowired注解:

   public Student(ArrayList<String> moreLanguages){
        System.out.println("Student带参构造方法被调用");
        this.languages = moreLanguages;
    }

    @Autowired
    public void setLanguages(ArrayList<String> moreLanguages){
        System.out.println("setLanguages方法被调用");
        this.languages = moreLanguages;
    }

        运行Application类的main方法,查看控制台输出的内容。

        可以看到,移除带参构造方法前的@Autowired注解后,Spring默认调用无参构造方法来创建Student对象,然后调用set方法注入依赖。因此,set方法的注入时机晚于构造方法。

        在Student类中新增一个String类型的属性,添加对应的set和get方法,并在set方法前添加@Autowired(required=false)注解。

@Component
public class Student {
    // 通过Spring框架获取Computer对象
    @Autowired
    private Computer computer;
    private ArrayList<String> languages;
    private String name;

    @Autowired(required = false)
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
// 省略该类中其他内容

        在Application类中增加对getName方法的调用。

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(BeanDefineConfig.class);
        Student student = context.getBean(Student.class);
        System.out.println(student);
        System.out.println(student.getName());
// 省略该类中其他内容

        执行程序的main方法,可以看到程序正常执行,getName方法返回的结果为null。此处主要演示的是@Autowired(required = false)的执行逻辑,即如果能找到相关符合条件的Bean对象,则执行注入,反之则不执行注入。这种注入方式适用于那些可选的依赖。

3.4 为什么Spring不建议使用字段注入

        1、Spring框架支持三种主要的注入方式:字段注入、构造器注入、setter方法注入。其中,字段注入是指在类的字段上添加注解来注入依赖。

        2、可能导致空指针异常:一个Bean的初始顺序为静态变量或静态代码块 > 实例变量或初始化语句块 > 构造方法 > 字段注入。在静态代码块、初始化语句块、构造方法中使用@Autowired标记的字段,会引起空指针异常。

        3、不利于测试: 在单元测试中,为了隔离被测试类与外部依赖之间的耦合,常常需要模拟依赖对象。字段注入会使得测试类无法直接通过构造器或方法注入模拟对象,从而增加了测试的复杂性。

        4、无法注入 final 字段: 字段注入无法用于注入 final 字段,这会限制一些设计和测试的可能性。

3.5 @Autowired的Bean匹配机制

        @Autowired的Bean匹配机制是指在执行依赖注入之前,Spring容器会根据被注入字段、方法参数的类型来查找匹配的 Bean。

        该机制的执行顺序为:

        1、先根据类型匹配

  • 若没有匹配类型,则注入失败
  • 有匹配类型,对应的实例有1个,则注入

        2、如果匹配类型的实例有多个,则查看优先级注解:

  • 查看是否有@Qualifier注解,有则按其指定规则匹配
  • 查看是否有@Primary注解,有则按其指定规则匹配

        3、如果类型匹配的实例有多个,且无法选出唯一的,则转为根据Bean的名称匹配

  • 如果名称匹配成功就注入,反之注入失败

        在entity包下声明DemoBean类。

@Component
public class DemoBean {
    @Autowired
    private ArrayList<String> list;

    @Override
    public String toString() {
        return "DemoBean{" +
                "list=" + list +
                '}';
    }
}

        在Application类中获取DemoBean的对象并输出。

public class Application {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(BeanDefineConfig.class);
        DemoBean demoBean = context.getBean(DemoBean.class);
        System.out.println(demoBean);
    }
}

        此时,程序抛出NoUniqueBeanDefinitionException,表示有多个匹配的实例,但是无法确定该使用哪一个。

        接下来,将list属性的名称修改为moreLanguages。

@Autowired
private ArrayList<String> moreLanguages;

@Override
public String toString() {
    return "DemoBean{" + "list=" + moreLanguages + "}";
}

        运行Application类,可以发现,依赖注入成功,根据属性名匹配了名为moreLanguages的Bean。

        然后,在BeanDefineConfig类的names方法前添加@Primary注解。

    @Bean
    @Primary
    public ArrayList<String> names(){
        ArrayList<String> names = new ArrayList<>();
        names.add("Tom");
        names.add("Jerry");
        return names;
    }

        运行Application类,可以看到,依赖注入成功。从结果可以看出,@Primary注解的优先级高于按属性名匹配。

        接下来,在DemoBean类的list属性前添加@Qualifier("languages")注解,再次运行测试用例,查看效果。

        此处可以发现,@Qualifier("languages")注解的优先级高于@Primary注解。

        综上,如果存在多个匹配的Bean,可以通过三种方式解决:@Qualifier注解,@Primary注解或使用Bean名称匹配。这三种方式的优先级为:@Qualifier > @Primary > Bean名称。

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

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

相关文章

基于Spring Boot的商务安全邮件收发系统设计与实现

基于Spring Boot的商务安全邮件收发系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 已发送效果图&#xff0c;用户可以对已发送信息…

javaweb学习week6

javaweb学习 九.登录认证 5.登录后下发令牌 生成令牌&#xff1a;引入JWT令牌操作工具类&#xff0c;登录完成后&#xff0c;调用工具类生成JWT令牌&#xff0c;并返回 代码实例&#xff1a; 6.Filter入门 概念&#xff1a;Filter过滤器&#xff0c;是Javaweb三大组件之一…

构建本地大语言模型知识库问答系统

MaxKB 2024 年 4 月 12 日&#xff0c;1Panel 开源项目组正式对外介绍了其官方出品的开源子项目 ——MaxKB&#xff08;github.com/1Panel-dev/MaxKB&#xff09;。MaxKB 是一款基于 LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统。MaxKB 的产品…

【生活日常】听歌识曲失败后,寻找一首曲子的心路历程……

Have a nice day :) 0x00 事情经过 叶子现在心情很是激动&#xff01;是这样的…… 昨天下午呢&#xff0c;去了家不错的咖啡厅&#xff0c;点了杯冰美式&#xff0c;很不错&#xff01; 而且店里的 Marshall 音箱也很带劲&#xff01; 然后我听到了种类似 "合成器节奏…

SpringMVC进阶(过滤器解决中文乱码,处理json以及文件上传下载)

文章目录 1.中文乱码处理1.引出问题1.恢复原来取消掉的属性绑定2.启动服务器&#xff0c;引出问题 2.自定义中文乱码过滤器1.MyCharacterFilter.java2.web.xml配置过滤器&#xff08;这个解决乱码的过滤器放到最前面&#xff09;3.结果展示 3.使用Spring过滤器处理&#xff08;…

物联网通信网关的主要功能体现在哪些方面?-天拓四方

在信息化、智能化的时代&#xff0c;物联网技术的广泛应用正在逐渐改变我们的生活方式。物联网通过各种传感器和设备&#xff0c;将现实世界与数字世界紧密相连&#xff0c;从而实现智能化、自动化的生活和工作方式。作为物联网生态系统中的重要组成部分&#xff0c;物联网通信…

解决Pytorch的cuDNN error: CUDNN_STATUS_NOT_INITIALIZED

目录 1. 问题报错2. 可能原因2.1 GPU内存不足2.2 缓存问题2.3 CUDA和Pytorch版本不兼容2.4 CUDA和cuDNN版本不兼容 3. 验证CUDA是否可用4. 参考 1. 问题报错 在使用GPU加速模型训练的过程中经常会遇到这样的错误&#xff1a; RuntimeError: cuDNN error: CUDNN_STATUS_NOT_IN…

8_手眼标定总结_auboi5机械臂与海康平面相机

经过不断地学习与调试&#xff0c;不断地学习网络上其他同志分享的资料&#xff0c;opencv手眼标定迎来了阶段性结束。实际测试结果在机械臂坐标系中X方向差5mm左右。 代码参考《https://blog.csdn.net/wanggao_1990/article/details/81435660》 注意事项&#xff1a; ①标定…

开源相机管理库Aravis例程学习(五)——camera-api

开源相机管理库Aravis例程学习&#xff08;五&#xff09;——camera-api 简介例程代码函数说明arv_camera_get_regionarv_camera_get_pixel_format_as_stringarv_camera_get_pixel_formatARV_PIXEL_FORMAT_BIT_PER_PIXEL 简介 本文针对官方例程中的&#xff1a;03-camera-api…

Kafka(十二)Streams

目录 Streams1 什么式是流式处理2 流式处理的相关概念2.1 拓扑2.2 时间2.2.1 输入时间2.2.2 输出时间 2.3 状态2.4 流和表2.5 时间窗口2.5.1 测试时间窗口 2.6 处理保证 3 流式处理设计模式3.1 单事件处理3.2 使用本地状态3.3 多阶段处理和重分区3.4 使用外部查找&#xff1a;流…

HTML5(1)

目录 一.HTML5(超文本&#xff08;链接&#xff09;标记&#xff08;标签<>&#xff09;语言) 1.开发环境&#xff08;写代码&#xff0c;看效果&#xff09; 2.vscode 使用 3.谷歌浏览器使用 4.标签语法 5.HTML基本骨架&#xff08;网页模板&#xff09; 6.标签的…

【多维动态规划】Leetcode 64. 最小路径和【中等】

最小路径和 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;grid [[1,3,1],[1,5,1],[4,2,1]] 输出…

Stable Diffusion 模型分享:Inkpunk Diffusion(动漫、墨水朋克)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 在 Dreambooth 上训练的微调稳定扩散模型。隐约受…

现代神经网络总结(AlexNet VGG GoogleNet ResNet的区别与改进)

VGG NIN GoogleNet 1.VGG&#xff0c;NIN&#xff0c;GoogleNet的块结构图对比(注意:无AlexNet) 这些块带来的区别与细节 AlexNet未使用块,主要对各个层进行了解: 卷积:捕捉特征 relu:增强非线性 池化层:减少计算量 norm:规范数据分布 全连接层:分类VGG块的改善(对比AlexNe…

将要上市的自动驾驶新书《自动驾驶系统开发》中摘录片段

全书共分15章&#xff1a;第1章是自动驾驶系统的概述&#xff08;场景分类、开发路径和数据闭环等&#xff09;&#xff0c;第2章简介自动驾驶的基础理论&#xff0c;即计算机视觉和深度学习等&#xff0c;第3&#xff5e;4章是自动驾驶的软硬件平台分析&#xff0c;包括传感器…

使用STM32CubeMX对STM32F4的CAN1/2/3配置及接收中断开启

目录 1. CAN配置1.1引脚&#xff08;STM32F413VGT6-LQFP100&#xff09;1.2 时钟1.3 RCC配置1.4 CAN1配置1.5 CAN2配置1.6 CAN3配置1.7 输出设置 2. CAN代码2.1 CAN初始化2.2 CAN滤波器设置2.3 CAN使能2.4 激活中断2.5 CAN发送函数2.6 CAN回调函数2.7 main之后的代码 1. CAN配置…

20232831 袁思承2023-2024-2 《网络攻防实践》第8次作业

目录 20232831 袁思承2023-2024-2 《网络攻防实践》第8次作业1.实验内容2.实验过程一、动手实践任务一二、动手实践任务二&#xff1a;分析Crackme程序①crackme1.exe②crackme2.exe 三、分析实践任务一四、分析实践任务二 3.学习中遇到的问题及解决4.学习感悟、思考等参考资料…

你不需要总是在 React 中使用 useState

在我审查的一个拉取请求中&#xff0c;我注意到在许多拉取请求中看到的一种模式。React 组件具有多个 UI 状态&#xff0c;例如 loading、error 和 success。 作者使用了多个 useState 钩子来管理这些状态&#xff0c;这导致代码难以阅读且容易出错&#xff0c;例如&#xff1a…

ArcGIS Pro3.0软件破解版安装教程

软件名称&#xff1a;ArcGIS Pro 3.0 安装环境&#xff1a;Windows 软件大小&#xff1a;7.3GB 硬件要求&#xff1a;CPU2GHz&#xff0c;内存4G(或更高) 百度云下载链接 &#xff1a; https://pan.baidu.com/s/1CXy1MSwdQXdVnJoV2X422A 提 取 码 &#xff1a;r0w1 教学内…

uniapp 对接facebook第三方登录

1.登录facebook开发者中心&#xff0c;打开我的应用页面在这里插入图片描述 2.创建应用 3.选择类型 4.填写信息 5.添加登录 6.添加平台 安卓密钥生成【需要 Java 环境!!! 和 openssl库】 Google Code Archive 的 Windows 版 openssl-for-windows OpenSSL 库 将openssl下载到…