总结 Spring 中存储 Bean 的相关注解以及这些注解的用法.
目录
1 Bean的存储
1.1 @Controller(控制器存储)
1.2 @Service(服务存储)
1.3 @Repository(仓库存储)
1.4 @Component(组件存储)
1.5 @Configuration(配置存储)
2 为什么要这么多类注解?
3 ⽅法注解@Bean
3.1 ⽅法注解要配合类注解使⽤
3.2 定义多个对象
3.3 Bean传递参数
3.4 重命名Bean
4 扫描路径
5 总结
在了解Bean的存储之前, 需要先知道IOC这个思想, IOC就是将控制权反转, 将创建依赖对象的权利交给Spring, 而不是原来的使用方对象来创建依赖对象了. 那么将创建对象的控制权交给Spring的IOC容器后, 就由IOC容器来创建并管理对象, 这也就是Bean的存储
1 Bean的存储
共有两类注解类型可以实现:
1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2. ⽅法注解:@Bean.
1.1 @Controller(控制器存储)
使⽤@Controller 存储bean的代码如下所⽰:
@Controller //将对象存储在Spring中
public class UserController {
public void doController() {
System.out.println("doController...");
}
}
如何观察这个对象已经存在Spring容器当中了呢?
接下来我们学习如何从Spring容器中获取对象
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
// 获取Spring上下文
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
// 从Spring上下文中获取对象
UserController bean = context.getBean(UserController.class);
// 使用对象
bean.doController();
}
}
观察运⾏结果,发现成功从Spring中获取到Controller对象,并执⾏了Controller的doController方法.
如果把@Controller删掉,再观察运⾏结果
也就是Spring找不到这个对象, 因为没有将这个bean存储在Spring中.
获取bean对象的其他⽅式
上述代码是根据类型来查找对象,如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢? ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext获取bean对象的功能, 是⽗ 类BeanFactory提供的功能
常用的获取bean的方式是通过bean的名称获取, bean的类型获取, bean的名称和类型一起获取.
这其中涉及到了bean的名称来获取对象, 那么bean的名称是什么呢?
Spring bean是Spring框架在运⾏时管理的对象, Spring会给管理的对象起⼀个名字.
⽐如学校管理学⽣, 会给每个学⽣分配⼀个学号, 根据学号, 就可以找到对应的学⽣. Spring也是如此, 给每个对象起⼀个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.
程序开发⼈员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该 bean⽣成唯⼀的名称. 命名约定使⽤Java标准约定作为实例字段名. 也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式⼤⼩写
⽐如
类名:UserController, Bean的名称为:userController
类名:AccountManager,Bean的名称为: accountManager
类名:AccountService, Bean的名称为:accountService
也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的⼤⼩写. 这些规则与java.beans.Introspector.decapitalize (Spring在这⾥使⽤的)定义的规则相同.
⽐如 类名:UController, Bean的名称为:UController
类名:AManager, Bean的名称为: AManager
根据这个命名规则,我们来获取Bean.
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//从Spring上下⽂中获取对象
//根据bean类型,从Spring上下⽂中获取对象
UserController userController1 = context.getBean(UserController.class);
userController1.doController();
//根据bean名称,从Spring上下⽂中获取对象
UserController userController2 = (UserController) context.getBean("userController");
userController2.doController();
//根据bean名称 + 类型,从Spring上下⽂中获取对象
UserController userController3 = context.getBean("userController", UserController.class);
userController3.doController();
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
地址⼀样, 说明对象是同一个.
1.2 @Service(服务存储)
使⽤@Service 存储bean的代码如下所⽰:
@Service
public class UserService {
public void doService() {
System.out.println("do Service...");
}
}
读取bean的代码:
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
UserService userService = context.getBean(UserService.class);
userService.doService();
}
}
1.3 @Repository(仓库存储)
使⽤ @Repository 存储bean的代码如下所⽰:
@Repository
public class UserRepository {
public void doRepository() {
System.out.println("do Repository...");
}
}
读取bean的代码和上面的基本差不多.
1.4 @Component(组件存储)
使⽤@Component 存储bean的代码如下所⽰:
@Component
public class UserComponent {
public void doUserComponent() {
System.out.println("do UserComponent....");
}
}
读取bean的代码和上面的基本差不多.
1.5 @Configuration(配置存储)
使⽤@Configuration 存储bean的代码如下所⽰:
@Configuration
public class UserConfig {
public void doUserConfig() {
System.out.println("do UserConfig...");
}
}
读取bean的代码和上面的基本差不多.
2 为什么要这么多类注解?
这个是和应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.
• @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.
• @Servie:业务逻辑层,处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层.负责数据访问操作
• @Configuration:配置层.处理项⽬中的⼀些配置信息.
这和每个省/市都有⾃⼰的⻋牌号是⼀样的. ⻋牌号都是唯⼀的,标识⼀个⻋辆的.但是为什么还需要设置不同的⻋牌开头呢. ⽐如陕西的⻋牌号就是:陕X:XXXXXX,北京的⻋牌号:京X:XXXXXX,甚⾄⼀个省不同的县区也 是不同的,⽐如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样. 这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地.
类注解之间的关系
查看 @Controller / @Service / @Repository / @Configuration 等注解源码的发现:
@Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持 久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择.
⽐如杯⼦有喝⽔杯,刷⽛杯等,但是我们更倾向于在⽇常喝⽔时使⽤⽔杯,洗漱时使⽤刷⽛杯.
以上介绍的五大注解只能加在类上, 并且只能加在自己的代码上. 如果你引入了一个第三方的jar包, 并且也希望交给Spring管理, 是没有办法加五大注解的,
3 ⽅法注解@Bean
类注解是添加到某个类上的,但是存在两个问题:
1. 使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类,需要多个对象,⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解@Bean
我们先来看看⽅法注解如何使⽤:
// 这个是@Data是lombok插件的注解
@Data
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
public class BeanConfig {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
然⽽,当我们写完以上代码,尝试获取bean对象中的user时却发现,根本获取不到:
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//@Bean演示
UserInfo userInfo = (UserInfo) context.getBean(UserInfo.class);
System.out.println(userInfo);
}
}
这是为什么呢?
3.1 ⽅法注解要配合类注解使⽤
在Spring框架的设计中,⽅法注解 @Bean要配合类注解才能将对象正常的存储到Spring容器中, 如下代码所⽰:
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
再次运行代码, 就没有报错了.
3.2 定义多个对象
对于同⼀个类,如何定义多个对象呢?
⽐ 如多数据源的场景,类是同⼀个,但是配置不同,指向不同的数据源
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setName("lisi");
userInfo.setAge(11);
return userInfo;
}
}
定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象呢?
运行结果:
报错信息显⽰:期望只有⼀个匹配,结果发现了两个,userInfo,userInfo2.
从报错信息中,可以看出来, @Bean注解的bean,bean的名称就是它的⽅法名.
接下来我们根据名称来获取bean对象
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//@Bean演示
UserInfo userInfo = (UserInfo) context.getBean("userInfo");
System.out.println(userInfo);
UserInfo userInfo2 = (UserInfo) context.getBean("userInfo2");
System.out.println(userInfo2);
}
}
3.3 Bean传递参数
3.4 重命名Bean
可以通过设置name属性给Bean对象进⾏重命名操作,如下代码所⽰:
@Bean(name = {"u1", "userInfo"})
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
此时我们使⽤u1就可以获取到User对象了,如下代码所⽰:
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//@Bean演示
UserInfo userInfo = (UserInfo) context.getBean("u1");
System.out.println(userInfo);
}
}
name={} 可以省略,如下代码所⽰:
@Bean({"u1", "userInfo"})
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
只有⼀个名称时,{}也可以省略,如:
@Bean("u1")
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
4 扫描路径
bean想要生效, 就要能够被Spring扫描到.
下⾯我们通过修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:
在运行代码:
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//@Bean演示
UserInfo userInfo = (UserInfo) context.getBean("u1");
System.out.println(userInfo);
}
}
解释:没有bean的名称为u1.
为什么没有找到bean对象呢? 使⽤五⼤注解声明的bean,要想⽣效,还需要配置扫描路径,让Spring扫描到这些注解 也就是通过 @ComponentScan 来配置扫描路径.
@ComponentScan({"org.example"})
@SpringBootApplication
public class IocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
//@Bean演示
UserInfo userInfo = (UserInfo) context.getBean("u1");
System.out.println(userInfo);
}
}
运行结果
{} ⾥可以配置多个包路径 这种做法仅做了解,不做推荐使⽤
那为什么前⾯没有配置@ComponentScan注解也可以呢? @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中了
默认扫描的范围是SpringBoot启动类所在包及其⼦包
我们通常的做法是把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到.
5 总结
Bean的存是通过五大注解和@Bean实现的, 使用五大注解和@Bean时, Spring会给一个默认的名称.
五大注解: BeanName是类名的小驼峰表示法, 如果前两个字母是大写, BeanName为类名.
@Bean: BeanName是方法名.
那如何修改BeanName呢?
UserService userService = (UserService) context.getBean("uuu"); userService.doService();
UserInfo userInfo = (UserInfo) context.getBean("uu"); System.out.println(userInfo);