IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储。
5.1Bean的存储
共有两类注解类型可以实现:
1.类注解:@Controller、@Service、@Repository、@Component、@Configuration.
2.方法注解:@Bean.
5.1.1@Controller(控制器存储)
使⽤@Controller 存储bean
@Controller
public class UserController {
public void sayHi(){
System.out.println("hi,UserController...");
}
}
从Spring容器中获取对象
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//使用对象
userController.sayHi();
}
}
关于上下⽂的概念 。上学时,阅读理解经常会这样问:根据上下⽂,说⼀下你对XX的理解 在计算机领域,上下⽂这个概念,咱们最早是在学习线程时了解到过,⽐如我们应⽤进⾏线程切换的时 候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该 线程⼜得到CPU时间的时候,从上下⽂中拿到线程上次运⾏的信息 这个上下⽂,就是指当前的运⾏环境,也可以看作是⼀个容器,容器⾥存了很多内容,这些内容是当前 运⾏的环境.
观察运⾏结果,发现成功从Spring中获取到Controller对象,并执⾏Controller的sayHi⽅法
删掉@Controller,再观察运⾏结果
报错信息显⽰:找不到类型是:com.example.demo.controller.UserController的bean
5.1.2 Bean命名约定
程序开发⼈员不需要为bean指定名称(BeanId),如果没有显式的提供名称(BeanId),Spring容器将为该 bean⽣成唯⼀的名称.
命名约定使用Java标准约定作为实例字段名.也就是说,bean名称以小写字母开头,然后使用驼峰式大小写
⽐如:
类名:UserController, Bean的名称为:userController
类名:AccountManager,Bean的名称为: accountManager
类名:AccountService, Bean的名称为:accountService
特殊情况,当有多个字符并且第⼀个和第⼆个字符都是⼤写时,将保留原始的大小写
⽐如:
类名:UController, Bean的名称为:UController
类名:AManager,Bean的名称为: AManager
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//使用对象
userController.sayHi();
//根据bean类型,从Spring上下文获取对象
UserController userController1 = context.getBean(UserController.class);
//根据bean名称,从Spring上下文获取对象
UserController userController2 = (UserController) context.getBean("userController");
//根据bean类型+名称,从Spring上下文获取对象
UserController userController3 = context.getBean("userController", UserController.class);
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
运行结果:
获取bean对象,是⽗类BeanFactory提供的功能
ApplicationContext VS BeanFactory(常⻅⾯试题)
• 继承关系和功能方面来说:Spring容器有两个顶级的接⼝:BeanFactory和 ApplicationContext。其中BeanFactory提供了基础的访问容器的能⼒,⽽ ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
• 从性能方面来说:ApplicationContext是⼀次性加载并初始化所有的Bean对象,⽽ BeanFactory 是需要那个才去加载那个,因此更加轻量.(空间换时间)
5.1.3 @Service(服务存储)
使⽤@Service 存储bean的代码:
@Service
public class UserService {
public void sayHi() {
System.out.println("Hi,UserService");
}
}
读取bean:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//从Spring中获取UserService对象
UserService userService = context.getBean(UserService.class);
userService.sayHi();
}
}
观察运⾏结果,发现成功从Spring中获取到UserService对象,并执⾏UserService的sayHi⽅法
把注解@Service删掉,再观察运⾏结果
5.1.4 @Repository(仓库存储)
使用@Repository 存储bean的代码如下所示:
package com.example.demo.repo;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void doRepository() {
System.out.println("de repo...");
}
}
读取bean的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//从Spring中获取UserRepository对象
UserRepository userRepository = context.getBean(UserRepository.class);
userRepository.doRepository();
}
}
观察运⾏结果,发现成功从Spring中获取到UserRepository对象,并执⾏UserRepository的sayHi⽅法
把注解@Repository删掉,再观察运⾏结果
5.1.5 @Component(组件存储)
使用@Component 存储bean的代码如下所示:
@Component
public class UserComponent {
public void doComponent() {
System.out.println("doComponent...");
}
}
读取bean的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//从Spring中获取UserComponent对象
UserComponent userComponent = context.getBean(UserComponent.class);
userComponent.doComponent();
}
}
观察运⾏结果,发现成功从Spring中获取到UserComponent对象,并执⾏UserComponent的sayH方法 。
把注解@Component删掉,再观察运⾏结果
5.1.6 @Configuration(配置存储)
使用@Configuration 存储bean的代码如下所示:
@Configuration
public class UserConfig {
public void doConfig() {
System.out.println("do configuration...");
}
}
读取bean的代码:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
//从Spring中获取UserConfig对象
UserConfig userConfig = context.getBean(UserConfig.class);
userConfig.doConfig();
}
}
观察运⾏结果,发现成功从Spring中获取到UserConfiguration对象,并执⾏UserConfiguration的 sayHi⽅法
把注解@Configuration删掉,再观察运⾏结果
5.2使用多类注解的好处
与应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.
• @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.
• @Servie:业务逻辑层,处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层.负责数据访问操作
• @Configuration:配置层.处理项⽬中的⼀些配置信息.
这和每个省/市都有自己的车牌号是一样的,
车牌号都是唯一的,标识一个车辆的.但是为什么还需要设置不同的车牌开头呢.
比如江苏的车牌号就是:苏X:XXXXXX,北京的车牌号:京X:XXXXXX,甚至一个省不同的县区也
是不同的,比如南京就是,苏A:XXXXX,无锡:苏B:XXXXXX,,苏州E:XXXXXX,一样.
这样做的好处除了可以节约号码之外,更重要的作用是可以直观的标识一辆车的归属地.
程序的应⽤分层,调⽤流程如下:
5.2.1 类注解之间的关系
查看 @Controller @Service @Repository @Configuration 等注解的源码
其实这些注解里面都有一个注解@Component,说明它们本身就是属于@Component」的"子类"
@Component是一个元注解,也就是说可以注解其他类注解,如@Controller,@Service
@Repository等.这些注解被称为@Component的衍生注解.@Contrller,@Service和@Repository用于更具体的用例(分别在控制层,业务逻辑层,持久化层),在开发过程中,如果你要在业务逻辑层使用@Component或@Service,显然@Service是更好的选择。
5.3 ⽅法注解@Bean
类注解是添加到某个类上的,但是存在两个问题:
1.使用外部包里的类,没办法添加类注解
2.一个类,需要多个对象,比如多个数据源
这种场景,我们就需要使用方法注解@Bean
5.3.1 ⽅法注解要配合类注解使⽤
方法注解的使用:
public class BeanConfig {
@Bean
public UserInfo user() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
}
尝试获取bean对象中的UserInfo
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserController userController = context.getBean(UserController.class);
UserInfo userInfo = context.getBean( UserInfo.class);
System.out.println(userInfo);
}
程序报错
原因是方法注解要配合类注解使⽤!
在Spring框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到Spring容器中, 如下代码所示:
@Component
public class BeanConfig {
@Bean
public UserInfo user() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
}
再次运⾏程序结果如下:
5.3.2 定义多个对象
对于同⼀个类,定义多个对象
@Bean的使用如下:
@Component
public class BeanConfig {
@Bean
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo2() {
UserInfo userInfo = new UserInfo();
userInfo.setName("李四");
userInfo.setAge(20);
return userInfo;
}
}
定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象?
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserInfo userInfo = context.getBean( UserInfo.class);
System.out.println(userInfo);
}
}
运⾏结果:
报错信息显⽰:期望只有⼀个匹配,结果发现了两个,userInfo1,userInfo2 从报错信息中,可以看出来, @Bean注解的bean,bean的名称就是它的⽅法名
接下来我们根据名称来获取bean对象
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo userInfo1 = (UserInfo) context.getBean("userInfo");
UserInfo userInfo2 = (UserInfo) context.getBean("userInfo2");
System.out.println(userInfo1);
System.out.println(userInfo2);
}
}
运⾏结果:
5.3.3 重命名Bean
可以通过设置name属性给Bean对象进行重命名操作,如下代码所示:
@Component
public class BeanConfig {
@Bean(name = {"u1", "user1"})
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
}
此时我们使用u1或user1就可以获取到User对象了,如下代码所示:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserInfo userInfo = (UserInfo) context.getBean("u1");
System.out.println(userInfo);
}
}
name={} 可以省略,如下代码所示:
@Component
public class BeanConfig {
@Bean({"u1", "user1"})
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
}
只有⼀个名称时,{}也可以省略,如下代码所示:
@Component
public class BeanConfig {
@Bean("u1")
public UserInfo userInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(18);
return userInfo;
}
}
5.3 扫描路径
Q: 使⽤前⾯学习的四个注解声明的bean,不⼀定会⽣效。原因是bean想要生效,还需要被Spring扫描。
修改项目工程的目录结构,来测试bean对象是否生效:
运⾏代码:
使⽤五⼤注解声明的bean,要想生效,还需要配置扫描路径,让Spring扫描到这些注解 也就是通过 @ComponentScan 来配置扫描路径 。
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//获取Spring上下文对象
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//从Spring上下文中获取对象
UserInfo userInfo = (UserInfo) context.getBean("u1");
System.out.println(userInfo);
}
}
运行结果:
那为什么前⾯没有配置@ComponentScan注解也可以呢? @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注@SpringBootApplication 中了。默认扫描的范围是SpringBoot启动类所在包及其子包。