Spring IOC(二)

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名称。

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

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

相关文章

C语言程序设计(一)

1、指令、程序、软件 2、计算机语言&#xff1a;机器语言、汇编语言、高级语言 高级语言的发展&#xff1a;非结构化语言&#xff08;FORTRAN&#xff09;、结构化语言&#xff08;C语言&#xff09;、面向对象的语言&#xff08;C、面向对象&#xff09; 3、源程序、二进制…

数据可视化在不同行业中有哪些应用?

数据可视化即通过图表的形式将数据的内在信息有逻辑性地呈现给用户&#xff0c;使用户更容易发现数据中蕴藏的规律&#xff0c;找出问题&#xff0c;进而做出决策&#xff1b;另一方面&#xff0c;数据可视化项目也是一张重要的名片&#xff0c;是企业数字化建设效果的呈现。本…

FPGA实现图像处理之【直方图均衡-寄存器版】

FPGA实现直方图统计 一、图像直方图统计原理 直方图的全称为灰度直方图&#xff0c;是对图像每一灰度间隔内像素个数的统计。即对一张图片中每隔二灰度值的像素数量做统计&#xff0c;然后以直方图的形式展现出来。图下的亮暗分布在直方图中就可以一目了然&#xff0c;直方图…

【数据结构与算法】力扣 225. 用队列实现栈

题目描述 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元…

AI项目二十:基于YOLOv8实例分割的DeepSORT多目标跟踪

若该文为原创文章&#xff0c;转载请注明原文出处。 前面提及目标跟踪使用的方法有很多&#xff0c;更多的是Deepsort方法。 本篇博客记录YOLOv8的实例分割deepsort视觉跟踪算法。结合YOLOv8的目标检测分割和deepsort的特征跟踪&#xff0c;该算法在复杂环境下确保了目标的准…

R语言的基本图形

一&#xff0c;条形图 安装包 install.packages("vcd") 绘制简单的条形图 barplot(c(1,2,4,5,6,3)) 水平条形图 barplot(c(1,2,4,5,6,3),horiz TRUE) 堆砌条形图 > d1<-c("Placebo","Treated") > d2<-c("None",&qu…

linux运行python怎么结束

假如你已经进入到【>>>】&#xff0c;那么输入【quit&#xff08;&#xff09;】&#xff0c;然后按一下回车键即可退出了。 如果是想要关闭窗口的&#xff0c;那么直接在这个窗口上按【ctrld】。

vue2集成ElementUI编写登录页面

目录 1. 整理目录文件&#xff1a; a. app.vue文件如下&#xff1a; b. Login.vue文件如下&#xff1a; c. router/index.js文件如下&#xff1a; d. 删除components中的文件&#xff1a; e. 最终项目目录整理如下&#xff1a; 2. 集成ElementUI编写登录页面 a. 安装El…

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

文章目录 基础代码1. watch2. computed&#xff08;推荐&#xff09; 官方给的例子是关于el-input的&#xff0c;如下。但是input不是所有组件标签都有的属性啊&#xff0c;有没有一种通用的办法呢&#xff1f; <script setup> defineProps({firstName: String,lastName…

Docker容器:搭建LNMP架构

目录 前言 1、任务要求 2、Nginx 镜像创建 2.1 建立工作目录并上传相关安装包 2.2 编写 Nginx Dockerfile 脚本 2.3 准备 nginx.conf 配置文件 2.4 生成镜像 2.5 创建 Nginx 镜像的容器 2.6 验证nginx 3、Mysql 镜像创建 3.1 建立工作目录并上传相关安装包 3.2 编写…

FANUC机器人SOCKET断开KAREL程序编写

一、添加一个.KL文件创建编辑断开指令 添加一个KL文件用来创建karel程序中socket断开指令 二、断开连接程序karel代码 PROGRAM SOC_DIS %COMMENT SOCKET断开 %INCLUDE klevccdf VAR str_input,str_val : STRING[20] status,data_type,int_val : INTEGER rel_val : REALBEGING…

