Spring IOC(二)

embedded/2024/10/18 8:19:20/

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:

java">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*/@Beanpublic 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*/@Beanpublic ArrayList<String> moreLanguages(ArrayList<String> languages){ArrayList moreLanguages = new ArrayList(languages);moreLanguages.add("Python");moreLanguages.add("SQL");return moreLanguages;}
}

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

java">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获取对象的方式:

java">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方法返回的类型为Mapcontext.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:

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

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

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

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

java">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);// 测试默认的ScopeSingletonBean bean1 = context.getBean(SingletonBean.class);SingletonBean bean2 = context.getBean(SingletonBean.class);System.out.println("bean1 和 bean2 是否是同一个对象:" + (bean1 == bean2));// 测试prototypePrototypeBean 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方法:

java">@Component
public class Student {@Autowiredprivate Computer computer;private ArrayList<String> languages;public Student(){System.out.println("Student无参构造方法被调用");}@Autowiredpublic Student(ArrayList<String> moreLanguages){System.out.println("Student带参构造方法被调用");this.languages = moreLanguages;}@Overridepublic String toString() {return "Student{computer=" + computer + ", languages=" + languages + '}';}public void use(){System.out.println("使用电脑:"+computer);}
}

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

java">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注解:

java">   public Student(ArrayList<String> moreLanguages){System.out.println("Student带参构造方法被调用");this.languages = moreLanguages;}@Autowiredpublic 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)注解。

java">@Component
public class Student {// 通过Spring框架获取Computer对象@Autowiredprivate 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方法的调用。

java">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类。

java">@Component
public class DemoBean {@Autowiredprivate ArrayList<String> list;@Overridepublic String toString() {return "DemoBean{" +"list=" + list +'}';}
}

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

java">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。

java">@Autowired
private ArrayList<String> moreLanguages;@Override
public String toString() {return "DemoBean{" + "list=" + moreLanguages + "}";
}

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

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

java">    @Bean@Primarypublic 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.ppmy.cn/embedded/28365.html

相关文章

操作系统(2)——进程线程

目录 小程一言专栏链接: [link](http://t.csdnimg.cn/8MJA9)基础概念线程详解进程详解进程间通信调度常用调度算法 重要问题哲学家进餐问题问题的描述策略 读者-写者问题问题的描述两种情况策略 总结进程线程一句话 小程一言 本操作系统专栏&#xff0c;是小程在学操作系统的过…

jenkins搭建

安装jdk yum install -y java-1.8.0-openjdk.x86_64 默认安装到usr/lib/jvm目录下 查看JDK信息,输入命令:java -version 检测JDK安装包,输入命令:rpm -qa | grep java 进入安装目录。 输入命令:cd /usr/lib/jvm 删除Java相关文件,输入命令:rm -rf /usr/lib/jvm 配置…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习三

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

使用Python及R语言绘制简易数据分析报告

Pytohn实现 在python中有很多包可以实现绘制数据分析报告的功能&#xff0c;推荐两个较为方便的包&#xff1a;pandas-profiling 和 sweetviz 。 使用 pandas-profiling 包&#xff08;功能全面&#xff09; 这个包的个别依赖包与机器学习的 sklearn 包的依赖包存在版本冲突&a…

制作一个RISC-V的操作系统十六-系统调用

文章目录 用户态和内核态mstatus设置模式切换核心流程封装代码背景解释代码示例解析解释目的 用户态和内核态 mstatus设置 此时UIE设置为1和MPIE为1&#xff0c;MPP设置为0 代表当前权限允许UIE中断发生&#xff0c;并且在第一个mret后将权限恢复为用户态&#xff0c;同时MIE也…

力扣:100. 相同的树(Java)

目录 题目描述输入&#xff1a;输出&#xff1a;代码实现&#xff1a; 题目描述 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 输入&#xff1a…

[1673]jsp在线考试管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 在线考试管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

【IDEA】设置Maven路径 当前项目 全部项目

IDEA设置Maven路径 1. 当前项目有效&#xff08;每次都要设置Maven路径&#xff09;2. 全部项目有效&#xff08;一次设置永久解决&#xff09; 1. 当前项目有效&#xff08;每次都要设置Maven路径&#xff09; 将Maven home path、User settings file、Local repository设置为…