Spring框架学习 -- 读取和存储Bean对象

目录 🚀🚀

回顾

getBean()方法的使用

根据name来获取对象

再谈getBean()

[(1) 配置扫描路径](#(1) 配置扫描路径)

[(2) 添加注解](#(2) 添加注解)

[① spring注解简介](#① spring注解简介)

[② 对类注解的使用](#② 对类注解的使用)

[③ 注解Bean对象的命名问题](#③ 注解Bean对象的命名问题)

[④ 方法加Bean注解](#④ 方法加Bean注解)

[(3) @Bean 注解的重命名](#(3) @Bean 注解的重命名)

[(4) 获取Bean对象 -- 对象装配](#(4) 获取Bean对象 -- 对象装配)

属性注入

属性注入优缺点

setter注入

setter注入优缺点

构造方法注入 (官方推荐)

构造方法优缺点

[(5) 另外一种注入关键字: @Resource](#(5) 另外一种注入关键字: @Resource)

[(6) 同一个变量多个@Bean注入报错](#(6) 同一个变量多个@Bean注入报错)

总结


😍创作不易多多支持🫡


回顾

回忆一下我们之前是如何存储和使用Bean对象的, 首先我们需要在spring配置文件写入Bean对象, 或者说是spring容器中注入Bean对象:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="user1" class="org.example.User"></bean>
        <bean id="user2" class="org.example.User"></bean>
</beans>

然后在启动类中new出一个上下文对象, 要么使用BeanFactory, 要么使用ApplicationContext, 然后调用上下文对象中的getBean方法, 来获取到对应的Bean对象.

但是我每次使用都要去在spring配置文件中写入bean标签, 然后写上对应id和类. 这些步骤过于繁琐, 于是spring的作者就创造出了一种更简单的方式, 那就是直接在对应的类下面写上注解, 注解中填入id, 就省去了写路径的痛苦.

我们现在就可以直接通过一行注解, 来代替我们之前要写一行配置的尴尬了,

不过在写注解之前需要一点准备工作.

首先在这之前我们应该去了解一下获取Bean对象的方法, 也就是getBean方法

getBean()方法的使用

spring容器提供了五种获取Bean的方法, 可以根据Bean的name来获取Bean, 也可以根据classType 来获取Bean对象, 所有的根据Bean的name来获取Bean的方法, 最后底层都会调用下面的doGetBean方法来获取Bean对象

根据name来获取对象

spring 容器提供了三种根据Bean 的name来获取Bean的方法;

java 复制代码
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));
        User user = (User) beanFactory.getBean("user");
        User user1 = (User) beanFactory.getBean("user", User.class);
        User user2 = (User) beanFactory.getBean("user", new OrderUser());
  • getBean("user") 是根据Bean的name 来获取Bean对象
  • getBean("user", User.class) 是根据Bean的name来获取Bean, 然后判断Bean是否属于User类
  • getBean("user", new OrderUser());则是使用自定义的方法来生成Bean对象

上面三种方法都会调用doGetBean方法来生成Bean对象, 下面是doGetBean方法的原码:

java 复制代码
protected <T> T doGetBean(
    String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {

    // name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx
    // name有可能传入进来的是别名,那么beanName就是id
    String beanName = transformedBeanName(name);
    Object beanInstance;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

真正的BeanName来自这个transformedBeanName(name) 方法;

下面是这个方法的具体实现:

java 复制代码
protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

顺藤摸瓜. 先来看看BeanFactoryUtils.transformedBeanName(name) 的实现:

java 复制代码
public static String transformedBeanName(String name) {
    Assert.notNull(name, "'name' must not be null");
    if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
        return name;
    }
    return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
        do {
            beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
        }
        while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
        return beanName;
    });
}

什么是BeanFactory.FACTORY_BEAN_PREFIX?

翻阅原码可以知道这个是一个字符串对象, 值为"&".

我们再来看看String的subString方法:

结合原码, 也就是说beanName的生成为:

java 复制代码
beanName = beanName.subString(1);

里层是一个do while循环, 也就是先去掉一个字符, 然后循环查看beanName的前面是否有"&"字符, 如果有就一直去掉这&字符.

如果传进来的name从一开始就不是&字符开头的字符串, 那么就直接返回当前的name.

总结来说就是 BeanFactoryUtils.transformedBeanName(name) 是去掉name前面的所有的&字符

接下来回头看看这个canonicalName方法:

java 复制代码
protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

看看这个方法的具体实现:

java 复制代码
public String canonicalName(String name) {
    String canonicalName = name;
    // Handle aliasing...
    String resolvedName;
    do {
        resolvedName = this.aliasMap.get(canonicalName);
        if (resolvedName != null) {
            canonicalName = resolvedName;
        }
    }
    while (resolvedName != null);
    return canonicalName;
}

解析:

  • String name 为上面一层函数BeanFactoryUtils.transformedBeanName(name) 传过来的去掉开头的所有的&字符的字符串.
  • 此函数里面首先使用canonicalName字符串对象将这个去掉&的字符串给存起来了.
  • 创建了一个字符串变量: resolvedName
  • 随后进入dowhile循环, 首先是调用了this.aliasMap.get(canonicalName);
  • aliasMap是一个hashMap, 这里调用get方法拿到key = canonicalName, 如果对应value值不为空, 那么就将这个值赋值给canonicalName, 然后返回这个值canonicalName.

Bean的别名存放在一个aliasMap中,其中KEY=别名,VALUE=beanName/别名,根据别名从aliasMap中拿到的可能是真正的beanName,也可能还是一个别名,所以用do-while循环,直到拿出来的名字从aliasMap再找不到对应的值,那么该名字就是真正的beanName了

对bean 进行定义的时候,除了可以使用id 命名,为了提供多个别名,使用alias来指定,这些所有的名称都指向同一个bean:

XML 复制代码
<bean class="User1" name="user1"/>
<alias name="user1" alias="user2,user3"/>

再谈getBean()

我们明明就可以根据一个String name来锁定一个Bean对象, 为什么后面还需要传入第二个参数 XXX.class??

首先我们如果传入xxx.class, 我们在获取Bean对象的时候, 是返回一个Object类型的对象, 这个时候还需要我们进行强制类型转换成我们需要的类型, 但是这个时候可能会出错. 也就是说, 如果将其转换成了一个不是我们所需要的类, 那么在运行期间就会抛出异常.

我们第二个参数传入一个class, 此方法更安全,因为我们可以编译阶段就发现错误而不是在运行阶段。

(1) 配置扫描路径

想要将对象成功存储到容器中, 我们需要先配置一下存储对象的扫描路径, 只有被扫描到的包下的类, 添加注解才能被成功的识别到并被保存到容器中.

首先我们拿下面这个这个目录结构为例:

其中User类的代码如下:

java 复制代码
package org.example;

public class User {
    public void sayHi() {
        System.out.println("hello, how are you?");
    }
}

Student的代码如下:

java 复制代码
package org.select;

public class Student {
    public void sayHi() {
        System.out.println("hello student!! ");
    }
}

下面我们首先添加扫描路径, 我们扫描select包下的所有的类, 于是就在spring配置文件中写入其扫描路径:

XML 复制代码
<content:component-scan base-package="org.select"></content:component-scan>

将其访问spring配置文件的beans标签中:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

        <content:component-scan base-package="org.select"></content:component-scan>
</beans>

-- 其实添加这个扫描路径 就是为了更加精确的扫描出需要的类的所在的包, 为了更佳的性能

(2) 添加注解

想要通过添加注解的方式来存储Bean对象, 那么首先就需要去了解一下spring的注解:

① spring注解简介

Spring的注解是一种用于简化配置和开发的方式,它可以帮助开发人员更轻松地使用Spring框架。下面是几个常用的Spring注解的介绍:

  1. @Autowired:用于自动装配依赖关系。当Spring容器需要为一个属性注入一个bean时,它会查找与该属性类型匹配的bean,并将其自动注入到属性中。

  2. @Qualifier:用于指定要注入的bean的名称。当有多个与属性类型匹配的bean时,可以使用@Qualifier注解来指定要注入的bean的名称。

  3. @Component:用于将一个类标记为Spring容器的组件。被标记为@Component的类将被Spring自动扫描并注册为bean。

  4. @Controller:用于标记一个类作为Spring MVC的控制器。被标记为@Controller的类将处理HTTP请求并返回相应的视图。

  5. @Service:用于标记一个类作为业务逻辑层的组件。被标记为@Service的类通常包含业务逻辑,并被其他组件调用。

  6. @Repository:用于标记一个类作为数据访问层的组件。被标记为@Repository的类通常用于访问数据库或其他持久化存储。

  7. @Configuration:用于标记一个类作为Spring的配置类。被标记为@Configuration的类通常包含了一些用于配置Spring容器的bean定义。

  8. @Bean:用于在配置类中定义一个bean。被标记为@Bean的方法将返回一个对象,并将其注册为Spring容器的bean。

  9. @Value:用于注入属性值。可以将@Value注解应用于属性上,从而将属性值从配置文件中注入到属性中。

下面是一个使用@Autowired和@Qualifier注解的示例:

java 复制代码
@Component
public class MyComponent {
    @Autowired
    @Qualifier("myBean")
    private MyBean myBean;
    
    // ...
}

接下来我们仔细看看该如何使用...!!!

② 对类注解的使用

java 复制代码
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));
        Student student = beanFactory.getBean("student", Student.class);
        student.sayHi();

输出:

使用spring容器手动添加Bean对象和扫描注解Bean对象是可以混用的.

③ 注解Bean对象的命名问题

使用注解访问Bean对象, 那么getBean传入的第一个参数开头字母直接小写即可, 但是如果首字母和第二个字母都是大写, 那么开头一个字母小写是错误的, 这个时候直接使用原来的类名即可.

其源码如下:

java 复制代码
public static String decapitalize(String name) {
 if (name == null || name.length() == 0) {
 return name;
 }
 // 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了
 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
 Character.isUpperCase(name.charAt(0))){
 return name;
 }
 // 否则就将⾸字⺟⼩写
 char chars[] = name.toCharArray();
 chars[0] = Character.toLowerCase(chars[0]);
 return new String(chars);
}

举个例子, 有三个类, 他们添加了注解, 然后使用getBean方法取出去Bean对象, 如下:

其中第二种就会找不到Bean对象.

④ 方法加Bean注解

下面我们创建一个Test1类, 此类中有两个字段, 同时实现了其toString方法, 下面我们将通过添加Bean注解的方法来将这个类注入到容器:

java 复制代码
package org.example2;

/**
 * 普通的文章实体类
 */
public class Test1 {
    private int age;
    private String name;

    @Override
    public String toString() {
        return "Test1{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

但是@Bean注解必须配合五大类注解一起使用

java 复制代码
package org.select;

/**
 * 普通的文章实体类
 */
public class Test1 {
    private int age;
    private String name;

    @Override
    public String toString() {
        return "Test1{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

添加Bean注解和五大类注解:

java 复制代码
package org.example2;

import org.select.Test1;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class RetTest1 {
    @Bean
    public Test1 returnTest() {
        Test1 test1 = new Test1();
        test1.setAge(18);
        test1.setName("张三");
        return test1;
    }
}

文章目录结构:

添加扫描路径:

XML 复制代码
<content:component-scan base-package="org.example2"></content:component-scan>

配置上下文, 使用getBean获取Test1 的对象

java 复制代码
ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
//        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));
        RetTest1 retTest1 = context.getBean("retTest1", RetTest1.class);
        retTest1.sayHi();
        Test1 test1 = context.getBean("test1", Test1.class);
        System.out.println(test1.toString());

-- 输出结果如下:

可以发现, 一个类的方法添加了@Bean注解, 这个@Bean注解可以让被标记的方法返回的对象存储进入容器, 同时@Bean需要配合五大类注解进行, 所以被五大类注解标记的类对象同样也会被存入容器.

需要注意的是, 使用@Bean注解的时候, 这里如果去使用BeanFactory去获取上下文的话, 就会找不到@Bean注解的方法返回的类.

还有一点就是, 最后@Bean注解的方法, 在获取这个类的时候, getBean中的参数是@Bean注解的方法名, 而不是类名.

如果将ApplicationContext 获取的上下文对象换成 BeanFactory, 就会显示如下:

原因定位:

使用`new XmlBeanFactory(new ClassPathResource("bean.xml"))`实例化出来的对象是无法读取Spring注解配置文件的,因为`XmlBeanFactory`只能解析XML格式的配置文件,而无法解析注解配置。而`ClassPathXmlApplicationContext`可以同时解析XML和注解配置文件,因此使用`new ClassPathXmlApplicationContext("bean.xml")`可以成功读取Spring注解配置文件。

所以我们在IDEA中使用BeanFactory的时候可以看到一个删除线:

此删除线的意思是 IDEA 不推荐使用这个方法, 而不是说不能用, 不推荐使用的原因是此方法已经过时, 有更好, 更安全的方法替代. 例如 ApplicationContext.

(3) @Bean 注解的重命名

以下是@Bean注解的源码, 我们可以参考参考

java 复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    /** @deprecated */
    @Deprecated
    Autowire autowire() default Autowire.NO;

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

在使用@Bean注解的时候, 可以通过name 属性来对Bean进行重新命名, 例如将下面一个名为myBean的Bean对象重新命名为newName:

java 复制代码
@Bean(name = "newName")
public MyBean myBean() {
    return new MyBean();
}

或者是直接在里面传入字符串, 即name = 此字符串

此外,还可以使用@Bean注解的value属性来进行重命名,例如:

java 复制代码
@Bean(name = "newName")
public MyBean myBean() {
    return new MyBean();
}

我们总结一下这三种方式:

@Bean支持指定多个别名

需要注意的是, 不管是value 还是 name 来命名这个Bean注解, 都不能再使用原来的方法名来获取这个Bean对象了.

不管是value还是name. 它们都是一个字符串的数组, 可以传入多个别名:

java 复制代码
 @Bean( value = {"newName","newName2","newName3"})
    public Test1 test1() {
        Test1 test1 = new Test1();
        test1.setAge(18);
        test1.setName("张三");
        return test1;
    }

但是这几个名字newName, newName2,newName3都是指的同一个类.

需要注意的一个问题是, 如果我连续有三个方法或者是更多的方法都用的同一个Bean别名, 如下:

java 复制代码
    @Bean("getTest1")
    public Test1 test1() {
        Test1 test1 = new Test1();
        test1.setAge(18);
        test1.setName("张三");
        return test1;
    }

    @Bean("getTest1")
    public Test1 test2() {
        Test1 test1 = new Test1();
        test1.setAge(17);
        test1.setName("李四");
        return test1;
    }
    @Bean("getTest1")
    public Test1 test3() {
        Test1 test1 = new Test1();
        test1.setAge(19);
        test1.setName("王五");
        return test1;
    }

这个时候我再去访问这个getTest1这个Bean对象, 将会输出哪一个??

-- 输出:

从结果上来看是输出的第一个, 其实这个是和它的加载的顺序时有关系的, 其中我们可以使用@Order注解来设定我们@Bean注解的加载顺序

如果多个Bean的名称相同, 那么程序执行不会报错, 但是第一个Bean之后的对象不会被存放到容器中, 也就是只有在第一次创建Bean的时候, 会将对象和Bean名称关联起来, 后续再有相同名称的Bean存储的时候, 容器会自动忽略.

同时如果我们的方法里面传入了参数, 那么在编译期间也会抛出异常:

java 复制代码
    @Bean("getTest1")
    public Test1 test1(int age) {
        Test1 test1 = new Test1();
        test1.setAge(age);
        test1.setName("张三");
        return test1;
    }

或者版本更高一点的编译器会在编译前就出现错误提示:

(4) 获取Bean对象 -- 对象装配

获取Bean对象, 也叫作对象装配, 是把对象取出来放到某个类中, 有时候页脚对象注入

对象装配的实现方法有以下三种:

  1. 属性注入
  2. 构造方法注入
  3. Setter注入

属性注入

属性注入是使用@Autowired实现的, 将Service类注入到Controller中, Service类中的代码实现如下:

首先创建User类:

java 复制代码
package com.java.demo.User;

public class User {
    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

创建UserService类:

java 复制代码
@Service
public class UserService {
    public User getUser(Integer id) {
        User user = new User();
        user.setAge(id);
        user.setName("my" + id);
        return user;
    }
}

创建UserController类:

java 复制代码
@Controller
public class UserController {
    @Autowired
    private UserService userService;

    public User getUser(Integer id) {
        return userService.getUser(id);
    }
}

生成启动类:

java 复制代码
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
        UserController userController = context.getBean(UserController.class);
        System.out.println(userController.getUser(1).toString());
    }
}

解释: 我们首先配置好spring上下文对象之后, 就去获取这个userController的Bean对象, 然后调用其getUser方法 ,但是你可能会问, userController的getUser方法里面是UserService的getUser方法,但是这个对象里面的UserService字段userService未赋值, 怎么能调用其getUser方法啊, 这其实就是Autowired注解的作用, 他自动将UserService的Bean对象赋值给这个UserService字段, 然后调用.

随后在userService的对象中调用getUser(1)

最后返回User, 调用这个User的toString方法. 生成的结果如下:

属性注入优缺点

属性注入使用非常简单, 但是也存在着一些问题:

(a) 无法注入被final修饰的变量

图中显示, 变脸没有被初始化, 这个时候如果我们在这个类中加上这个变量的构造方法:

java 复制代码
    @Autowired
    private final UserService userService;

    public UserController() {
        userService = new UserService();
    }

就不会出现编译前异常.

(b) 通用性问题: 只是用与ioc容器

(c) 更容易违背单一性设计元素, 但是用起来更简单.

setter注入

创建类UserService:

java 复制代码
public class UserService {

    public UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void sayHi() {
        System.out.println("UserService says hi");
        userRepository.add();
    }
}

创建类UserRepository类:

java 复制代码
@Repository
public class UserRepository {
    public int add() {
        System.out.println("userRepository add");
        return 1;
    }
}

添加main方法:

java 复制代码
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.sayHi();
    }
}

此时UserService类中的UserRepository字段会根据@Autowired修饰的setter方法自动注入Bean对象.

结果输出--

setter注入优缺点

(a) 无法注入被final修饰的变量 .

(b) setter中每一次都只设置一个属性, 遵守设置的单一性设计原则.

(c) setter注入的对象可以被修改.

构造方法注入 (官方推荐)

创建UserRepository类:

java 复制代码
@Repository
public class UserRepository {
    public int add() {
        System.out.println("userRepository add");
        return 1;
    }
}

创建UserService类:

java 复制代码
@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void sayHi() {
        System.out.println("sayHi in UserService: " + userRepository.add());
    }
}

添加启动项:

java 复制代码
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.sayHi();
    }
}

-- 输出

此时此刻,我将UserService类中的构造方法上面的@Autowired注解给注释掉:

java 复制代码
@Service
public class UserService {

    private UserRepository userRepository;

    // @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void sayHi() {
        System.out.println("sayHi in UserService: " + userRepository.add());
    }
}

启动, 发现仍然可以输出结果:

为什么? 其实这是官方推荐的注入方法,所以被spring官方特别照顾的注入方式, 标准的方法是要加的.

如果当前类中只存在一个构造方法时, @Autowired是可以省略的.

如果有多个构造方法, 仍然注释掉@Autowired :

java 复制代码
//    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserService(UserRepository userRepository, Integer i) {
        this.userRepository = userRepository;
    }

