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

news2024/11/17 7:24:47

目录 🚀🚀

回顾

getBean()方法的使用 

根据name来获取对象

再谈getBean()

(1) 配置扫描路径

(2) 添加注解

① spring注解简介 

② 对类注解的使用

③ 注解Bean对象的命名问题

④ 方法加Bean注解

(3) @Bean 注解的重命名

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

 属性注入

属性注入优缺点

setter注入 

 setter注入优缺点

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

构造方法优缺点

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

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

总结 


😍创作不易多多支持🫡 


回顾

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

<?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的方法;

        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方法的原码:

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) 方法; 

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

protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

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

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的生成为:

beanName = beanName.subString(1);

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

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

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

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

protected String transformedBeanName(String name) {
    return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

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

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:

<bean class="User1" name="user1"/>
<alias name="user1" alias="user2,user3"/>

再谈getBean()

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

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

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

(1) 配置扫描路径

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

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

 其中User类的代码如下:

package org.example;

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

Student的代码如下:

package org.select;

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

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

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

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

<?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注解的示例:

@Component
public class MyComponent {
    @Autowired
    @Qualifier("myBean")
    private MyBean myBean;
    
    // ...
}

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

② 对类注解的使用

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml"));
        Student student = beanFactory.getBean("student", Student.class);
        student.sayHi();

 输出:

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

③ 注解Bean对象的命名问题

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

其源码如下:

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注解的方法来将这个类注入到容器:

package org.example2;

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

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

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

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注解和五大类注解:

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;
    }
}

        文章目录结构:

        添加扫描路径:

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

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

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注解的源码, 我们可以参考参考

//
// 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:

@Bean(name = "newName")
public MyBean myBean() {
    return new MyBean();
}

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

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

@Bean(name = "newName")
public MyBean myBean() {
    return new MyBean();
}

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

        @Bean支持指定多个别名 

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

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

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

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

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

    @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存储的时候,  容器会自动忽略.

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

    @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类:

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类:

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

创建UserController类:

@Controller
public class UserController {
    @Autowired
    private UserService userService;

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

 生成启动类:

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修饰的变量

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

    @Autowired
    private final UserService userService;

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

        就不会出现编译前异常. 

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

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

setter注入 

        创建类UserService:

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类:

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

        添加main方法:

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类:

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

        创建UserService类:

@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());
    }
}

        添加启动项:

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注解给注释掉:

@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 :

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

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

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

         释放掉这个 @Autowired :

@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修饰的, 仍然可以注入:

 

    private final UserRepository userRepository;

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

构造方法优缺点

        优点

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

        缺点

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

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

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

@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就不知道注入哪一个,就会产生报错信息, 代码如下;

@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对象, 如下:

public class UserController {
    @Resource
    private User user;

    public User getUser() {
        return user;
    }
}

        main方法:

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

@Controller
public class UserController {
    @Resource(name = "user1")
    private User user;

    public User getUser() {
        return user;
    }
}

(b) 使用@Qualifier注解:

@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")


 

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

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

相关文章

投标文件的注意事项

一、检查标书 1.1有时候标书需要从别的地方复制黏贴文件&#xff0c;记住复制内容可以&#xff0c;但是不要复制“落款和时间”的格式&#xff0c;落款和时间的格式借鉴你的招标文件中给响应文件格式的落款和时间&#xff0c;切记&#xff01; 1.2检查标书是否有空页&#xf…

数据中心运维管理:从人工到智能需要走几步?

一切的变化来自于数据中心规模、复杂度、设备多样性的挑战&#xff0c;将运维平台的重要性推向历史高点。 此外&#xff0c;基于业务连续性方面的考虑&#xff0c;分布式数据中心成为越来越多客户的选择。 一、数据中心面临的挑战 运维管理分散&#xff0c;缺乏统一的管理 I…

Linux:设置Ubuntu的root用户密码

执行以下命令&#xff1a; 给root用户设置密码 sudo passwd 输入两次密码 切换root su root 退出root用户 exit

地埋式积水监测仪厂家直销推荐,致力于积水监测

地埋式积水监测仪是一种高科技设备&#xff0c;能够实时监测地面积水深度&#xff0c;并及时发出预警信息&#xff0c;有效避免因积水而产生的安全隐患。这种智能监测仪可以安装在城市道路、立交桥、地下车库等易积水地势较低的地方&#xff0c;以确保及时监测特殊地段的积水&a…

【Sorted Set】Redis常用数据类型: ZSet [使用手册]

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 目录 ⑤Redis Zset 操作命令汇总1. zadd 添加或…

APM工具skywalking部署

一 整体架构 整个架构&#xff0c;分成上、下、左、右四部分&#xff1a; 上部分 Agent &#xff1a;负责从应用中&#xff0c;收集链路信息&#xff0c;发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。而我们目前采用的是&…

css渐变详解(重复性线性渐变、径向渐变、重复性径向渐变的使用)

