Spring让读取和存储Bean更加简单(上篇)——使用注解储存Bean对象
⭐️前面的话⭐️
本篇文章将介绍如何使用注解存储Bean,五大类注解,命名规范,方法注解及其重命名。
📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2022年8月31日🌴
✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《Spring实战》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!
1.前言
使用注解来使用spring,我们来回顾一下,前面我们使用配置文件的方式来储存对象,就像下面这样:
我们发现这种方式还是挺麻烦的,实际上在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
目录下创建类:
那么这个配置文件中根路径为com.beans
,所以我们将base-package
的值设置为com.beans
。
<content:component-scan base-package="com.beans"></content:component-scan>
3.使用注解储存Bean对象
想要使用注解,那得先知道注解,在Spring中有五大类注解和方法注解,分别为:
- 类注解:@Controller(控制器)、@Service(服务)、@Repository(仓库)、@Component(组件)、@Configuration(配置)。
- 方法注解:@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();
}
}
运行结果:
刚刚前面我们配置了一个扫描路径,我们现在把这个类移动到该路径外,和路径里面一个目录中看看能不能正常运行。
我们将这个类移动到扫描路径以外,比如就将它移动到com
目录,所在目录结构如图:
我们再来运行程序:
报错了,看来类不能创建在扫描路径外面,我们再来试试里面,我们在beans
目录新建一个目录inner
,再将类移动进去,看看能不能运行,目录结构如图:
来看看运行结果:
成功了,因此我们创建类时,必须创建在扫描路径里面才能使用注解储存对象,否则是无效的,所以这个扫描路径也叫做根路径。
设置根路径也是为了提高程序的性能,因为如果不设置根路径,spring就会扫描项目文件中所有的目录,但是不是所有类都需要储存到spring,这样性能就会比较低,设置了根路径,spring就只扫描该根路径下所有的目录,这样就提高了程序的性能。
最后我们再来试一试其他四个注解可不可以达到同样的目的。
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();
}
}
运行程序之后,它们都能达到同样的目的。
3.3为什么需要五大类注解?
既然都可以完成同样的工作,那为什么要有五大类注解呢?
要解释这个问题,就需要了解一些软件工程的知识了,就是一个软件分为以下几层:
- @Configuration:配置层,完成一些配置工作。
- @Controller:表示的是业务逻辑层,前端参数校验。
- @Servie:服务层,组织调用接口与数据组装等,但不是直接去调用。
- @Repository:持久层,负责与数据库进行交互。
那么为什么需要怎么多的类注解的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途。
这就像是车牌号一样,比如看到车牌号前两个字就知道这辆车来自哪里一样,比如湘A,看到就知道它是来自长沙,湘E,看到它就知道这辆车来自邵阳,那么类注解有五个的原因也是类似的,看到@Configuration就表示这个类是做配置相关的,看到@Controller就知道这个类就是做校验相关的,看到@Repository就知道这个类用来调用数据库的。
简要来说,五大类注解能够提高代码可读性,能让程序员直观地看到当前类的用途。
五大类注解有没有关系呢?
在前面分析五大类注解的作用时,其实还少了一位老朋友,那就是@Component(组件)注解,我们试着点进其他四个类注解的源码看看。
我们发现@Controller(控制器)、@Service(服务)、@Repository(仓库)、@Configuration(配置)四大类注解都有@Component(组件)注解的身影,其实四个类注解都是基于@Component实现的,所以可以理解为 @Component是其他四个注解的“父类”。
3.4有关获取对象参数的命名规则
我们前面在使用传统方法获取对象时,getBean
的beanName
是使用类名的小驼峰形式,这是因为使用注解储存对象时,会将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对象。
程序报错了,说没有找到beanName
为aPIController
的对象,那这个beanName
到底是什么呢?我们试试大驼峰,看能不能获取:
APIController api = (APIController) context.getBean("APIController", APIController.class);
运行结果:
有意思,获取到了,我们这是瞎猫碰见死耗子了,为了搞清楚原因,我们来翻一翻源码。
那从哪里找呢?我们是根于对象名称来找到对象的,因此输入对象名参数beanName
,我们来试着搜索一下:
我们发现有AnnotationBeanNameGenerator类与BeanNameGenerator接口,我们试着去AnnotationBeanNameGenerator类去看看。
当然,在编译器中是由.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:
这个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
,里面有id
与name
,当一个方法返回一个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);
}
}
运行结果:
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("-------------------------------");
}
}
运行结果:
我们再来考虑一个问题,当一个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);
}
}
运行结果:
通过实际操作,答案很明显,不可以,那我们可以做如下的总结:
@Bean命名规则,当没有设置name
属性时,那么Bean的默认名称就是方法名,一旦添加了别名name
属性后,只能通过重命名的name
属性来获取,此时方法名不能获取到Bean了。
- 点赞
- 收藏
- 关注作者
评论(0)