此时启动的话是会报错的:

释放掉这个 @Autowired :

java 复制代码
@Service
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public UserService(UserRepository userRepository, Integer i) {
        this.userRepository = userRepository;
    }
    public void sayHi() {
        System.out.println("sayHi in UserService: " + userRepository.add());
    }
}

问题消失:

使用构造方法去注入一个对象, 即使这个对象是被final修饰的, 仍然可以注入:

java 复制代码
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

构造方法优缺点

优点

  • 可以注入一个被final修饰的变量
  • 注入的对象不会被修改, 因为构造方法只会加载一次
  • 构造方法注入可以保证注入对象完全初始化
  • 构造方法注入通用性更好

缺点

  • 写法比属性注入复杂
  • 使用构造方法注入, 无法解决循环依赖问题.

(5) 另外一种注入关键字: @Resource

在进行类注入的时候,除了可以使用@Autowired 注解之外, 还可以使用@Resource进行注入, 如下:

java 复制代码
@Controller
public class UserController {
    @Resource
    private UserService userService;
    
    public User getUser(Integer id) {
        return UserService.getUser(id);
    }
}

@Autowired 和 @Resource 的区别

  • 出身不同, @Autowired 来自spring, 而@Resource来自JDK注解
  • 使用时设置的参数不同, 相比于@Autowired来说, @Resource支持更多的参数设置, 例如name, 根据名词获取Bean
  • @Autowired 可以用于setter注入, 构造方法注入, 属性注入, 但是@Resource只能用于setter注入, 和属性注入, 不能使用构造方法注入.
  • IDEA兼容性不同, 使用@Autowired 可能在idea专业版可能会出现误报的问题.

