JavaEE进阶:Spring 更简单的读取和存储对象

news2025/1/16 20:51:41

文章目录

  • 前言
  • 一、存储 Bean 对象
    • 1、前置⼯作:配置扫描路径(重要)
    • 2、添加注解存储 Bean 对象
      • ① @Controller(控制器存储)
      • ② @Service(服务存储)
      • ③ @Repository(仓库存储)
      • ④ @Component(组件存储)
      • ⑤ @Configuration(配置存储)
      • ⑥ 小提示
    • 3、为什么要这么多类注解?
      • ① 类注解之间的关系
      • ② 注意 Bean 的命名
    • 4、方法注解 @Bean
      • ① 方法注解要配合类注解使用
      • ② @Bean 注解命名规则
        • Ⅰ 问题
        • Ⅱ 解决方案
      • ③ 重命名 Bean
        • Ⅰ 问题
        • Ⅱ 解决方案
        • Ⅲ 练习
      • ④ 注意事项
  • 二、获取 Bean 对象(对象装配)
    • 1、属性注入
      • ① 练习1
      • ② 练习2
      • ③ 优点
      • ④ 缺点
    • 2、Setter 注入
      • ① 优点
      • ② 缺点
    • 3、构造方法注入(推荐)
      • ① 优点
        • Ⅰ 注入不可变对象(final)
        • Ⅱ 注入的对象不会被修改
        • Ⅲ 完全初始化
        • Ⅳ 通用性更好
    • 4、三种注入优缺点分析
    • 5、@Resource:另⼀种注⼊关键字
      • ① @Autowired 和 @Resource 的区别
    • 6、同⼀类型多个 @Bean 报错
      • ① 问题
      • ② 方案1:@Resource注解
      • ③ 方案2:@Qualifier注解
  • 三、综合练习

前言

经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的⽅法。

在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相关注解,来存储和读取 Bean 对象。

一、存储 Bean 对象

之前我们存储 Bean 时,需要在 spring-config 中添加⼀⾏ bean 注册内容才⾏,如下图所示:
在这里插入图片描述
⽽现在我们只需要⼀个注解就可以替代之前要写⼀⾏配置的尴尬了,不过在开始存储对象之前,我们先要来点准备⼯作。

1、前置⼯作:配置扫描路径(重要)

注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。

在 spring-config.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="com.wzr。service"></content:component-scan>
</beans>

其中标红的⼀⾏为注册扫描的包,如下图所示:
在这里插入图片描述

即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。

2、添加注解存储 Bean 对象

想要将对象存储在 Spring 中,有两种注解类型可以实现:

  1. 类注解:
    • @Controller:【控制器】验证前端传递的参数的 “安全检查”;
    • @Service:【服务层】服务调用的编译和汇总;
    • @Repository:【仓库(数据仓库…)】直接操作数据库
    • @Component:【组件】通用化的工具类
    • @Configuration:【配置】项目的所有配置
  2. ⽅法注解:@Bean。
    接下来我们分别来看。

① @Controller(控制器存储)

使⽤ @Controller 存储 bean 的代码如下所示:

@Controller
public class UserController {
    public void sayHello() {
        System.out.println("hello");
    }
}

此时我们先使⽤之前读取对象的⽅式来读取上⾯的 UserController 对象,如下代码所示:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
        UserController userController = context.getBean("userController", UserController.class);
        // 3.操作 Bean 对象
        userController.sayHello();
    }
}

② @Service(服务存储)

使⽤ @Service 存储 bean 的代码如下所示:

@Service
public class UserService {
    public void doService() {
        System.out.println("Do user service.");
    }
}

读取 bean 的代码:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();

        UserService userService =
                context.getBean("userService", UserService.class);
        userService.doService();
    }
}

③ @Repository(仓库存储)

使⽤ @Repository 存储 bean 的代码如下所示:

@Repository
public class UserRepository {
    public void doRepository() {
        System.out.println("Do user repository.");
    }
}

读取 bean 的代码:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();

//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();

        UserRepository userRepository =
                context.getBean("userRepository", UserRepository.class);
        userRepository.doRepository();
    }
}

④ @Component(组件存储)

使⽤ @Component 存储 bean 的代码如下所示:

@Component
public class UserComponent {
    public void doComponent() {
        System.out.println("Do user component.");
    }
}

读取 bean 的代码:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();

//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();

