JavaEE【Spring】:Spring 更简单的读取和存储对象

前言

经过前⾯的学习,我们已经可以实现基本的 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. 运行结果为:
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/WZRbeliever/article/details/127942182