(6) 同一个变量多个@Bean注入报错

对于同一个对象进行注入的时候, 但是找到了多个Bean对象, 此时spring就不知道注入哪一个,就会产生报错信息, 代码如下;

java 复制代码
@Component
public class Users {
    
    @Bean
    public User user1() {
        User user = new User();
        user.setAge(18);
        user.setName("张三");
        return user;
    }
    
    @Bean
    public User user2() {
        User user = new User();
        user.setAge(20);
        user.setName("李四");
        return user;
    }
}

在另外一个类中获取User对象, 如下:

java 复制代码
public class UserController {
    @Resource
    private User user;

    public User getUser() {
        return user;
    }
}

main方法:

java 复制代码
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml");
        UserController userController = context.getBean("userController", UserController.class);

    }

-- 输出

找到了两个Bean对象. 抛出此异常.
分析: User类中有两个方法都是使用了@Bean注解, 在UserController类中使用@Resource进行属性注入, 此时就会根据@Bean来获取到这个User对象,但是这里有两个Bean对象, 到底赋值哪一个?
原因: 非唯一的Bean对象
解决方案:

  • @Resource的name值去定义, 例如 @Resource(name = "user1")
  • 使用@Qualifier注解定义其使用的Bean对象的名称
    解决方案的实例:

