赞
踩
在Spring 中定义Bean的方式主要有三种:
1、基于XML配置文件的方式(了解):通常会在配置文件中使用<bean>标签来定义Bean,并设置Bean的属性、依赖关系等信息。
2、基于注解的方式:在Java代码中使用注解来标识Bean,并指定Bean的属性、依赖关系等信息。常用的注解有@Component、@Controller、@Service等。
3、基于Java Config的方式,在Java配置类中使用@Bean注解来定义Bean。当开发者想要使用的Bean是第三方组件时,不能在源码上标注@Component,可以使用@Bean注解的方式。
总的来说,不同的方式适用于不同的场景。在实际使用过程中,我们可以根据业务需求来选择最适合的方式来定义Bean,也可以兼用多种方式来定义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);
- }
- }
在Spring框架中,@Bean注解和@Component注解均用于定义Bean,但是添加的位置和应用的场景不同。
1、@Bean注解主要用于在配置类中显式配置Bean。
2、@Component注解主要用于在类前隐式的配置Bean。
3、实际应用中,通常是两种方式混合使用。
在Spring IoC容器中获取Bean可以通过以下几种方式:
1、context.getBean(Class<T> requiredType):根据类型获取Bean。其中:
2、context.getBean(String name):根据名称获取Bean。其中:
3、context.getBean(String name, Class<T> requiredType):根据Bean名称和类型获取Bean。
4、context.getBeansOfType(Class<T> type):根据类型获取容器中所有实现该类型的Bean。
在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);
- });
- }
- }
Bean的作用域就是指Spring中Java Bean 有效范围,其中最为常用的作用域有2种:
可以通过@Scope注解来设置Bean的作用域,该注解可以添加在类前(与@Component注解搭配使用),也可以添加在方法前(与@Bean注解搭配使用)。
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));
- }
- }
Spring框架主要支持三种注入方式:字段注入、构造器注入、setter方法注入。
1、字段注入(Field Injection):通过直接在类的字段上添加注解来注入依赖。
2、构造器注入(Constructor Injection):通过类的构造方法来注入依赖。
3、setter方法注入(Setter Injection):通过类的setter方法来注入依赖。
修改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对象构造出错。因此,构造器注入适用于必须的依赖关系。
修改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对象,则执行注入,反之则不执行注入。这种注入方式适用于那些可选的依赖。
1、Spring框架支持三种主要的注入方式:字段注入、构造器注入、setter方法注入。其中,字段注入是指在类的字段上添加注解来注入依赖。
2、可能导致空指针异常:一个Bean的初始顺序为静态变量或静态代码块 > 实例变量或初始化语句块 > 构造方法 > 字段注入。在静态代码块、初始化语句块、构造方法中使用@Autowired标记的字段,会引起空指针异常。
3、不利于测试: 在单元测试中,为了隔离被测试类与外部依赖之间的耦合,常常需要模拟依赖对象。字段注入会使得测试类无法直接通过构造器或方法注入模拟对象,从而增加了测试的复杂性。
4、无法注入 final 字段: 字段注入无法用于注入 final 字段,这会限制一些设计和测试的可能性。
@Autowired的Bean匹配机制是指在执行依赖注入之前,Spring容器会根据被注入字段、方法参数的类型来查找匹配的 Bean。
该机制的执行顺序为:
1、先根据类型匹配
2、如果匹配类型的实例有多个,则查看优先级注解:
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名称。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。