//        UserRepository userRepository =
//                context.getBean("userRepository", UserRepository.class);
//        userRepository.doRepository();

        UserComponent userComponent =
                context.getBean("userComponent", UserComponent.class);
        userComponent.doComponent();
    }
}

⑤ @Configuration(配置存储)

使⽤ @Configuration 存储 bean 的代码如下所示:

@Configuration
public class UserConfiguration {
    public void doConfiguration() {
        System.out.println("Do user configuration.");
    }
}

读取 bean 的代码:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//        // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
//        UserController userController = context.getBean("userController", UserController.class);
//        // 3.操作 Bean 对象
//        userController.sayHello();

//        UserService userService =
//                context.getBean("userService", UserService.class);
//        userService.doService();

//        UserRepository userRepository =
//                context.getBean("userRepository", UserRepository.class);
//        userRepository.doRepository();

//        UserComponent userComponent =
//                context.getBean("userComponent", UserComponent.class);
//        userComponent.doComponent();

        UserConfiguration userConfiguration =
                context.getBean("userConfiguration", UserConfiguration.class);
        userConfiguration.doConfiguration();
    }
}

⑥ 小提示

我们之前提到过:即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。

那么如果我们像把不是在配置的扫描包下的类对象存储到 Spring 中,应该怎么做呢?
我们的第一想法应该是:采用之前我们所学的方法,直接注册 Bean 到 Spring 中。

那么这种方式究竟可不可行呢?
答案是:可行!

例如:
此时无法扫描到 UserConfiguration 这个 Bean,我们在使用 UserConfiguration 的时候就会报错。
在这里插入图片描述
但是当我们用之前学的方法,手动的 把 UserConfiguration 注册到 Spring 中:
在这里插入图片描述
此时就可以成功使用 UserConfiguration 了。

总结:扫描和手动注册两种方法可以混用!

3、为什么要这么多类注解?

类的注解其实就是将类进行分类,让程序更加精细化,让程序员看到类注解之后,就能直接了解当前类的⽤途,方便维护,⽐如:

  • @Controller:表示的是业务逻辑层;
  • @Servie:服务层;
  • @Repository:持久层;
  • @Configuration:配置层。

程序的⼯程分层,调⽤流程如下:
在这里插入图片描述

① 类注解之间的关系

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
在这里插入图片描述
结论:@Controller / @Service / @Repository / @Configuration 都是基于 @Component,它们的作用都是将 Bean 储存到 Spring 中。

② 注意 Bean 的命名

默认情况下,使用 5 大类注解的 Bean 名称是将类首字母小写的命名规则。

ex:UserConfiguration -> userConfiguration

特殊情况:当⾸字⺟和第⼆个字⺟都是⼤写时
在这里插入图片描述
我们可以在 Idea 中使⽤搜索关键字“beanName”可以看到以下内容:
在这里插入图片描述
顺藤摸⽠,我们最后找到了 bean 对象的命名规则的⽅法:
在这里插入图片描述
它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,ctrl+左键 得到 源码如下:

	public static String decapitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        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);
    }

我们可以用一个类来测试一下:

public class App2 {
    public static void main(String[] args) {
        String name = "UserController"; // 首字母小写
        String name2 = "UController"; // 原类名
        System.out.println("name: " + Introspector.decapitalize(name));
        System.out.println("name2: " + Introspector.decapitalize(name2));
    }
}

得到的结果为:
在这里插入图片描述
所以对于上⾯报错的代码,我们只要改为以下代码就可以正常运⾏了:
在这里插入图片描述
总结:当⾸字⺟和第⼆个字⺟都是⼤写时,那么 Bean 的名称为原类名!

4、方法注解 @Bean

类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的,如以下代码的实现:

