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


    前言

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

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

    一、存储 Bean 对象

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

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

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

    在 spring-config.xml 添加如下配置:

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    此时我们先使⽤之前读取对象的⽅式来读取上⾯的 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ② @Service(服务存储)

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

    @Service
    public class UserService {
        public void doService() {
            System.out.println("Do user service.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ③ @Repository(仓库存储)

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

    @Repository
    public class UserRepository {
        public void doRepository() {
            System.out.println("Do user repository.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ④ @Component(组件存储)

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

    @Component
    public class UserComponent {
        public void doComponent() {
            System.out.println("Do user component.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ⑤ @Configuration(配置存储)

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

    @Configuration
    public class UserConfiguration {
        public void doConfiguration() {
            System.out.println("Do user configuration.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    ⑥ 小提示

    我们之前提到过:即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    4、方法注解 @Bean

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

    public class UserBeans {
        @Bean // 方法注解
        public User user(){
            // 构建数据方法
            User user = new User();
            user.setId(1);
            user.setName("张三");
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然⽽,当我们写完以上代码,尝试获取 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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

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

    在 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    ② @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    @Component
    public class UserBeans {
        @Bean // 方法注解
        public User user1(){
            // 构建数据方法
            User user = new User();
            user.setId(1);
            user.setName("张三");
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    如果程序正确运行,则说明 @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其结果为:
    在这里插入图片描述
    说明 @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    @Component
    public class StudentBeans {
        @Bean // 方法注解
        public User user1(){
            // 构建数据方法
            User user = new User();
            user.setId(1);
            user.setName("StudentBeans: 小李");
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @Component
    public class UserBeans {
        @Bean // 方法注解
        public User user1(){
            // 构建数据方法
            User user = new User();
            user.setId(1);
            user.setName("UserBeans: 张三");
            user.setAge(18);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    其运行结果为:
    在这里插入图片描述
    这样就会出现 数据覆盖 的问题,这里的 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    此时我们使⽤ 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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果为:
    在这里插入图片描述
    其实除了上述方法,还有其他方法进行重命名:
    实际上这个重命名的 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    Ⅲ 练习

    使用 @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行结果为:
    在这里插入图片描述
    结论使⽤ @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.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    获取 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    ① 练习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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    答案是:可以!

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

    这是因为,Spring 存储 Bean:实际上是以一个 HashMap 的形式存储。
    @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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    ① 优点

    • 符合单一设计原则(一个 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    当当前类有且仅有一个构造方法时,@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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    运行结果为:
    在这里插入图片描述
    **原理:**遵循 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这里会有一种问题:
    使用 @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果为:
    在这里插入图片描述
    我们之前在学习 @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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    ③ 方案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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    三、综合练习

    在 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 运行结果为:
      在这里插入图片描述
  • 相关阅读:
    硬件工程师秋招注意事项
    c++ 静态库,动态库的制作和使用
    单例模式有几种写法?
    Web自动化成长之路:selenium基础操作
    BufferPool缓存机制
    根据文本描述生成图片不是梦!
    第 1 章 概述 习题
    【游戏建模全流程】使用ZBrush制作龙模型
    快上车,LLM专列:想要的资源统统给你准备好了
    nvm 切换、安装 Node.js 版本
  • 原文地址:https://blog.csdn.net/WZRbeliever/article/details/127942182