(a) 使用@Resource 的name注解:

java 复制代码
@Controller
public class UserController {
    @Resource(name = "user1")
    private User user;

    public User getUser() {
        return user;
    }
}

(b) 使用@Qualifier注解:

java 复制代码
@Controller
public class UserController {
    @Resource
    @Qualifier(value = "user1")
    private User user;

    public User getUser() {
        return user;
    }
}

总结

  • 将对象存储到spring中的方法:
    • 使用类注解: @Controller, @Service, @Repository, @Configuration, @Component
    • 使用方法注解: @Bean, 必须配合上面的类注解来使用
  • Bean命名的规则, 首字母和第二个字母都非大写, 获取Bean的用首字母小写,如果首字母和第二个字母都是大写, 那么直接使用欧原来的Bean名获取Bean对象.
  • 从spring中获取对象
    • 属性注入
    • setter注入
    • 构造方法注入(官方推荐)
  • 注入的关键字还有
    • @Autowired
    • @Resource
    • @Resource和@Autowired的区别
  • 解决同一变量不同的Bean对象的问题
    • 使用@Resource(name = "xxx")
    • 使用@Qualifier(value = "xxx")


相关推荐
爱睡懒觉的焦糖玛奇朵1 小时前
【从视频到数据集:焦糖玛奇朵的魔法工具使用说明】
人工智能·python·深度学习·学习·算法·yolo·音视频
夏天想2 小时前
人类将从“执行者“变为“总导演”,学习Ai知识
人工智能·学习
晓梦林3 小时前
Baji1靶场学习笔记
笔记·学习
Java面试题总结3 小时前
java高频面试题(2026最新)
java·开发语言·jvm·数据库·spring·缓存
希冀1233 小时前
【CSS学习第十一篇】
前端·css·学习
苦逼的猿宝3 小时前
学生心理咨询评估系统
java·毕业设计·springboot·计算机毕业设计
隔窗听雨眠3 小时前
doctype、charset、meta如何控制整个渲染流水线
java·服务器·前端
牧羊狼的狼3 小时前
浅谈电商下单微服务流程
spring·spring cloud·微服务
魔法阵维护师4 小时前
从零开发游戏需要学习的c#模块,第十六章(安装 MonoGame 并创建第一个窗口)
学习·游戏·c#·monogame
xian_wwq4 小时前
【学习笔记】大模型备案到底要交什么材料
笔记·学习