public class UserBeans {
    @Bean // 方法注解
    public User user(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

然⽽,当我们写完以上代码,尝试获取 Bean 对象中的 user 时却发现,根本获取不到:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("user", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}

以上程序的执⾏结果如下:
在这里插入图片描述
这是为什么呢?

① 方法注解要配合类注解使用

在 Spring 框架的设计中,⽅法注解 @Bean 要配合五大类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:

@Component
public class UserBeans {
    @Bean // 方法注解
    public User user(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

再次执⾏以上代码,运⾏结果如下:
在这里插入图片描述

② @Bean 注解命名规则

Ⅰ 问题

我们之前学习了 五大类注解 Bean 命名规则:

  1. 首字母和第⼆个字⺟都是⼤写时,那么 Bean 的名称为原类名
  2. 其他情况,类名首字母小写为 Bean 名称。

刚刚我们也学习到了,方法注解 要配合 类注解 使用,那么 @Bean 注解 的命名规则也和 五大类注解 相同吗?
我们用一个测试类来解决,如下所示:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("user", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}
@Component
public class UserBeans {
    @Bean // 方法注解
    public User user1(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

在这里插入图片描述
如果程序正确运行,则说明 @Bean 注解 的命名规则也和 五大类注解 相同;反之,则 @Bean 注解 有其独立的命名规则。

其结果为:
在这里插入图片描述
程序报错,说明 @Bean 注解 的命名规则也和 五大类注解 不同。

Ⅱ 解决方案

然而,我们对程序做出如下改变:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("user1", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}

其结果为:
在这里插入图片描述
说明 @Bean 注解的名称是方法名

③ 重命名 Bean

Ⅰ 问题

我们在使用五大类注解的时候,因为行为规范的原因,类很少有同名的情况出现;但是使用 @Bean 注解的时候会遇到一个问题 -> 不同类下方法名可能相同,例如:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("user1", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}
@Component
public class StudentBeans {
    @Bean // 方法注解
    public User user1(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("StudentBeans: 小李");
        user.setAge(18);
        return user;
    }
}
@Component
public class UserBeans {
    @Bean // 方法注解
    public User user1(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("UserBeans: 张三");
        user.setAge(18);
        return user;
    }
}

在这里插入图片描述
其运行结果为:
在这里插入图片描述
这样就会出现 数据覆盖 的问题,这里的 StudentBeans 的 user1 就被覆盖了,那我们不想让 StudentBeans 的 user1 该怎么办呢?

Ⅱ 解决方案

为了解决上述问题,我们可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所示:

@Component
public class StudentBeans {
    @Bean(name = "student_user1") // 方法注解
    public User user1(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("StudentBeans: 小李");
        user.setAge(18);
        return user;
    }
}

此时我们使⽤ student_user1 就可以获取到 StudentBeans 的 user1 了,如下代码所示:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("student_user1", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}

运行结果为:
在这里插入图片描述
其实除了上述方法,还有其他方法进行重命名:
实际上这个重命名的 name 其实是⼀个数组,⼀个 Bean 可以有多个名字,同时 name= 可以省略。

@Component
public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")
    @Bean(name = {"stu_user1", "student_user1"})
    public User user1() {
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("StudentBeans: 小李");
        user.setAge(18);
        return user;
    }
}

Ⅲ 练习

使用 @Bean 注解并重命名,尝试使⽤原来的类名⾸字⺟⼩写是否能正确获取到对象?

没有重命名前

public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")
//    @Bean(name = {"stu_user1", "student_user1"})
    @Bean
    public User studentUser1() {
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("StudentBeans: 小李");
        user.setAge(18);
        return user;
    }
}
public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("studentUser1", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}

运行结果为:
在这里插入图片描述
重命名后

@Component
public class StudentBeans {
//    @Bean(name = "student_user1") // 方法注解
//    @Bean("stu_user1")
//    @Bean(name = {"stu_user1", "student_user1"})
    @Bean(name = "stu_user1")
    public User studentUser1() {
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("StudentBeans: 小李");
        user.setAge(18);
        return user;
    }
}
public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user =
                context.getBean("studentUser1", User.class);
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}

运行结果为:
在这里插入图片描述
结论使⽤ @Bean 注解并重命名,使用方法名就不能获得 Bean 对象了。

④ 注意事项

  • 必须配合 五大类注解 一起使用(不然注入不进去);
  • @Bean 方法注解 只能使用在无参的方法上。

二、获取 Bean 对象(对象装配)

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊

对象装配(对象注⼊)的实现⽅法以下 3 种:

  1. 属性注⼊
  2. 构造⽅法注⼊
  3. Setter 注⼊

接下来,我们分别来看。下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中

1、属性注入

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。

UserService 类的实现代码如下:

@Service
public class UserService {
    public void doService() {
        System.out.println("Do user service.");
    }
}

UserController 类的实现代码如下:

@Controller
public class UserController {

    // 读取 UserService[从 Spring 读取]

    // 1.属性注入(Field Injection)
    @Autowired // 自动装配
    private UserService userService;

    public void sayHello() {
        System.out.println("Do User Controller.");
        userService.doService();
    }
}

获取 UserController 中的 sayHello ⽅法:

public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        UserController controller =
                context.getBean("userController", UserController.class);
        controller.sayHello();
    }
}

运行结果为:
在这里插入图片描述

① 练习1

我们可以使⽤ @Autowired 将 Service 类注⼊到 Controller 类中,那么我们做出如下修改,能否成功运行呢?
在这里插入图片描述

答案是:不行!

在这里插入图片描述

② 练习2

我们修改一下 Controller 类注入 Service 的对象名称,那么程序还能正确运行吗?

@Controller
public class UserController {

