文章目录
- 1、@Bean方法注解简介
- 2、@Bean注解重命名
- 3、对象装配(获取Bean对象)
- 3.1 对象装配之属性注入
- 3.2 对象装配之Set 注入
- 3.3 对象装配之构造方法注入
- 4、@Resource VS @Autowired
- 5、Bean对象的作用域
- 5.1 验证Bean对象的默认作用域
- 5.2 Bean对象的六大作用域详解
- 5.3 @Scope注解设置Bean对象作用域
- 6、Spring执行流程
- 7、Bean生命周期
1、@Bean方法注解简介
上一节我们介绍了五大类注解,这一节介绍方法注解@Bean,@Bean作用的对象是方法,该注解需要搭配五大类注解同时进行使用,因为类方法的数量远远大于类的数量,如果使用@Bean注解标记方法的类没有被标记,那么Spring Boot项目在启动时需要遍历所有的类的所有方法,开销无疑是巨大的,但如果只遍历用五大类注解标记的类的方法,无疑大大减小了遍历范围
2、@Bean注解重命名
@Bean 类注解默认情况下,Bean name = 方法名,但是方法名是非常容易重复的,很可能在两个类中有两个相同的方法,它们通过方法类注解返回同一个类的对象,若这两个对象内部属性不相同,则可能出现误调的情况(本想调用A类的student方法返回名为张三的学生的,结果调用成B类的student方法返回了一个名为李四的学生)
并且这种错误并不会引起报错,一旦出现错误非常难以排查问题,而针对上述问题可以通过给@Bean设置name属性获取对象
@Controller
public class StudentBeans {
@Bean(name = {"s1", "s2"})
public Student student() {
// 伪代码构建对象
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
stu.setAge(18);
return stu;
}
}
注:当给一个@Bean设置了name属性后,就无法使用方法名获取Bean对象了,只能通过设置的名称获取
3、对象装配(获取Bean对象)
获取Bean对象也叫做对象装配,就是把注入到Spring Boot中的某个对象取出来取出来放到指定类中,这种方法也叫做对象注入,对象装配的方法有三种,接下来将详细介绍
3.1 对象装配之属性注入
属性注入是使用@Autowired 来实现的,将Service类注入到Controller类中
- 优点:使用简单
- 缺点:
- 1、功能性问题: 无法注入一个不可变对象(final对象)
- 2、通用型问题:只能适应于IoC容器
- 3、设计原则问题:更容易违背单一设计原则
属性注入实例: 将StudentService对象注入到StudentController类中
package com.demo.controller;
import com.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
@Autowired
private StudentService studentService;
public void sayHi() {
studentService.sayHi();
}
}
package com.demo.service;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void sayHi() {
System.out.println("StudentService sayHi");
}
}
接下来来介绍属性注入的缺点:
1、功能性问题:无法注入final对象。首先这个应该是jdk的问题而并非spring的问题,final修饰的变量不可被改变,一旦获取了初始值就不能重新被赋值,如果在类中使用final属性的成员,要么直接赋值,要么在构造函数中赋值
private StudentService studentService; // 添加final关键字,无法赋值
private final int number; // 报错:必须要直接进行赋值,或者写一个构造函数,在构造函数中赋值
2、通用性问题:只能适用于IoC容器
3、设计原则问题:更容易违背单一设计原则
3.2 对象装配之Set 注入
完全符合单一设计原则,每一个Setter方法只针对一个对象,但是它的缺点也很明显,不能注入不可变对象,注入的对象可以被任意修改
使用方法:
// 2、Setter注入
private StudentService studentService;
@Autowired
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
3.3 对象装配之构造方法注入
Spring Boot 官方推荐的用法,如果当前类中只有一个构造方法,那么@Autowired注解可以省略
基本使用方法
// 3、构造方法注入
private StudentService studentService;
public StudentController(StudentService studentService) {
this.studentService = studentService;
}
构造方法注入的优点
1.可注入不可变对象,不可变对象可以在构造函数中初始化
2.注入对象不会被修改:构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况
3.注入对象会被完全初始化
4.通用性更好:构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
4、@Resource VS @Autowired
功能:两者都是用来实现依赖注入的,功能非常相近
@Resource 和 @Autowired 的区别
- 出身不同:@Autowired来自于Spring,而@Resource来自于JDK的注解
- 使用时设置的参数不同:相比于@Autowired来说,@Resource支持更多的参数设置,例如name设置,根据名称获取Bean
- @Autowired可以用于Setter注入,构造函数注入属性注入,而@Resource只能用于Setter注入和属性注入,不能用于构造函数注入
由于@Autowired支持的参数设置很少,所以产生了@Qualifier
注解来扩充@Autowired 组件的功能
场景:使用@Bean注解向Spring中注入两个Student对象,当我们想要从Spring中取出对象,注入到其他对象中时,Spring就会不确定到底使用哪一个对象
package com.demo.componect;
import com.demo.model.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Controller
public class StudentBeans {
@Bean(name = {"s1", "s2"})
public Student student1() {
// 伪代码构建对象
Student stu = new Student();
stu.setId(1);
stu.setName("张三");
stu.setAge(18);
return stu;
}
@Bean
public Student student2() {
Student stu = new Student();
stu.setId(2);
stu.setName("李四");
stu.setAge(20);
return stu;
}
}
@Resource 解决方式:
可以根据@Bean注解时设置的名称,来确定应该调用哪个@Bean注解标记的方法来获取指定对象
@Resource(name = "s1")
private Student student;
@Autowired 解决方式:
-
可以根据 注入方法名 = 对象名称的方式来获取对象(不推荐)
@Autowired private Student student2;
-
使用@Qualifier注解可以对获取对象的方法进行筛选
@Autowired @Qualifier("student2") private Student student;
5、Bean对象的作用域
作用域是Bean对象在整个Spring框架(项目)中的某种行为模式,Bean对象默认情况下是单例状态(singleton),也就是所有人使用的都是同一个Bean对象,所以Spring中的Bean的作用域默认也是单例模式(singleton)
5.1 验证Bean对象的默认作用域
示例:
@Controller
public class UserController {
@Autowired
private User user1;
public void getUser() {
System.out.println("User1:" + user1);
User u = user1;
u.setName("李四");
System.out.println("u:" + u);
}
}
@Controller
public class UserAdviceController {
@Resource
private User user1;
public void getUser() {
System.out.println("王五 | user1: " + user1);
}
UserController uc = context.getBean("userController", UserController.class);
uc.getUser();
UserAdviceController ua = context.getBean("userAdviceController", UserAdviceController.class);
ua.getUser();
输出:
User1:User(id=1, name=张三, password=15157722660)
u:User(id=1, name=李四, password=15157722660)
王武 | user1: User(id=1, name=李四, password=15157722660)
可以看到不管是通过创建局部变量,还是重新装配,获取到的都是同一个Bean对象,这说明Bean对象全局只有一份,这个对象是一个单例
5.2 Bean对象的六大作用域详解
Spring容器在初始化Bean实例时,同时会指定该实例的作用域,Spring有6种作用域,最后四种是基于Spring MVC生效的:
- 1、singleton : 单例作用域(Spring默认选择该作用域),IOC容器中只存在一个实例
- 使用场景:通常无状态Bean使用 该作用域,即Bean对象的属性状态无需更新(对象不会被改变)
- 2、prototype : 原型作用域(多例作用域),每次对该作用域下的Bean的请求都会创建新的实例
- 使用场景:通常有状态的Bean使用该作用域
- 3、request:请求作用域,每次Http请求会创建新的Bean实例,类似于prototype
- 使用场景:一次http的请求和响应的共享Bean(限定SpringMVC中使用)
- 4、session:会话作用域,在一个http session中,定义一个Bean实例
- 使用场景:用户回话的共享Bean,比如:记录一个用户的登录信息(限定SpringMVC种使用)
- 5、application:全局作用域,在一个http servlet Context中,定义一个Bean实例。即一个context对象使用getBean获取类得到的是同一个实例
- 使用场景:Web应用的上下文信息,比如:记录一个应用的共享信息(限定SpringMVC中使用)
- 6、websocket:在一个HTTP WebSocket的生命周期中,定义一个Bean实例
- 使用场景:WebSocket的每次会话中,保存一个Map结构的头信息,包裹客户端消息头。第一次初始化后,直到WebSocket结束都是用的同一个Bean(限定Spring WebSocket中使用 )
单例作用域(singleton) vs 全局作用域(application)
- singleton是Spring Core的作用域; application 是Spring Web中的作用域
- singleton作用域IoC容器,而application作用域Servlet容器
5.3 @Scope注解设置Bean对象作用域
@Scope标签可以用来声明Bean的作用域,比如设置Bean的作用域,如下代码所示:
@Data
@Controller
public class UserBeans {
// 方法1:
// @Scope("prototype")
// 方法2:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Bean
public User user1() {
User user = new User();
user.setName("张三");
user.setId(1);
user.setPassword("15157722660");
return user;
}
}
6、Spring执行流程
7、Bean生命周期
所谓生命周期指的是一个对象从诞生到销毁的整个生命过程,我们将这一过程叫做一个Bean对象的生命周期
-
实例化Bean,将字节码转换成内存中的对象(即加载,为Bean分配内存空间)
-
设置属性(Bena注入和装配)
-
Bean初始化:实现了各种Aware通知方法,如@BeanPostProcessor(注解初始化前置方法),@PostConstruct xml初始化方法(依赖注入后被执行)
-
使用Bean对象
-
销毁Bean对象