目录 线性渐变 重复性线性渐变 径向渐变 重复性径向渐变的使用 线性渐变 线性渐变是向下、向上、向左、向右、对角方向的颜色渐变。 其语法格式为&#xff1a; background-image: linear-gradient(side-or-corner|angle, linear-color-stop); 参数说明如下&#xff1a; …

mongo DB -- aggregate分组查询后字段展示

一、分组查询 在mongoDB中可以使用aggregate中的$group操作对集合中的文档进行分组,但是查询后的数据不显示其他字段,只显示分组字段 aggregate进行分组示例 db.collection.aggregate([{$group: {_id: "$field"}},]) 查询后显示 展开只显示两个字段 二、显示所有字段…

10.分组循环练习题

分组循环 https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528771/jiao-ni-yi-ci-xing-ba-dai-ma-xie-dui-on-zuspx/?envTypedaily-question&envId2023-11-16 分组循环 适用场景&#xff1a; 按照题目要求&#xff0c;数组会被分割成若…

微信运营神器:从群发到批量添加,让你的微信营销更轻松

在这个数字化时代&#xff0c;微信已经成为了我们生活中不可或缺的一部分。对于许多企业和个人来说&#xff0c;微信营销也是非常重要的一部分。但是&#xff0c;微信营销并不是一件容易的事情&#xff0c;需要花费大量的时间和精力。为了解决这个问题&#xff0c;今天我们将向…

邻趣连接力:如何无代码集成CRM、电商平台和营销系统,提升广告推广效率

连接即服务&#xff1a;邻趣无代码集成方法 传统的电商系统集成过程需要大量的时间和资源进行API开发&#xff0c;这不仅耗时耗力&#xff0c;还需要专业的技术团队支持。然而&#xff0c;邻趣通过提供一种无需API开发的连接方法&#xff0c;极大地简化了整个集成过程。商家只…

3D人脸扫描设备助力企业家数字人复刻,打破商业边界

京都薇薇推出数字人VN&#xff0c;以京都薇薇董事长为原型制作&#xff0c;赋能品牌直播、短片宣传、线上面诊等活动&#xff0c;进一步增强消费者对品牌的交互体验&#xff0c;把元宇宙与品牌相融合&#xff0c;推动品牌线上服务与线下服务实现数字一体化&#xff0c;打造一个…

【C/C++】排序算法代码实现

这里&#xff0c;汇总了常见的排序算法具体代码实现。使用C语言编写。 排序算法实现 插入排序冒泡排序选择排序快速排序希尔排序归并排序 插入排序 #include <stdio.h> #include <stdlib.h>void InsertSort(int arr[],int n){int i,j,temp;for(i 1;i < n;i){ …

捷诚管理信息系统 SQL注入漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、产品介绍 捷诚管理信息系统是一款功能全面&#xff0c;可以支持自…

【独家发布】抖音半蓝V官方免费认证技术

先在巨量引擎升级dou账号 随后上传资料进行验证即可 逐步操作 全程实操保姆及教程 后续0粉点亮蓝v技术教程 来自&#xff1a;人类小徐-分享有价值的资源

构建个性化预约服务:预约上门服务系统源码解读与实战

随着社会的发展&#xff0c;预约上门服务系统在满足用户需求、提升服务效率方面发挥着越来越重要的作用。在本文中&#xff0c;我们将深入研究预约上门服务系统的源码&#xff0c;通过实际的技术代码示例&#xff0c;揭示系统内部的关键机制&#xff0c;以及如何在实际项目中应…

数据治理技术:研究现状与数据规范

随着信息技术的迅速发展,数据规模逐渐扩大&#xff0c;与此同时&#xff0c;劣质数据也随之而来&#xff0c;极大地降低了数据挖掘的质量&#xff0c;对信息社会造成了严重的困扰&#xff0c;劣质数据大量存在于很多领域和机构&#xff0c;国外权威机构的统计表明&#xff1a;美…

OpenSearch开发环境安装Docker和Docker-Compose两种方式

文章目录 简介常用请求创建映射写入数据查询数据其他 安装Docker方式安装OpenSearch安装OpenSearchDashboard Docker-Compose方式Docker-Compose安装1.设置主机环境2.下载docker-compose.yml文件3.启动docker-compose4.验证 问题问题1&#xff1a;IPv4 forwarding is disabled.…

完美解决:在Ubuntu18.04下ROS Melodic基于python3的cv_bridge的一点子歪门邪道

由于在Ubuntu18.04下ROS Melodic是运行在python 2.7环境下&#xff0c;而我的程序需要运行在anaconda创建的python 3.x环境里&#xff0c;这就需要用到cv_bridge这个库&#xff0c;而不出意外的&#xff0c;各种报错&#xff0c;比如&#xff1a; from cv_bridge.boost.cv_bri…

竞赛选题 题目:垃圾邮件(短信)分类 算法实现 机器学习 深度学习 开题

文章目录 1 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器学习的垃圾邮件分类 该项目…