    // 读取 UserService[从 Spring 读取]

    // 1.属性注入(Field Injection)
    @Autowired // 自动装配
    private UserService us;

    public void sayHello() {
        System.out.println("Do User Controller.");
        us.doService();
    }
}

答案是:可以!

我们之前提到,五大类注解是根据类名进行命名,而 @Bean 注解是根据方法名进行注解;但是到了这里,由于 @Autowired 过于强大,它只需要我们二者满足其一即可!

这是因为,Spring 存储 Bean:实际上是以一个 HashMap<String,Object> 的形式存储。
@Bean key=方法名, value=方法返回的对象.

③ 优点

  • 写法简单

④ 缺点

  1. 功能缺陷(主要):不能注入一个 final 修饰的属性
    final 修饰的变量需要满足
    1. 使用时直接赋值
    2. 构造方法赋值
  2. 通用性问题(主要):只适用于 IoC 框架(容器)
  3. 设计原则问题:更容易违背单一设计原则(因为使用简单,所以滥用的风险更大)

2、Setter 注入

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解,如下代码所示:

@Controller
public class UserController {

    // 读取 UserService[从 Spring 读取]

//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;

    // 2.Setter 注入(Setter Injection)
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void sayHello() {
        System.out.println("Do User Controller.");
        userService.doService();
    }
}

① 优点

  • 符合单一设计原则(一个 Setter 只针对一个对象)。

② 缺点

  1. 不能注入一个不可变的对象(例如:final)。
  2. 注入对象可能改变(setter 方法可能会被多次调用,所以就有被修改的风险)。

3、构造方法注入(推荐)

构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:

@Controller
public class UserController {

    // 读取 UserService[从 Spring 读取]

//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;

//    // 2.Setter 注入(Setter Injection)
//    private UserService userService;
//
//    @Autowired
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }

    // 3.构造方法注入(Constructor Injection)
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void sayHello() {
        System.out.println("Do User Controller.");
        userService.doService();
    }
}

当当前类有且仅有一个构造方法时,@Autowired 可以省略!
即:当存在多个构造方法时,@Autowired 不能省略!!!!!!

① 优点

Ⅰ 注入不可变对象(final)

案例:

@Controller
public class UserController {

    // 读取 UserService[从 Spring 读取]

//    // 1.属性注入(Field Injection)
//    @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
//    private UserService us;

//    // 2.Setter 注入(Setter Injection)
//    private UserService userService;
//
//    @Autowired
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }

    // 3.构造方法注入(Constructor Injection)
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        System.out.println("----------------- 执行第1个构造方法 ------------------");
        this.userService = userService;
    }

    public void sayHello() {
        System.out.println("Do User Controller.");
        System.out.println();
        userService.doService();
    }
}

运行结果为:
在这里插入图片描述
**原理:**遵循 Java 的规范。
使用 final 关键字的用法只有两种:

  • 使用时直接赋值
  • 构造方法赋值

Ⅱ 注入的对象不会被修改

构造方法只会在对象创建的时候执行一次,它不会像 Setter 注入一样,执行多次,所以,不存在注入对象被修改的情况。

Ⅲ 完全初始化

依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类创建之初就会执行的方法。

Ⅳ 通用性更好

通用性更好,因为构造方法是 Java(JDK)支持【最底层的框架】,所以更换任何的框架,它都是适用的。

