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

举报
未见花闻 发表于 2022/08/31 21:57:52 2022/08/31
【摘要】 本篇文章将介绍如何使用注解存储Bean,五大类注解,命名规范,方法注解及其重命名。

⭐️前面的话⭐️

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

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2022年8月31日🌴
✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《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>

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

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

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("你好! 注解!");
    }
}

像这样,在扫描路径下创建类,并在类上加上@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();
    }
}

运行结果:
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();
    }
}

运行程序之后,它们都能达到同样的目的。
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");
    }
}

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();
    }
}

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

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

运行结果:
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);
    }

返回值是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并且满足第一个与第二个字母为大写,则构造的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;
    }
}

假设我有一个类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);
    }
}

运行结果:
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;
    }

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

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

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

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("-------------------------------");
    }
}

运行结果:
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);
    }
}

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。