• Spring让读取和存储Bean更加简单(上篇)——使用注解储存Bean对象


    ⭐️前面的话⭐️

    本篇文章将介绍如何使用注解存储Bean,五大类注解,命名规范,方法注解及其重命名。

    📒博客主页:未见花闻的博客主页
    🎉欢迎关注🔎点赞👍收藏⭐️留言📝
    📌本文由未见花闻原创,CSDN首发!
    📆首发时间:🌴2022年7月27日🌴
    ✉️坚持和努力一定能换来诗与远方!
    💭参考书籍:📚《Spring实战》
    💬参考在线编程网站:🌐牛客网🌐力扣
    博主的码云gitee,平常博主写的程序代码都在里面。
    博主的github,平常博主写的程序代码都在里面。
    🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!



    封面区


    1.前言

    使用注解来使用spring,我们来回顾一下,前面我们使用配置文件的方式来储存对象,就像下面这样:
    1
    我们发现这种方式还是挺麻烦的,实际上在Spring储存Bean,往往都是靠注解来实现的,其实不仅只有存储对象使用注解,Spring其他很多功能的配置也是靠注解,等你学到Spring Boot你的感触就会更加的深刻。

    在Spring项目中,使用注解来实现Bean的储存和读取,也是依赖于Maven的,所以我们的第一步还是创建Maven项目,创建Maven项目过程前面已经详细演示了,这里不多赘述。

    2.配置扫描路径

    创建好项目后,我们的第一步就是配置扫描路径,注意这一步骤非常关键,这里错了,后面也会出错。

    我们先在resources目录下创建一个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=""></content:component-scan>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中配置文件中,里面 base-package的值设置为你需要扫描对象的根路径,这个路径从java目录开始,比如我在如图中的beans目录下创建类:
    2
    那么这个配置文件中根路径为com.beans,所以我们将 base-package的值设置为com.beans

    <content:component-scan base-package="com.beans"></content:component-scan>
    
    • 1

    3.使用注解储存Bean对象

    想要使用注解,那得先知道注解,在Spring中有五大类注解和方法注解,分别为:

    1. 类注解:@Controller(控制器)、@Service(服务)、@Repository(仓库)、@Component(组件)、@Configuration(配置)。
    2. 方法注解:@Bean

    3.1使用五大类注解储存Bean

    首先,我们来了解使用五大类注解来储存对象,怎么用的,以@Controller注解为例,我们有如下的代码:

    package com.beans;
    
    import org.springframework.stereotype.Controller;
    
    @Controller
    public class UserController {
        public void sayHi() {
            System.out.println("你好! 注解!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    像这样,在扫描路径下创建类,并在类上加上@Controller注解就OK了,很简单吧。

    我们来试一试看看是否能够从Spring中读取出我们的对象,由于我们还没有介绍如何使用注解注入对象,我们先按照最普通的方式获取。

    import com.beans.UserController;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Main {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
            //获取对象时使用小驼峰形式的类名作为name参数
            UserController userController = (UserController) context.getBean("userController", UserController.class);
            userController.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:
    3
    刚刚前面我们配置了一个扫描路径,我们现在把这个类移动到该路径外,和路径里面一个目录中看看能不能正常运行。

    我们将这个类移动到扫描路径以外,比如就将它移动到com目录,所在目录结构如图:
    1
    我们再来运行程序:
    6
    报错了,看来类不能创建在扫描路径外面,我们再来试试里面,我们在beans目录新建一个目录inner,再将类移动进去,看看能不能运行,目录结构如图:

    7
    来看看运行结果:
    8
    成功了,因此我们创建类时,必须创建在扫描路径里面才能使用注解储存对象,否则是无效的,所以这个扫描路径也叫做根路径。

    设置根路径也是为了提高程序的性能,因为如果不设置根路径,spring就会扫描项目文件中所有的目录,但是不是所有类都需要储存到spring,这样性能就会比较低,设置了根路径,spring就只扫描该根路径下所有的目录,这样就提高了程序的性能。

    最后我们再来试一试其他四个注解可不可以达到同样的目的。
    8

    public class Main {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
            //获取对象时使用小驼峰形式的类名作为name参数
            UserService service = (UserService) context.getBean("userService", UserService.class);
            service.sayHi();
            UserConfiguration configuration = (UserConfiguration) context.getBean("userConfiguration");
            configuration.sayHi();
            UserComponent component = (UserComponent) context.getBean("userComponent");
            component.sayHi();
            UserRepository repository = (UserRepository) context.getBean("userRepository");
            repository.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行程序之后,它们都能达到同样的目的。
    9

    3.3为什么需要五大类注解?

    既然都可以完成同样的工作,那为什么要有五大类注解呢?

    要解释这个问题,就需要了解一些软件工程的知识了,就是一个软件分为以下几层:

    • @Configuration:配置层,完成一些配置工作。
    • @Controller:表示的是业务逻辑层,前端参数校验。
    • @Servie:服务层,组织调用接口与数据组装等,但不是直接去调用。
    • @Repository:持久层,负责与数据库进行交互。

    10
    那么为什么需要怎么多的类注解的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途。

    这就像是车牌号一样,比如看到车牌号前两个字就知道这辆车来自哪里一样,比如湘A,看到就知道它是来自长沙,湘E,看到它就知道这辆车来自邵阳,那么类注解有五个的原因也是类似的,看到@Configuration就表示这个类是做配置相关的,看到@Controller就知道这个类就是做校验相关的,看到@Repository就知道这个类用来调用数据库的。

    简要来说,五大类注解能够提高代码可读性,能让程序员直观地看到当前类的用途。

    五大类注解有没有关系呢?
    在前面分析五大类注解的作用时,其实还少了一位老朋友,那就是@Component(组件)注解,我们试着点进其他四个类注解的源码看看。

    12
    我们发现@Controller(控制器)、@Service(服务)、@Repository(仓库)、@Configuration(配置)四大类注解都有@Component(组件)注解的身影,其实四个类注解都是基于@Component实现的,所以可以理解为 @Component是其他四个注解的“父类”

    3.4有关获取对象参数的命名规则

    我们前面在使用传统方法获取对象时,getBeanbeanName是使用类名的小驼峰形式,这是因为使用注解储存对象时,会将id设置为小驼峰的类名形式,因此可以使用小驼峰类名来获取对象,但是有特例!

    比如,我有一个类,前两个字母大写,如APIController,我们来试一试使用小驼峰类名还能不能获取到对象。

    package com.beans;
    import org.springframework.stereotype.Controller;
    @Controller
    public class APIController {
        public void sayHi() {
            System.out.println("你好!API");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    main方法:

    public class Main2 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
            APIController api = (APIController) context.getBean("aPIController", APIController.class);
            api.sayHi();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们看看能不能成功获取Bean对象。
    13
    程序报错了,说没有找到beanNameaPIController的对象,那这个beanName到底是什么呢?我们试试大驼峰,看能不能获取:

    APIController api = (APIController) context.getBean("APIController", APIController.class);
    
    • 1

    运行结果:
    14
    有意思,获取到了,我们这是瞎猫碰见死耗子了,为了搞清楚原因,我们来翻一翻源码。

    那从哪里找呢?我们是根于对象名称来找到对象的,因此输入对象名参数beanName,我们来试着搜索一下:
    15

    我们发现有AnnotationBeanNameGenerator类与BeanNameGenerator接口,我们试着去AnnotationBeanNameGenerator类去看看。

    16
    当然,在编译器中是由.class文件转换过来的代码给我们看,没有注释,想要看注释可以去gitee或github搜索spring源码,来找到对应的.java文件查看,这里的源码比较简单我就不带着去找了。
    在源码中有这样一个方法,看名字也知道,用来建立默认的BeanName:

        protected String buildDefaultBeanName(BeanDefinition definition) {
            String beanClassName = definition.getBeanClassName();
            Assert.state(beanClassName != null, "No bean class name set");
            String shortClassName = ClassUtils.getShortName(beanClassName);
            return Introspector.decapitalize(shortClassName);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    返回值是Introspector.decapitalize方法的返回值,我们再进入这个方法看看。
    我们发现这个方法所在类来自于jdk:
    17
    这个decapitalize方法内容如下:

        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

    不难分析得到,如果类名长度大于1并且满足第一个与第二个字母为大写,则构造的BeanName为原类名,其他正常情况为小驼峰形式的类名。

    所以这就解释了APIController类的BeanName为什么就是原类名的原因。

    根据原码,我们可以总结BeanName的规范命名规则:

    • 如果类名不存在或类名为空字符串,BeanName为原类名。
    • 如果类名字长度大于1,且第一个与第二个字符为大写,BeanName为原类名。
    • 其他情况,BeanName为原类名的小驼峰形式。

    4.使用方法注解储存Bean对象

    4.1方法注解储存对象的用法

    使用@Bean方法注解储存Bean的方法如下:

    @Component
    public class UserBeans {
        @Bean
        public User user1() {
            User user = new User();
            user.setId(1);
            user.setName("张三");
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    假设我有一个类User,里面有idname,当一个方法返回一个User对象时,我们可以使用方法注解@Bean来将对象储存到Spring,但是单单使用一个@Bean是不能够成功储存对象的,还需要在方法所在类上使用五大类注解才行,比如搭配一个@Component注解。

    获取方法注解储存的对象时,传入的BeanName参数值为方法名,我上面这个实例的方法名为user1,所以获取时,使用user1作为参数来进行获取,不能使用小驼峰类名来获取。

    public class Main3 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
            User user = (User) context.getBean("user1", User.class);
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    22

    4.2@Bean的重命名

    但是实际开发中,像这样返回对象的方法名称往往是getXXX,如果我们直接使用方法名作为BeanName参数获取对象,语法与实现上没有任何问题,但是使用这种名字获取对象很怪啊,为了解决这个问题,我们可以为注入的对象起别名,实际上注解@Bean是可以加参数的,它可以设置为储存的对象重命名,可以设置多个名字。

        @Bean(name = {"userinfo", "userdemo"})
        public User getUser() {
            User user = new User();
            user.setId(2);
            user.setName("李四");
            return user;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果只有一个别名,可以省略大括号,由于@Bean只有一个参数,因此name=可以省略,就像下面这样:

    //只有一个别名
     @Bean("userinfo")
    //name可以省略
     @Bean({"userinfo", "userdemo"})
    
    • 1
    • 2
    • 3
    • 4

    我们获取储存的对象时就能够使用别名来进行获取。

    public class Main4 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
            User userinfo = context.getBean("userinfo", User.class);
            System.out.println(userinfo);
            System.out.println("-------------------------------");
            User userdemo = context.getBean("userdemo", User.class);
            System.out.println(userdemo);
            System.out.println("-------------------------------");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:
    23

    我们再来考虑一个问题,当一个Bean有别名了,那之前那个方法名还能够获取到对象吗?对于这个问题,我们来试一试:

    public class Main5 {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
            User user = context.getBean("getUser", User.class);
            System.out.println(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    运行结果:
    25
    通过实际操作,答案很明显,不可以,那我们可以做如下的总结:
    @Bean命名规则,当没有设置name属性时,那么Bean的默认名称就是方法名,一旦添加了别名name属性后,只能通过重命名的name属性来获取,此时方法名不能获取到Bean了。


    觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!

    1-99

  • 相关阅读:
    [英雄星球六月集训LeetCode解题日报] 第23日 字典树
    反序列化漏洞
    My Seventy-seventh Page - 零钱兑换 - By Nicolas
    springboot整合swagger,postman,接口规范
    什么牌子的电容笔性价比高?电容笔牌子排行
    web-2(httpd2.4)
    Python教程——配置环境,再探IDE
    linux 关闭ssh后命令继续执行
    群接龙大团长有哪些,群接龙大团长如何对接?
    基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(四)
  • 原文地址:https://blog.csdn.net/m0_59139260/article/details/125882423