4、三种注入优缺点分析

  • 属性注入主要的优点就是简洁;缺点就是只适用于 IoC 容器,其他容器不通用,同时属性注入不能注入 final 修饰的属性,其实属性注入还有一个不能算缺点的缺点:它容易违背单一设计原则。
  • Setter 注入是在属性注入上做出了优化,它不会违背单一设计原则;但它同样不能注入 final 修饰的属性,同时也因为 setter 方法可能被多次调用,所以可能会有 注入对象被修改的风险
  • 构造方法注入 是目前 Spring 推荐的注⼊⽅式,它实际上满足了属性注入和 Setter注入的缺点,构造方法注入可以注入 final 修饰的属性注入的对象不会被修改通用性好,保证注入前,完全初始化要注入的类;但它的不足之处在于:当同时注入多个类的时候,就需要考虑是否符合程序的单⼀原则的设计模式

5、@Resource:另⼀种注⼊关键字

其实除了 @Autowired,还有一种注入关键字,也可以实现对象注入,即 @Resource。

@Controller
public class UserController2 {
//     // 1.属性注入
//    @Autowired
//    @Resource
//    private UserService userService;

//    // 2.Setter注入
//    private UserService userService;
//
//    @Resource
//    public void setUserService(UserService userService) {
//        this.userService = userService;
//    }

    // 3.构造方法注入【不支持】
    private UserService userService;

    @Resource
    public UserController2(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        System.out.println("Do user controller 2.");
        System.out.println();
        userService.doService();
    }
}

这里会有一种问题:
使用 @Resource 进行属性注入 以及 Setter 注入的时候,都和 @Autowired 一样,可以成功注入;但在进行 构造方法注入 的时候,就会报错,如图:
在这里插入图片描述
@Resource 注解,不能使用在构造方法上

① @Autowired 和 @Resource 的区别

  1. @Autowired 支持构造方法注入,@Resource不支持;
  2. 两者的参数不同:
    在这里插入图片描述

6、同⼀类型多个 @Bean 报错

① 问题

我们刚刚提到 @Autowired 和 @Resource 两者的参数不同。我们先来看一个例子:

@Component
public class UserBeans {
    @Bean(name = "user_user1") // 方法注解
    public User user1(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("UserBeans: 张三");
        user.setAge(18);
        return user;
    }

    @Bean // 方法注解
    public User user2(){
        // 构建数据方法
        User user = new User();
        user.setId(1);
        user.setName("李四");
        user.setAge(18);
        return user;
    }
}
@Controller
public class UserController3 {

    @Autowired // type or name
    private User user;

    public void doController() {
        System.out.println("Do user controller 3.");
        System.out.println();
        System.out.println("user id: " + user.getId() +
                " | name:" + user.getName());
    }
}
public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        UserController3 userController3 =
                context.getBean("userController3", UserController3.class);
        userController3.doController();
    }
}

运行结果为:
在这里插入图片描述
我们之前在学习 @Autowired 的时候,了解过,@Autowired 实际上是以 方法名和 返回类的类型 来进行注入的,先查询返回类的类型,然后再继续查询方法名,二者满足其一即可注入。回到本案例,这里的方法 user1 和 user2 同样都是 返回 User 对象,所以需要继续查询方法名,但是并没有一个叫 user 的方法,无法分辨到底注入那个方法,就会报错。

② 方案1:@Resource注解

这个时候我们想要使用 @Autowired 来解决问题就比较困难了,就需要用到 参数更多的 @Resource,通过更多的参数来获取 Bean,代码如下:

@Controller
public class UserController3 {

//    @Autowired // type or name
    @Resource(name = "user_user1")
    private User user;

    public void doController() {
        System.out.println("Do user controller 3.");
        System.out.println();
        System.out.println("user id: " + user.getId() +
                " | name:" + user.getName());
    }
}

运行结果为:
在这里插入图片描述

③ 方案2:@Qualifier注解

那有人就有疑问了,使用 @Autowired 比较困难,那如果一定要使用 @Autowired 注解呢?实际上还有一种方法可以解决这个问题,这个时候就要请出一个外援 @Qualifier 注解,代码如下:

@Controller
public class UserController3 {

//    @Autowired // type or name
//    @Resource(name = "user_user1")
    @Autowired
    @Qualifier(value = "user_user1")
    private User user;

    public void doController() {
        System.out.println("Do user controller 3.");
        System.out.println();
        System.out.println("user id: " + user.getId() +
                " | name:" + user.getName());
    }
}

运行结果为:
在这里插入图片描述

三、综合练习

在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可。

  1. 创建一个 User 类:
