在IOC和DI的场景中,bean这个概念十分重要
下面我简单做一个总结
IOC(控制反转)
要想把某个对象交给IOC容器来管理,需要通过注解来实现
注解分为两类:
- 类注解: @Controller、@Service、@Repository、@Component、@Configuration
- 方法注解: @Bean(需要搭配类注解使用,否则会失效报错)
接下来就是考虑如何从容器中获取对象
通过类对象获取对象
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("hi,UserController...");
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication .class, args);
//从Spring上下⽂中获取对象
UserController userController = context.getBean(UserController.class);
//使⽤对象
userController.sayHi();
}
}
这个场景是通过类名来获取对象,但是如果一个类有多个bean对象呢
我们查看源码可以发现,ApplicationContext获取bean对象的功能是继承了父类BeanFactory的功能,而BeanFactory还有其他获取bean对象的方法,也就解决了上述问题
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
源码中1,2涉及到bean的名称,那么什么是bean的名称呢
bean的命名规范
bean名称以小写字母开头,然后使用驼峰式大小写.
比如
类名:UserController,Bean的名称为:userController
类名:AccountManager,Bean的名称为:accountManager
类名:AccountService,Bean的名称为:accountService
也有⼀些特殊情况,当有多个字符并且第一个和第二个字符都是大写时,将保留原始的大小写.这些规则与java.beans.Introspector.decapitalize(Spring在这里使用)定义的规则相同
比如
类名:UController,Bean的名称为:UController
类名:AManager,Bean的名称为:AManager
一个类多个bean对象
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication .class, args);
//根据bean名称, 从Spring上下⽂中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
重命名bean
对于五大注解来说
通过 value属性设置 @Controller(value = “user”)
对于@bean来说是②通过name属性设置 @Bean(name = {“u1”,“user1”})
@Bean(name = {"u1","user1"})
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
DI(依赖注入)
注入方式
对于依赖注入,spring也有三种方式
- 属性注入
@Autowired
private UserService userService;
- 构造方法注入
@Controller
public class UserController2 {
//注⼊⽅法2: 构造⽅法
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
}
}
如果类只有⼀个构造方法,那么@Autowired注解可以省略;如果类中有多个构造方法,那么需要添加上@Autowired来明确指定到底使用哪个构造方法
3. setter注入
@Controller
public class UserController3 {
//注⼊⽅法3: Setter⽅法注⼊
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
上述代码在一个类中只有一个bean对象的时候,使用@Autowired/没有问题
但是如果存在多个bean对象的时候,就会报错,因为编译器也不知道具体该注入哪一个bean对象
因此,spring提供了以下几种解决办法
注入注解
1.@Primary确定默认的注入对象
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
2.@Qualifier 指定当前要注入的bean对象。在@Qualifier的value属性中,指定注入的bean的名称。@Qualifier注解不能单独使用,必须配合@Autowired使用
@Controller
public class UserController {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
3.@Resource 是按照bean的名称进行注入。通过name属性指定要注入的bean的名称
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
@Autowird与@Resource的区别
• @Autowired是spring框架提供的注解,而@Resource是JDK提供的注解
• @Autowired默认是按照类型注入而@Resource是按照名称注入
bean的作用域
在Spring中支持6中作用域,后4种在Spring MVC环境才生效
- singleton:单例作用域 每个Spring IoC容器内同名称的bean只有⼀个实例(单例)(默认)
- prototype:原型作用域(多例作用域)每次使用该bean时会创建新的实例(非单例)
- request:请求作用域 每个HTTP 请求生命周期内, 创建新的实例(web环境中)
- session:会话作用域 每个HTTP Session生命周期内, 创建新的实例(web环境中)
- Application: 全局作用域 每个ServletContext生命周期内, 创建新的实例(web环境中)
- websocket:HTTP WebSocket 作用域 每个WebSocket生命周期内, 创建新的实例(web环境中)
@Component
public class DogBeanConfig {
@Bean
public Dog dog(){
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog(){
Dog dog = new Dog();
return dog;
}
@Bean
@RequestScope
public Dog requestDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@SessionScope
public Dog sessionDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@ApplicationScope
public Dog applicationDog() {
Dog dog = new Dog();
return dog;
}
}
bean的生命周期
生命周期指的是⼀个对象从诞生到销毁的整个生命过程, 我们把这个过程就叫做⼀个对象的生命周期.
Bean 的生命周期分为以下5个部分:
- 实例化(为Bean分配内存空间)
- 属性赋值(Bean注入和装配, 比如@AutoWired )
- 初始化
a. 执行各种通知, 如 BeanNameAware , BeanFactoryAware ,
ApplicationContextAware 的接口方法.
b. 执行初始化方法
▪ xml定义 init-method
▪ 使用注解的方式@PostConstruct
▪ 执行初始化后置方法( BeanPostProcessor ) - 使用Bean
- 销毁Bean
a. 销毁容器的各种方法, 如 @PreDestroy , DisposableBean 接口方法, destroy method
实例化和属性赋值对应构造方法和setter方法的注入. 初始化和销毁是用户能自定义扩展的两个阶段,可以在实例化之后, 类加载完成之前进行自定义"事件"处理