【Linux】文件打包解压_tar_zip

文章目录 &#x1f4d1;引言&#xff1a;一、文件打包压缩1.1 什么是文件打包压缩&#xff1f;1.2 为什么需要文件打包压缩&#xff1f; 二、打包解压2.1 zip2.2 unzip2.3 tar指令 &#x1f324;️全篇小结&#xff1a; &#x1f4d1;引言&#xff1a; 在Linux操作系统中&#…

简单易懂的下载学浪视频教程- 小浪助手

接下来我就教大家如何通过小浪助手&#xff0c;轻松下载你想要下载的学浪app视频 首先准备好小浪助手 工具我已经打包好了&#xff0c;有需要的自己取一下 学浪下载器链接&#xff1a;https://pan.baidu.com/s/1djUmmnsfLEt_oD2V7loO-g?pwd1234 提取码&#xff1a;1234 -…

LLaMA3(Meta)微调SFT实战Meta-Llama-3-8B-Instruct

LlaMA3-SFT LlaMA3-SFT, Meta-Llama-3-8B/Meta-Llama-3-8B-Instruct微调(transformers)/LORA(peft)/推理 项目地址 https://github.com/yongzhuo/LLaMA3-SFT默认数据类型为bfloat6 备注 1. 非常重要: weights要用bfloat16/fp32/tf32(第二版大模型基本共识), 不要用fp16, f…

Llama 3 基于知识库应用实践(一)

一、概述 Llama 3 是Meta最新推出的开源大语言模型&#xff0c;其8B和13B参数的模型的性能与之前的Llama 2相比实现了质的飞跃。以下是官方给出的模型性能评测对比结果&#xff08;引自&#xff1a;https://ai.meta.com/blog/meta-llama-3/&#xff09;&#xff0c;如Llama 3 …

后端学习记录~~JavaSE篇(Module08-异常 上 )

总览&#xff1a; Java概述&#xff1a; 思维导图文件在本人个人主页上-----资源模块 资源详情&#xff08;免费下载&#xff09;&#xff1a;Java学习思维导图异常篇资源-CSDN文库https://download.csdn.net/download/m0_61589682/89238330 整体展示&#xff1a;

文件上传安全以及防止无限制文件上传

文件上传安全以及防止无限制文件上传 在网络应用中&#xff0c;文件上传是一项常见功能&#xff0c;用户可以通过它上传图片、文档或其他媒体文件。然而&#xff0c;如果没有适当的安全措施&#xff0c;文件上传功能可能成为安全漏洞的源头。本文将探讨文件上传过程中的安全风…

小米汽车充电枪继电器信号

继电器型号&#xff1a; 参考链接 小米SU7&#xff0c;便捷充放电枪拆解 (qq.com)https://mp.weixin.qq.com/s?__bizMzU5ODA2NDg4OQ&mid2247486086&idx1&sn0dd4e7c9f7c72d10ea1c9f506faabfcc&chksmfe48a110c93f2806f6e000f6dc6b67569f6e504220bec14654ccce7d…

秋招后端开发面试题 - JVM底层原理

目录 JVM底层原理前言面试题Java 对象的创建过程&#xff1f;什么是指针碰撞&#xff1f;什么是空闲列表&#xff1f;/ 内存分配的两种方式&#xff1f;JVM 里 new 对象时&#xff0c;堆会发生抢占吗&#xff1f;JVM 是怎么设计来保证线程安全的&#xff1f;/ 内存分配并发问题…

语音识别的基本概念

语音识别的基本概念​​​​​​​ ​​​​​​​ 言语是一种复杂的现象。人们很少了解它是如何产生和感知的。天真的想法常常是语音是由单词构成的&#xff0c;而每个单词又由音素组成。不幸的是&#xff0c;现实却大不相同。语音是一个动态过程&#xff0c;没有明确区分的…