public class User {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  1. 在 Repository 类的一个方法中创建 User 对象,返回一个 User 对象:
@Repository
public class MyRepository {
    public User doRepository() {
        System.out.println("Do my repository.");
        User user = new User();
        user.setId(1);
        user.setName("MyRepository: 王五");
        user.setAge(18);
        return user;
    }
}
  1. 在 Service 类中通过构造方法注入的方式获取到 Repository 类,获得 User 对象,并返回:
@Service
public class MyService {
    private MyRepository myRepository;

    @Autowired
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    public User doService() {
        System.out.println("Do my service.");
        System.out.println();
        return myRepository.doRepository();
    }
}
  1. 在 Controller 类中通过构造方法注入的方式获取到 Service 类,获得 User 对象,并返回:
@Controller
public class MyController {
    private MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    public User doController() {
        System.out.println("Do my controller.");
        System.out.println();
        return myService.doService();
    }
}
  1. 在 main ⽅法中获取到 Controller 类,并接收 User 类,打印信息:
public class App {
    public static void main(String[] args) {
        // 1.得到 Spring 上下文对象
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        MyController myController =
                context.getBean("myController", MyController.class);
        User user = myController.doController();
        System.out.println("id: " + user.getId() + " | name: " + user.getName());
    }
}
  1. 运行结果为:
    在这里插入图片描述

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

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

相关文章

栈的实现.

文章目录1.栈的概念及结构2.栈的实现&#xff08;数组实现&#xff09;2.1栈头文件2.2函数实现3.栈的习题3.1有效的括号3.1.1思路分析3.1.2代码实现1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删…

Qt第二十一章:Qt Designer 之 布局

简单场景&#xff1a;页面上放一个QTextEdit控件 预览后发现&#xff0c;拖拽放大窗口&#xff0c;QTextEdit控件不会进行缩放&#xff0c;就像下边自适应缩放 我们看到了QTextEdit控件撑满了整个界面&#xff1a;在控件sizePolicy属性的允许范围中尽可能的撑满界面。 如果换成…

基于h5的风云网球网站的设计

目 录 1绪 论 1 1.1 选题背景及意义 1 1.2 国内外研究现状 2 1.3本章小结 2 2 前端开发及相关技术 4 2.1 HTML5前端开发环境 4 2.2 HTML5前端开发工具 4 2.3 HTML5前端开发相关技术 5 2.3.1 javascript简介 5 2.3.2 javascript基本特点 5 2.3.3 css简介 6 2.3.4 jQuery 7 2.4 本…

Python精髓之括号家族:方括号、花括号和圆括号,你真的会用吗?

Python独一无二的特色除了缩进还有哪些特色呢&#xff1f;大多数的回答一定是 语法简洁、简单易学、代码高效、功能强大四项。那究竟是Python的哪些语言特性使得人们普遍认为Python具有这些特点呢&#xff1f;其实很大程度上&#xff0c;这要归功于列表&#xff08;list&#x…

公众号免费题库使用方法

公众号免费题库使用方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

haoop启动正常,但上不去网页hadoop102:9870

haoop启动正常&#xff0c;但上不去网页hadoop102:9870 症状如下: hadoop启动正常&#xff0c;jps正常 网页上不去 查下cmd&#xff0c;ping不通 解决方法 一、 先查hdfs 命令&#xff1a;vim /opt/module/hadoop-3.1.3/etc/hadoop/hdfs.site.xml 二、查hosts&#xff0c;我就是…

Redis实战篇(五)好友关注

1、点赞 ------------ Set 2、点赞排行 ------SortedSet 3、共同关注 -------set sinter 一、共同关注 Overridepublic Result followCommons(Long id) {// 1.获取当前用户Long userId UserHolder.getUser().getId();String key "follows:" userId;// 2.求交集St…

Gradle (史上最全): 5W字文

Gradle是绕不开的一个构建工具 对于用惯了 maven的人来说&#xff0c; Graddle不好用&#xff0c; 非常不好用&#xff0c; 主要是环境 会遇到各种各样的问题 但是&#xff0c;越来越多的 场景使用到了 Graddle&#xff0c;但是 spring 的源码&#xff0c;使用 Gradle 构建 …

【Fiddler】安卓7.0以上添加Fiddler/Charles证书到系统根证书(模拟器-雷电)

目录 一、安装工具 1、安装open-ssl 2、配置环境变量 3、验证安装 二、Fiddler 1、导出证书 2、转化cer格式变成PEM 3、查看PEM的哈希值 三、Charles 1、导出证书 2、查看PEM的哈希值 四、证书安装到安卓模拟器 &#xff08;雷电&#xff09; 1、使用模拟器的adb命令 2、…

【实战案例】——实战渗透某不法网站

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

RPA-机器人流程自动化

RPA-机器人流程自动化RPA-机器人流程自动化简介RPA是什么&#xff1f;RPA历史上的演变RPA原理RPA特点RPA技术框架及功能1.TagUI2.RPA for Python3.Robot Framework4.Automagica5.Taskt6.OpenRPARPA部署模式1 环境配置的参数调整2 将自动化程序整体打包部署3 版本的管理和控制机…

【微服务】SpringCloud的OpenFeign与Ribbon配置

&#x1f496; Spring家族及微服务系列文章 ✨【微服务】SpringCloud轮询拉取注册表及服务发现源码解析 ✨ 【微服务】SpringCloud微服务续约源码解析 ✨ ✨【微服务】SpringCloud微服务注册源码解析 ✨【微服务】Nacos2.x服务发现&#xff1f;RPC调用&#xff1f;重试机制&…

token的使用

一&#xff1a;什么是token及token的作用&#xff1f; 1.什么是token&#xff1f; Token是首次登录时&#xff0c;由服务器下发&#xff0c;作为客户端进行请求时的一个令牌。当请求后台方法时&#xff0c;用于身份验证 当第一次登录后&#xff0c;服务器生成一个Token便将此…

1013 Battle Over Cities

目录 Input Specification: Output Specification: Sample Input: Sample Output: 一、题目大意 二、思路 三、代码 It is vitally important to have all the cities connected by highways in a war. If a city is occupied by the enemy, all the highways from/to…

138.深度学习分布式计算框架-1

138.1 PaddlePaddle PaddlePaddle是百度开源的一个深度学习平台PaddlePaddle为深度学习研究人员提供了丰富的API&#xff0c;可以轻松地完成神经网络配置&#xff0c;模型训练等任务。官方文档中简易介绍了如何使用框架在 线性回归识别数字图像分类词向量个性化推荐情感分析语…

2.14 分享9个高吸睛小红书首图制作技巧,要认真学哦!【玩赚小红书】

在小红书里&#xff0c;推荐的图片比例是3:4、1:1、4:3。 做图的时候就要提前调整好比例&#xff0c;免得上传被自动裁剪掉重要信息。竖屏最常用&#xff0c;因为比较“霸屏”&#xff0c;展现的信息空间比较大。当然&#xff0c;选哪个比例还是看个人偏好&#xff0c;尽量保持…

2022年C++面试题万余字汇总【面试官常问】

2022年C面试题【常问重点问题】1、请你说说 GET 和 POST 的区别&#xff1f;2、简述一下 C 中的多态?3、说一说进程有多少种状态&#xff0c;如何转换?3、请你说说指针和引用的区别4、简述一下虚函数的实现原理5、说一说 vector 和 list 的区别&#xff0c;分别适用于什么场景…

剪映PC版英文字幕翻译最新方法(中英互译)

原文地址 剪映PC版英文字幕翻译最新方法&#xff08;中英互译&#xff09; – 方包博客 – java|python|前端开发|运维|电商|ui设计剪映PC版英文字幕翻译最新方法&#xff08;中英互译&#xff09;https://www.fang1688.cn/ziyuan/3431.html 我的是剪映 v3.3.0版本。旧版不支持…

Omorn - NJ301-1100 AND NX102-9000 - CIP - UCMM 通讯

目录 Omorn - NJ301-1100 AND NX102-9000 - CIP - UCMM 通讯 测试案例IP 创建变量类型 指令编写加数据测试 Omorn - NJ301-1100 AND NX102-9000 - CIP - CLASS3 通讯 Omorn - NJ301-1100 AND NX102-9000 - CIP - UCMM 通讯 说明&#xff1a; 1、NJ的网络通讯指令要有CIP…

【C语言入门数据结构】顺序表

&#x1f648;个人主页&#xff1a;阿伟t &#x1f449;系列专栏&#xff1a;【C语言–大佬之路】 &#x1f388;今日心语&#xff1a;你所看到的惊艳&#xff0c;都曾被平庸所历练。 前言&#xff1a;继【时间复杂度和空间复杂】度之后&#xff0c;本章我们来介绍数据结构中…