昨天我们详细的学习了控制反转IoC和依赖注入DI,通过Spring容器(也就是IoC容器)完成组件的实例化,从而将组件之前的依赖关系进行了解耦。然而控制反转和依赖注入都是通过Bean实现的,Bean其实本质上就是注册到Spring容器中的对象实例(可以是任何普通的Java对象),在Spring中被称为Bean是用来说明是以此来说明它们被Spring容器管理(整个生命周期的管理,创建、初始化、销毁等阶段)。
了解Spring IoC容器的原理
掌握Bean标签以及属性的使用
熟悉Bean的实例化
掌握Bean的作用域
掌握Bean的装配方式
熟悉Bean的生命周期
我们前俩天的项目和文章有学习到,IoC容器统一创建和管理Bean,并负责解决Bean之间的依赖关系,而不需要应用程序代码去手动创建或查找对象。我们前几天也有学了简单工厂模式,其实在IoC容器中,容器充当了工厂的角色,它负责创建和管理Bean对象,应用程序通过容器来获取Bean,而不需要直接实例化这些对象。
BeanFactory是Spring框架的核心接口之一,它是Spring IoC容器的基础接口。BeanFactory负责管理和提供Bean对象,它定义了一系列操作Bean的方法,包括获取Bean、判断Bean是否存在、获取Bean的类型等。
BeanFactory接口的主要实现类是XmlBeanFactory,它从XML配置文件中读取Bean的定义信息,并根据需要在运行时实例化Bean对象。XmlBeanFactory在初始化时不会实例化所有的Bean对象,而是在第一次访问某个Bean时才进行实例化,这种延迟加载的方式有助于提高性能和降低资源消耗。
它是懒加载的,懒加载是一种优化策略,可以在某些情况下提高性能,但在其他情况下可能需要权衡。它在访问Bean时才会解析配置文件,如果应用程序中有大量的Bean,并且这些Bean的初始化是耗时的操作,那么懒加载可以帮助减轻启动时的负担。但是,如果你的应用程序要求在启动时立即初始化所有Bean以确保后续的快速响应时间,那么懒加载可能不合适。
但是XmlBeanFactory它是Spring早期版本的容器实现,现在的Spring应用程序中不推荐使用,很少使用。Spring2.5版本中被标记为已过时。
ApplicationContext是Spring框架的高级容器,提供了一种更高级、更强大的 IoC 容器,它能够管理 Bean 的生命周期、依赖注入和预初始化等功能,以提高应用程序的性能和灵活性,它在Spring 2.0版本中作为BeanFactory的一个子接口被引入。它在容器启动时会预初始化单例的 Bean,这意味着容器会在启动时创建并初始化所有单例的 Bean,并且通过
元素执行setter方法注入属性值,而不是在第一次访问时才初始化。这有助于提高程序获取 Bean 实例的性能,因为单例 Bean 在容器初始化后就已经准备好了,可以直接使用这是 Spring IoC 容器的一项重要功能,它通过管理 Bean 的生命周期和属性注入来提供更高的性能和便利性。
相比XmlBeanFactory,ApplicationContext是一个更完善的IoC容器实现,提供了更多企业级应用所需的支持功能。从Spring 2.0版本开始,ApplicationContext成为了Spring的基本IoC容器。
大约在Spring 2.5版本中,XmlBeanFactory被标记为已过时,官方文档也建议使用ApplicationContext来替代XmlBeanFactory。
类名称 | 描述 |
---|---|
ClassPathXmlApplicationContext | 从类路径加载配置文件,实例化ApplicationContext接口 |
FileSystemXmlApplicationContext | 从文件系统加载配置文件,实例化ApplicationContext接口 |
AnnotationConfigApplicationContext | 从注解中加载配置文件,实例化ApplicationContext接口 |
WebApplicationContext | 在Web应用中使用,从相对于Web根目录的路径中加载配置文件,实例化ApplicationContext接口 |
ConfigurableWebApplicationContext | 扩展了WebApplicationContext类,它可以通过读取XML配置文件的方式实例化WebApplicationContext类 |
Spring容器支持XML
和Properties
两种格式的配置文件,在实际开发中,最常用的是XML格式的配置文件。XML是标准的数据传输和存储格式,方便查看和操作数据。在Spring中,XML配置文件的根元素是
,
元素包含
子元素,每个
子元素可以定义一个Bean,通过
元素将Bean注册到Spring容器中。
元素的常用属性属性 | 描述 |
---|---|
id | id属性是 元素的唯一标识符,Spring容器对Bean的配置和管理通过id属性完成,装配Bean时也需要根据id值获取对象。 |
name | name属性可以为Bean指定多个名称,每个名称之间用逗号或分号隔开。 |
class | class属性可以指定Bean的具体实现类,其属性值为对象所属类的全路径。 |
scope | scope属性用于设定Bean实例的作用范围,其属性值有:singleton(单例)、prototype(原型)、request、session和global session。 |
元素的常用子元素元素 | 描述 |
---|---|
|
通过构造方法注入,可以使用 元素为Bean的属性指定值。 |
|
元素的作用是调用Bean实例中的setter方法完成属性赋值,从而完成依赖注入。 |
ref | ref是 、 等元素的属性,可用于指定Bean工厂中某个Bean实例的引用;也可用于指定Bean工厂中某个Bean实例的引用。 |
value | value是 、 等元素的属性,用于直接指定一个常量值;也可以用于直接指定一个常量值。 |
|
元素是 等元素的子元素,用于指定Bean的属性类型为List或数组。 |
|
元素是 等元素的子元素,用于指定Bean的属性类型为set。 |
|
元素是 等元素的子元素,用于指定Bean的属性类型为Map。 |
|
元素是 元素的子元素,用于设定一个键值对。 元素的key属性指定字符串类型的键。 |
元素为构造方法的参数指定值,Spring 容器会根据这些参数值来实例化 Bean。
元素为属性指定值,Spring 容器会在实例化 Bean 后调用相应的 setter 方法来设置属性值。 通常,使用 setter 方法实例化更加灵活,因为它可以在 Bean 实例化后动态设置属性值,而构造方法则用于在实例化时确定属性值。(因为构造方法写死,一次必须全部属性都声明好才可以,然而setter方法可以一直调用,实现动态设置属性值)
具体的实例化我们昨天的文章已经有写过了,今天再复习一下好吧,其实也就是构造方法注入和setter方法注入,静态工厂实例化、实例工厂实例化,这就是今天我们要学习的。
<bean id="singletonBean" class="com.example.SingletonBean">
bean>
<bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype">
bean>
<bean id="requestBean" class="com.example.RequestBean" scope="request">
bean>
<bean id="sessionBean" class="com.example.SessionBean" scope="session">
bean>
<bean id="applicationBean" class="com.example.ApplicationBean" scope="application">
bean>
在spring-config.xml中找到想要修改的bean,然后进行如下添加。
<bean id="apple" class="entity.Apple" scope="singleton">
<property name="weight" value="3">property>
bean>
**编写测试类:**通过Spring容器获取Fruit类的两个实例,判断两个实例是否为同一个,我们这里声明创建的Bean为单例模式,Spring容器应该只会存在一个共享的Bean实例,因此预期结果是:ture
可以看到判断结果是:true正确。
那么接下来,我们尝试把scope=“singleton”去掉,因为我们前面提到了,默认就是单例模式,那么我们进行尝试
<bean id="apple" class="entity.Apple" >
<property name="weight" value="3">property>
bean>
编写测试类:
结果还是一样的:true;
修改spring-config.xml中的bean,每次请求都会创建一个新的 Bean 实例,预期结果:false
输出结果:false,符合预期想法。
注解 | 描述 |
---|---|
@Component | 指定一个普通的Bean,可以作用在任何层次 |
@Controller | 指定一个控制器组件Bean,用于将控制层的类标识为Spring中的Bean,功能上等同于@Component |
@Service | 指定一个业务逻辑组件Bean,用于将业务逻辑层的类标识为Spring中的Bean,功能上等同于@Component |
@Repository | 指定一个数据访问组件Bean,用于将数据访问层的类标识为Spring 中的Bean,功能上等同于@Component |
@Scope | 指定Bean实例的作用域 |
@Value | 指定Bean实例的注入值 |
@Autowired | 指定要自动装配的对象 |
@Resource | 指定要注入的对象 |
@Qualifier | 指定要自动装配的对象名称,通常与@Autowired联合使用 |
@PostConstruct | 指定Bean实例完成初始化后调用的方法 |
@PreDestroy | 指定Bean实例销毁前调用的方法 |
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>5.2.8.RELEASEversion>
dependency>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="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
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="entity" />
beans>
的 base-package
配置为 "entity"
意味着 Spring 只会扫描 entity
包及其子包中的组件,而不会扫描其他包。 所以要解决这个问题,我们可以考虑将
的 base-package
属性配置为一个更高级别的包,以便能够扫描到所有的子包。例如,你可以将它配置为你的主包的父级包,或者更高级别的包。这将确保 Spring 能够扫描到所有的组件类。
这就是为什么一般来说我们新建项目的包名有讲究,一般都是com.example.xx包,这个是个倒叙的域名写法,中间的example可以是自己的名字又或者公司名,这样我们需要更换一下,扫描的包,扫描的包,或者更换成更高级别的包不可以是java。因为java是关键字,若是扫描的包变成
<context:component-scan base-package="java" />
上述写法,这样会导致Spring识别不出我们设置的bean,然后报错。
所以我们这里要设置成如下格式
1:添加所有使用了bean的包,作为扫描的对象
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="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
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.steveDash.entity" />
<context:component-scan base-package="com.steveDash.controller" />
<context:component-scan base-package="com.steveDash.dao" />
<context:component-scan base-package="com.steveDash.service" />
beans>
2.在包命名的时候,严格按照公司规章制度,进行包名的创建和设置,如下,我们后续的项目根目录包名都为www.steveDash( 一般都采用这种)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="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
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.steveDash" />
beans>
package entity;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component("user")
@Scope("singleton")
public class User {
@Value("1")
private int id;
@Value("张三")
private String name;
@Value("123")
private String password;
// getter/setter方法和toString()方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String toString(){
return "用户id:"+id+",用户名:"+name+",密码:"+password;
}
}
package dao;
public interface UserDao {
public void save();
}
package dao;
import entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save(){
ApplicationContext applicationContext=new
ClassPathXmlApplicationContext("applicationContext.xml");
User user=(User) applicationContext.getBean("user");
System.out.println(user);
System.out.println("执行UserDaoImpl.save()");
}
}
package com.stevedash.service;
public interface UserService {
public void save();
}
package com.steveDash.service.impl;
import com.stevedash.dao.UserDao;
import com.stevedash.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service("userService")
public class UserServiceImpl implements UserService {
//使用@Resource注解注入UserDao
@Resource(name="userDao")
private UserDao userDao;
@Autowired
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
this.userDao.save();
System.out.println("执行UserServiceImpl.save()");
}
}
package com.steveDash.controller;
import org.springframework.stereotype.Controller;
import com.steveDash.service.UserService;
import javax.annotation.Resource;
@Controller
public class UserController {
//使用@Resource注解注入UserService
@Resource(name="userService")
private UserService userService;
public void save(){
this.userService.save();
System.out.println("执行UserController.save()");
}
}
package Test;
import com.steveDash.controller.UserController;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void getBeanTest(){
ApplicationContext applicationContext=new
ClassPathXmlApplicationContext("applicationContext.xml");
UserController usercontroller=(UserController)
applicationContext.getBean("userController");
usercontroller.save();
}
}
输出结果如上:可以看到也是能够正常获取到Bean对象的。
自动装配(Automatic Wiring)是 Spring 框架中一种方便管理依赖关系的方式。它允许 Spring 容器自动将一个 Bean 的属性设置成其他 Bean 的引用,从而建立起这些 Bean 之间的依赖关系。
可以使用XML或者使用注解方式实现Bean的自动装配。
自动装配可以减少在 Spring 配置文件中手动指定依赖关系的工作,使配置更加简洁和易于维护。 可以在 Spring 配置文件中使用
元素的 autowire
属性来指定自动装配的方式。
具体实例如下:
上述配置中,autowire
属性设置为 "byType"
,表示使用根据类型自动装配的方式。
PS:自动装配并不适用于所有情况,特别是当有多个 Bean 类型满足自动装配条件时,可能会导致歧义。在这种情况下,我们可能需要显式地配置依赖关系,或者使用限定符(@Qualifier
注解)来消除歧义。
Spring 提供了一些注解来简化自动装配的配置:
@Autowired
注解:可以用在属性、构造函数、setter 方法上,告诉 Spring 在容器中查找匹配类型的 Bean,并自动装配到标记了 @Autowired
的属性或构造函数参数上。例如:
@Autowired
private UserService userService;
这会自动装配一个类型为 UserService
的 Bean 到 userService
属性上。
@Resource
注解:与 @Autowired
类似,也用于属性、构造函数、setter 方法上,但它是按名称自动装配。可以通过 name
属性指定要自动装配的 Bean 名称。例如:
@Resource(name = "userService")
private UserService userService;
这会自动装配名称为 “userService” 的 Bean 到 userService
属性上。
@Qualifier
注解:通常与 @Autowired
或 @Resource
注解一起使用,用于消除自动装配的歧义。当有多个符合类型匹配条件的 Bean 时,可以使用 @Qualifier
注解指定具体要装配的 Bean 的名称。例如:
@Autowired
@Qualifier("userService1")
private UserService userService;
这会自动装配名称为 “userService1” 的 Bean 到 userService
属性上。
使用注解方式的自动装配通常更加简洁和直观,不需要在 XML 配置中显式指定依赖关系。只需要在相应的类或属性上添加注解,Spring 容器会在扫描到这些注解时自动完成装配工作。
Bean的生命周期是指Bean实例被创建、初始化和销毁的过程。
在Bean的生命周期中,有两个时间节点尤为重要,分别是Bean实例初始化后和Bean实例销毁前,在这两个时间节点通常需要完成一些指定操作。因此,常常需要对这两个节点进行监控。
Spring容器提供了**@PostConstruct用于监控Bean对象初始化节点**,
提供了**@PreDestroy用于监控Bean对象销毁节点**。
我们可以在Bean定义中通过
元素的init-method
属性来指定初始化方法,通过destroy-method
属性来指定销毁方法。例如:
<bean id="myBean" class="com.steveDash.entity.MyBean" init-method="customInit" destroy-method="customDestroy" autowire="constructor" scope="singleton" >bean>
上面的配置中,init-method
属性指定了初始化方法为initMethod
,destroy-method
属性指定了销毁方法为destroyMethod
。这些方法需要在MyBean类中定义,并由Spring容器在相应的生命周期阶段调用。
package com.steveDash.entity;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class MyBean {
public void customInit() {
// 在这里进行初始化操作
System.out.println("Bean被初始化了!");
}
public void hello(){
System.out.println("你好!");
}
public void customDestroy() {
// 在这里进行销毁操作
System.out.println("Bean被销毁了!");
}
}
package Test;
import com.steveDash.entity.MyBean;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyBeanTest {
private Logger logger = Logger.getLogger(MyBeanTest.class);
@Test
public void getMyBeanTest(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean myBean=(MyBean) context.getBean("myBean");
myBean.hello();
// 关闭ApplicationContext容器
((ClassPathXmlApplicationContext) context).close();
}
}
因为我们定义的Bean的作用域为Singleton单例模式,那么他的生命周期与IoC容器相同,若是不关闭IoC容器,那么这个Bean就一直存在,不会被销毁,那么destory-method就没办法调用。
输入结果如下:
几种颜色对应的输出顺序,都是在对应的生命周期进行前调用,比如创建前调用initMethod,销毁前调用destory-method。一般来说都是在销毁Bean实例前,释放他们所占用的资源。
我们可以在MyBean1类中使用@PostConstruct
和@PreDestroy
注解来标记初始化方法和销毁方法,例如:
package com.steveDash.entity;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class MyBean1 {
@PostConstruct
public void customInit() {
// 在这里进行初始化操作
System.out.println("Bean被初始化了!");
}
public void hello() {
System.out.println("你好!");
}
@PreDestroy
public void customDestroy() {
// 在这里进行销毁操作
System.out.println("Bean被销毁了!");
}
}
这里基本代码不变,就是加上了注解还有修改了类名,因为可以留着方便观看俩种监控时间节点的方式的不同。这些注解会告诉Spring容器在Bean的初始化和销毁阶段分别调用标记的方法。
@Test
public void getMyBeanByAnnotationTest(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
MyBean1 myBean1=(MyBean1) context.getBean("myBean1");
myBean1.hello();
// 关闭ApplicationContext容器
((ClassPathXmlApplicationContext) context).close();
}
原因是:Spirng的IoC容器,在创建的同时会同时创建所有的作用域为singleton的Bean实例,那么在创建前就会输出一次initMethod方法,在IoC容器关闭时也会同时调用一次destory-method方法,这里我们实际上创建了俩个单例模式的bean,因此会输出俩次,但是我们只调用了一个单例,所以只输出了一个你好。
其他的作用域下的Bean的生命周期,可以各自去尝试一下,这里就不一一尝试了。
无论是XML配置文件还是注解方式,都可以用来监控和管理Bean的生命周期,具体选择取决于项目的需要和个人偏好。硬要来说的话,那就是小型项目或更简单的Bean生命周期事件处理可以使用注解来实现,而大型或更复杂的项目可能会受益于使用XML配置文件,以获得更多的可配置性和清晰度。具体还是得根据实际开发情况,将这两种方式结合使用。
今天我们详细的讲解了,详细讲解Spring IoC容器的原理、掌握了Bean标签以及属性的使用、熟悉Bean的实例化、Bean的几种作用域、掌握Bean的装配方式、自动装配方式XML和注解方式的区别和优势、什么是Bean的生命周期。希望通过今天的学习,各位读者可以对Spring的IoC容器和Bean管理有个大致的了解,为框架开发打下坚实基础。
想要跟着学习的可以去我的资源里面找对应的文件下载,我的md文件也会发上去,项目文件会上传可以自己跟着学习一下。
作者:Stevedash
发表于:2023年9月2日 20点56分