• Spring 更简单的读取和存储对象


    引言

    在上一章节中,我们通过设置配置文件的方式简单实现了 Spring 中 Bean 对象的存取,但是相比之下,每次进行对象的注册和获取还是相对麻烦的,那么有没有更简单优雅的方式呢?答案当然是有的:在 Spring 中想要更简单的存储和读取对象的核心是使用注解,本章我们就详细介绍一下在 Spring 中如何使用注解更简单的存取 Bean。

    一、存储 Bean 对象

    1、配置扫描路径

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

    
    <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.java.demo">content:component-scan>
        
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、类注解存储 Bean

    (1)Spring 提供的五大类注解

    想要将对象存储在 Spring 中,我们可以采用类注解,Spring 中有五大类注解,虽然这些注解都可以将 对象存储到 Spring 容器中,但是它们存在语义上的差别:

    1. @Controller: 这个注解通常用于标记控制器类,通常用于处理Web应用程序中的请求和响应。

    2. @Service: 这个注解通常用于标记服务类,它表示这个类是一个服务组件,用于执行业务逻辑。

    3. @Repository: 这个注解通常用于标记数据访问层(DAO)的类,表示这个类用于数据库访问。

    4. @Configuration: 这个注解用于标记配置类,通常充当Spring应用程序上下文的配置源。通常与@Bean注解一起使用。

    5. @Component: 这是一个通用的注解,表示一个类是Spring组件。它可以用于任何类,但通常用于没有明确角色的类。其他更具体的注解(如@Controller、@Service、@Repository)实际上是@Component的子类。

    其实上面的五大注解,关联着Java项目中的不同层级。对于一个工程化的项目,通常将其划分为不同的层级,进行更精细化的管理,Java中对项目的标准分层规范如下:

    (2)使用类注解存储 Bean

    使用注解存储 Bean 的方法很简单,只需要在相应的类上添加对应的注解即可。例如将项目中一个表示服务的类添加到 Spring 中:

    @Service
    public class UserService {
        public void init(){
            System.out.println("this is UserService");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    (3)类注解存储 Bean 默认命名规则

    当我们使用类注解存储 Bean 时,如果我们想要通过 依赖查找 的方式获取到 Bean 对象,那么在 getBean 方法中如何写该 Bean 的名字呢?(在不使用@Service(“name”)的前提下)想要解答这个问题,我们就要取探讨一下在使用类注解存储 Bean 时,它的默认命名规则。

    我们通过在 IDEA 中搜索 ““beanName”,可以顺藤摸瓜找到 java.beans 包下的 decapitalize 方法,也就是说 Spring 中使用类注解对 Bean 对象的默认命名使用的是 JDK 中的命名方式:

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

    通过分析上述源代码,我们可以得出结论:

    被注解标注的类,如果类的第一个字母是大写,第二个字母是小写,那么 Bean 的名称就是首字母小写;如果类的第一个字母和第二个字母都是大写,则 Bean 的名称就是原类名。

    2、方法注解存储 Bean

    方法注解的形式为:@Bean,表示将该方法返回的对象存储到 IoC容器中。

    使用示例:

    @Configuration
    public class ConfigInfo {
        @Bean
        public Config config() {
            // 这里是伪代码
            // ...
            // 获取到 Config 的实例
            return config;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用方法注解存储 Bean 注意事项:

    1. @Bean 注解必须 配合 五大类注解一起使用。
    2. 使用@Bean 注解存储的 Bean 的默认命名为方法名。

    @Bean 注解重命名

    • @Bean(name=“xxx”),简写为@Bean(“xxx”)
    • @Bean(value=“xxx”),简写为@Bean(“xxx”)
    • @Bean(name={“xx”,“xxx”}),简写为@Bean(“xx”,“xxx”)
    • @Bean(value={“xx”,“xxx”}),简写为@Bean(“xx”,“xxx”)

    重命名注意事项:当使用 @Bean 重命名后,那么默认使用方法名获取 Bean 对象的方式就不能使用了。

    二、获取 Bean 对象

    使用注解获取 Bean 对象,是把对象取出来放到某个类中,因此也叫做 对象装配,有时候也叫 对象注入

    1、属性注入

    假如现在我在 Spring 容器中储存了一个 User 类型的 Bean,那么我们可以使用 @Autowrite 进行属性注入:

    public class UserService {
        @Autowired
        private User user;
        public void init(){
            System.out.println(user.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    属性注入优点:

    属性注入最大的优点就是使用简单。

    属性注入缺点:

    1. 属性注入无法注入 final 修饰的变量。这是由 java 语法特性决定的,在 Java 中 final 修饰的变量要么直接赋值,要么通过构造方法进行赋值。

    2. 属性注入只适用于 IoC 容器。如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了。

    3. 属性注入更容易违反单一设计原则。正是因为使用简单,所以滥用它的概率也越大,所以出现违背单一职责原则的概率也越大。

    2、Setter 注入

    假如现在我在 Spring 容器中储存了一个 User 类型的 Bean,那么我们可以使用 @Autowrite 进行 Setter 注入:

    @Service
    public class UserService {
    	// Setter 注入
        private User user;
    
        @Autowired
        public void setUser(User user) {
            this.user = user;
        }
        public void init(){
            System.out.println(user.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Setter 注入优点:

    对于 Setter 本身来说,通常每一个 Setter 只针对一个对象,因此更符合单一设计原则。

    Setter 注入缺点:

    1. 由于 Java 语法限制,Setter 注入仍然无法注入一个 final 修饰的变量。
    2. Setter 注入的对象可能被修改。因为 setter 本身是一个方法,也就意味着可能在某些地方被多次调用和修改。

    3、构造方法注入

    假如现在我在 Spring 容器中储存了一个 User 类型的 Bean,那么我们可以使用 @Autowrite 进行 构造方法注入:

    @Service
    public class UserService {
        private User user;
    
        @Autowired
        public UserService(User user) {
            this.user = user;
        }
    
        public void init(){
            System.out.println(user.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    :如果当前类中只有一个构造方法时,@Autowrite 可以省略。

    构造方法注入优点:

    构造方法注入是 Spring 4 之后推荐的注入方式。它主要有如下优点:

    1. 构造方法注入可以注入一个 final 修饰的变量。因为使用构造方法给 final 变量赋值符合 Java 语法规则。
    2. 构造方法注入的对象不会被修改。因为构造方法只会加载一次。
    3. 构造方法注入可以保证注入对象完全初始化。因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化。
    4. 构造方法的通用性更好。构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架。

    4、来自JDK的注入关键字:@Resource

    使用示例:

    @Service
    public class UserService {
        @Resource
        private User user1;
    
        public void init(){
            System.out.println(user1.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    @Resource 和 @Autowrite 的区别

    1. 出身不同:@Autowired 来自于 Spring,而@Resource 来自于 JDK 的注解。在Java程序中@Resource是先于@Autowrite执行的,因此在专业版的IDEA中,对于@Autowrite来说是不能识别当前类是否存在,因此会报错。
    2. 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
    3. @Autowired 可用于 Setter 注入、构造函数注入和属性注入,而@Resource 只能用于 Setter 注入和属性注入,不能用于构造函数注入。

    特别注意:Spring 框架通过扫描和解析注解来识别和管理Bean对象。当使用@Autowired或@Resource注解进行属性注入时,Spring会在容器中查找匹配的Bean,并将其注入到对应的属性上。如果被注入的属性所属的类没有被Spring容器管理,那么就找不到Spring管理的类,自然类中属性的注入操作就无法执行,可能会导致NullPointerException等异常。

    5、如何正确获取 Spring 容器中同类型的 Bean 对象?

    在上一章节中我们提到,依赖查找获取 Bean 是依赖于 Bean 的名称的。那么 @Autowrite 的依赖注入流程又是怎样的呢?这里我们先给出结论:

    @Autowrite 依赖注入,先根据属性的类型从容器中获取 Bean 对象,如果只获取到一个 Bean 对象,那么直接将此对象注入到当前属性上;如果获取到多个 Bean 对象,会将当前属性名和 Bean 对象名进行匹配,注入名称匹配的 Bean 对象。

    根据上述结论,进而引出了另一个问题:如何正确获取 Spring 容器中同类型的 Bean 对象?

    为了更好的演示,我们假设储存了两个 User 对象:

    @Component
    public class Users {
        @Bean("user1")
        public User user(){
            User user = new User();
            user.setName("张三");
            return user;
        }
        @Bean("user2")
        public User user2(){
            User user = new User();
            user.setName("李四");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    当然细心的同学经在上述结论中得到了一种答案:将属性的名字和欲获取的 Bean 名字对应。

    @Service
    public class UserService {
        @Autowired
        private User user1;
        public void init(){
            System.out.println(user1.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面是一种不错的解决方案,这里再给大家介绍另一种方法:@Autowrite 搭配 @Qualifier

    @Service
    public class UserService {
        @Autowired
        @Qualifier("user2")
        private User user;
        public void init(){
            System.out.println(user.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上面我们了解了@Resource可以设置参数,其中有一个参数就是设置获取 Bean 名称,因此可以使用 @Resource设置name属性(仅适用于属性注入和Setter方法注入)

    public class UserService {
    	@Resource(name = "user1")
        private User user;
        public void init(){
            System.out.println(user.toString());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    Python爬虫讲解(超详细)
    代码随想录刷题记录 3 - 哈希
    PMP每日一练 | 考试不迷路-9.1(包含敏捷+多选)
    【从入门到起飞】JavaSE—File的使用,构造方法,成员方法
    【校招VIP】前端HTML考察之cavas、svg
    MySQL 数据类型和搜索引擎
    FabFilter Pro-R 混响效果器
    【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)
    世界上最伟大的女程序员
    基于Java+vue前后端分离线上学习网设计实现(源码+lw+部署文档+讲解等)
  • 原文地址:https://blog.csdn.net/LEE180501/article/details/132600093