1. Spring 介绍
1.1 Spring 是什么?
Spring(Spring Framework) 是一个开源的轻量级框架,是包含了众多工具方法的 IoC 容器。
那么什么 是 IoC 容器呢?
容器 本身的含义是用来容纳某种物品的装置,而在之前的学习中,我们就应该接触过一些容器了,比如存储数据的容器 List、Map、Set 等等,Web 容器 Tomcat 等等。
那么 IoC 是什么呢?
1.2 IoC 是什么?
IoC(Inversion of Control),译为控制反转,它不是什么技术,而是一种思想。在 Java 开发中,IoC 意味着你将设计好的依赖对象 B 交给容器控制,而不是按照传统的方式,在你需要使用依赖对象 B 的对象 A 内部直接控制。
IoC 既然译为控制反转,那么我们就要理解谁控制谁,控制什么?要理解为何要反转,反转了什么?
谁控制谁,控制什么?
按照传统的设计思想,如果我们想要在 A 对象内部使用 B 对象,那么我们就会在 A 中直接 new 一个 B,这是我们程序员主动去创建的依赖对象;而 IoC 容器能够存储你需要的对象 B,当你要使用这个依赖对象 B 时,IoC 容器就会来控制依赖对象的创建,而程序只需要引入这个已经创建好的对象 B 就行。IoC 容器主要是控制了外部资源的获取(不只是包含要依赖的对象,还包括文件等等)。
为何要反转,反转了什么?
按照传统的设计思想,如果我们想要在 A 对象内部使用 B 对象,那么我们就会在 A 中直接 new 一个 B,这是我们程序员主动去创建的依赖对象;而反转则是使容器来帮忙创建并注入依赖对象,它使得对象 A 不需要再主动去创建对象 B 了,而是被动的接受由容器已经创建好的依赖对象,即对象创建、管理的控制权被反转了,依赖对象的获取被反转了。
IoC 的作用:
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试。除此之外,将对象存储在 IoC 容器相当于将以后可能?到的所有?具制作好后都放到仓库中,需要的时候直接取出就?,?完再把它放回到仓库,利于复用。? new 对象的?式相当于,每次需要?具时,才现做,?完就扔掉了也不会保存,下次再?的时候还得重新做。
注意:
并不是只有 Spring 的容器才叫 IoC 容器,基于 IoC 容器的框架还有很多,并非是 Spring 特有的
到这里 IoC 已经差不多介绍完了,从上面的介绍可知 IoC 它只是一种思想,那么 IoC 思想具体的实现是怎么样的呢?我们就不得不提到 DI。
1.3 DI 是什么?
DI(Dependency Injection),译为依赖注入,它是指 IoC 容器在运行期间,动态地将某种依赖关系注入到对象之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
DI 既然译为依赖注入,那么我们就要理解谁依赖谁,为什么需要依赖,谁注入谁,注入了什么?
- 谁依赖谁:应用程序依赖 IoC 容器。
- 为什么需要依赖:应用程序需要 IoC 容器来提供对象所需的外部资源。
- 谁注入谁:IoC 容器注入应用程序某个对象。
- 注入了什么:注入了某个对象所需的外部资源(包括对象、资源、常量数据等等)。
IoC 和 DI 的关系:
IoC 和 DI 其实是同一个概念的不同角度的描述,IoC 是一种思想,而 DI 是具体的实现,通过引入 IoC 容器,利用依赖关系注入的方式,就能实现对象之间的解耦。
1.4 Spring 的核心功能
回归主题,Spring 是包含了众多工具方法的 IoC 容器,我们已经介绍了 IoC 是什么,那么如何理解“Spring 就是一个 IoC 容器”呢?
上面介绍了很多关于 IoC 的知识,但是 Spring 的本质上是一个容器,容器才是 Spring 框架实现功能的核心,既然 Spring 是一个容器,它就会具备容器的两个最基础的功能:
也就是说 Spring 最核心的功能 就是将对象存入到 Spring 中,再从 Spring 中获取到对象。又因为 Spring 是?个 IoC 容器,那么除了它本身具备的存储对象和获取对象的能?,还具有管理对象的创建和销毁的权利。
1.5 Spring 的应用上下文
我们已经了解了 Spring 是一个 IoC 容器,但是容器只是一个提供给需要被管理的对象的空间,还需要通过 Spring 的应用上下文,来向容器中添加我们需要的被管理的对象。
Spring 的应用上下文 可以简单的理解成将你需要 Spring 帮你管理的对象放入到容器的容器对象,它是 Spring 容器的一种抽象化表述。
常用的 Spring 上下文对象为 ApplicationContext ,它本质上就是一个维护 Bean 定义以及对象之间协作关系的高级接口,它是继承了上下文对象 BeanFactory 后派生而来的应用上下文的抽象接口。在功能上,相比于 BeanFactory 只能提供基本的 DI 功能,它能提供更多的企业级的服务,比如对国际化支持、资源访问、以及事件传播等等。在性能上,ApplicationContext 是?次性加载并初始化所有的 Bean 对象(在获取上下文时,就会创建初始化所有的 Bean对象),所以调用快,? BeanFactory 是需要哪个 Bean 才去加载哪个 Bean(在 getBean 时才会创建初始化指定的 Bean 对象),因此更加轻量,但调用慢。
对于上述两种上下文抽象接口,Spring 提供了多种类型的容器实现,用于不同场景的使用
ClassPathXmlApplicationContext :从类路径下的一个或多个 xml 配置文件中加载上下文,适用于 xml 配置的方式。AnnotationConfigApplicationContext :从基于一个或多个 Java 的配置类中加载上下文,适用于 Java 注解的方式。FileSystemXmlApplicationContext :从文件系统下的一个或多个 xml 配置文件中加载上下文,从系统盘符中加载 xml 配置文件。AnnotationConfigWebApplicationContext :专门为 web 应用准备的,适用于注解方式。XmlWebApplicationContext :从 web 应用下的一个或多个 xml 配置文件加载上下文,适用于 xml 配置方式。
2. Spring 项目的创建和使用
在 Java 语言中对象也叫做 Bean,因此以下遇到的对象将以 Bean 著称。
2.1 创建 Maven 项目
-
创建一个 Maven 项目(在 IDEA 中创建好 Maven 项目) -
添加 Spring 框架支持(在项目的 pom.xml 文件中添加 Spring 框架的支持,配置如下)
Spring 项目需要添加的依赖有 spring-context (Spring 上下文)和 spring-beans (管理对象的模块)。可以去 Maven 中央仓库搜索,也可以直接使用下面的依赖。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
</dependencies>
-
添加启动类(在 /src/main/java 目录下创建一个启动类,类名自定义,包含 main 方法即可)
2.2 存储 Bean 对象
-
创建好需要的 Bean(可以创建一个包,用于存放要创建的 Bean)
存储 Bean 分为两个步骤,先创建好需要的 Bean,再将创建好的 Bean 注册到 Spring 容器中
-
将 Bean 注册到 Spring 容器中
先在 resources 目录下创建一个 xml 文件(文件名没有要求),用于存放 Spring 的配置文件,配置文件是固定的内容,内容如下。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
将创建好的 Bean 添加到 Spring 配置文件中。在原有的 beans 标签内再创建一个 beans 标签,里面用于存放要注册的 Bean,然后在创建好的 beans 标签内创建一个 bean 标签,表示要注册的某个 Bean,该 bean 标签内添加一个 id 属性表示存储对象在 Spring 中的身份标识,即 Bean 对象的名字,再添加一个 class 属性表示要存储的对象(包含包名和类名)
补充: Spring 存储 Bean 时的命名规则
- 当 bean 标签中主动设置 id 属性来命名 Bean 对象的名字时,Spring 中存储该 Bean 的名字即为 id 的值。(源码如下)
- 当 bean 标签没有设置 id 属性来命名 Bean 对象的名字时,Spring 中存储该 Bean 时生成的命名规则为:当类名的长度大于1,且第一个字符和第二个字符都为大写时,则直接命名为类名;否则将类名的第一个字符转成小写后,再返回新生成的字符。(源码如下)
2.3 获取并使用 Bean 对象
-
创建 Spring 上下文(这里使用 ApplicationContext 作为上下文对象,也可以使用 BeanFactory )
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
-
获取指定的 Bean 对象(这里使用上文正已经注册到 Spring 容器中的 Bean 对象 User)
getBean 方法用于加载上下文中的 Bean 对象,它有多种重载的方法
注意: 如果当前的一个类型被重复注册到 Spring 的配置文件中,那么不能使用第三种方式去获取 Bean 对象,需要根据名称来获取。
User user = (User) context.getBean("user");
User user = (User) context.getBean("user", User.class);
User user = (User) context.getBean(User.class);
-
使用 Bean user.say();
注意:Spring 中的 Bean 默认是单例模式。可以再创建一个 User 实例,打印两个实例得到结论
3. Spring 更简单的读取和存储对象方式
上面实现了基本的 Spring 存储和读取对象的操作,但是在操作过程中并不是那么的简单,接下来将介绍更简单的存储和读取 Bean 对象的方法,即使用注解。
3.1 存储 Bean 对象
之前将 Bean 存储到 Spring 中的方法,是在 Spring 的配置文件中直接添加 bean 注册内容,示例如下:
而通过使用注解,就无需在存储一个 Bean 对象时,在 Spring 的配置文件中再添加一个注册内容,只要在 Spring 的配置文件中配置一个扫描路径即可实现 Bean 的批量注册。
3.1.1 配置扫描路径
Spring 引入了组件自动扫描机制,它可以在类路径底下,扫描配置的 base-package 包下所有标注了@Component 、@Service 、@Controller 、@Repository 、@Configuration 注解的类,并把这些类纳入进Spring 容器中管理。
在 Spring 配置文件中添加如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:content="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
https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.t4.beans">
</content:component-scan>
</beans>
3.1.2 注册 Bean 相关的注解介绍
想要将对象存储在 Spring 中,有两种类型的注解可以实现:
-
类注解: 注解在类上,用于标识组件的类型
注解 | 描述 |
---|
@Controller | 组合了 @Component 注解,应用在 mvc 层(控制层),DispatcherServlet 会自动扫描注解了此注解的类,然后将 web 请求映射到注解了@RequestMapping 的方法上。 | @Service | 组合了 @Component 注解,应用在 service 层(业务逻辑层)。 | @Repository | 组合了 @Component 注解,应用在 dao 层(数据访问层)。 | @Component | 表示该类是一个“组件”,成为 Spring 管理的 Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component 还是一个元注解。 | @Configuration | 声明当前类为配置类,其中内部组合了@Component 注解,表明这个类是一个 Bean。 |
-
方法注解: 注解在方法上
注解 | 描述 |
---|
@Bean | 声明当前方法的返回值是一个 Bean,返回的 Bean 对应的类中可以定义 init() 方法和 destroy() 方法。 |
为何说 @Controller 、@Service 、@Repository 、@Configuration 都组合了 @Component 注解呢?
通过源码发现,这些注解本身都带了一个注解 @Component ,即它们本身就属于 @Component 的“子类”
3.1.3 添加 @Controller 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void say(){
System.out.println("你好 Controller!");
}
}
3.1.4 添加 @Service 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void say(){
System.out.println("你好 Service!");
}
}
3.1.5 添加 @Repository 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void say(){
System.out.println("你好 Repository!");
}
}
3.1.6 添加 @Component 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void say(){
System.out.println("你好 Component!");
}
}
3.1.7 添加 @Configuration 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
public void say(){
System.out.println("你好 Configuration!");
}
}
3.1.8 添加 @Bean 注解存储 Bean 对象
注解 @Bean 需要与类注解搭配使用,例如常与注解 @Configuration 结合使用。带有 @Bean 注解的方法返回的对象会存到 Spring 容器中,Bean 对象的名字为方法名。
虽说使用 @Bean 注解要搭配类注解使用,看似要多出一笔,但是加上了类注解后能大大加快扫描的性能。
示例代码:
package com.t4.beans;
import com.t4.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserBeans {
@Bean
public UserInfo getUserInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("张三");
userInfo.setPassword("1234");
return userInfo;
}
}
重命名 Bean:
由于方法名不能很直观的显示 Bean 名,为此可以重命名 Bean。重命名方式为:在 @Bean 注解后面增加一个 name 属性,该属性可以含多个值,其中 name = {} 都可以省略,只带新的 Bean 名。
注意1: 重命名后,如果起了多个名字,则使用任意一个都可以。但是不能再使用原方法名去获取 Bean
注意2: 重命名后,如果起了多个名字,获取的对象都是同一个
3.2 获取 Bean 对象
获取 Bean 对象也叫做对象装配或者对象注入,是把对象从容器中取出来放到某个类中。
之前我们介绍了先通过获取 Spring 应用上下文,再从中获取 Bean 对象的方法去得到 Bean,但是整体显得比较麻烦。接下来将介绍三种更简单的对象注入的方法:
- 方法一:属性注入
- 方法二:构造方法注入
- 方法三:Setter 注入
3.2.1 注入 Bean 相关注解介绍
这里介绍三种和注入 Bean 相关的注解:
注解 | 描述 | 包含的属性 |
---|
@Autowired | 可以标注在属性、构造方法和 Setter 方法上,用于完成自动装配。默认通过 Bean 类型进行查询,如果查询不到则会再通过 Bean 对象名进行查询,为 Spring 提供的注解。 | required | @Resource | 可以标注在属性和 Setter 方法上,用于完成自动装配。默认通过 Bean 名进行查询,为 JDK 提供的注解。 | name 、type 等 | @Qualifier | 用于指定需要注入的 Bean 对象的名字 | value |
- 使用
@Autowired 自动注入的时候,可以在属性或参数加上 @Qualifier("xxx") 指定注入到对象; - 使用
@Autowired 注解时,当 Spring 无法找到匹配的 Bean 装配,它会抛出异常。要解决这个问题,可以通过 @Autowired 的 required 属性设置为 false 来禁用此检查功能。 - Spring 将
@Resource 注解的 name 属性的值解析为为 Bean 的名字,type 属性的值解析为为 Bean 的类型。 - 使用
@Resource 注解默认通过 Bean 名进行查询装配。如果使用 name 属性,则使用 Bean 名 自动注入策略;如果使用 type 属性,则使用 Bean 类型自动注入策略。 @Autowired 注解用于构造方法注入时,如果只有一个构造方法,可以省略使用 @Autowired ,如果有多个注解则不能省略。@Autowired 可以用于数组和使用泛型的集合类型,然后 Spring 会将容器中所有类型符合的 Bean 注入进来。@Autowired 标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。@Autowired 标注作用于普通方法时,会产生一个副作用,就是在容器初始化该 Bean 实例的时候就会调用该方法。当然,前提是执行了自动装配,对于不满足装配条件的情况,该方法也不会被执行。
3.2.2 属性注入 Bean 对象
在需要的类中先创建一个要被注入的对象(该对象可以是一个集合,那么得到的结果就是所有匹配的 Bean 的集合),并在该对象上加上 @Autowired 或 @Resource 注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码1: 使用 @Autowired 注解
@Controller
public class UserController {
@Autowired
private UserService userService;
public void getUserService(){
System.out.println(userService);
}
}
示例代码2: 使用 @Resource 注解
@Controller
public class UserController {
@Resource
private UserService userService;
public void getUserService(){
System.out.println(userService);
}
}
3.2.3 构造方法注入 Bean 对象
在需要的类中先创建一个要被注入的对象,然后创建该类的构造方法,将要被注入的对象进行赋值,并在构造方法上加上 @Autowired 注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码: 使用 @Autowired 注解
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
public void getUSerService(){
System.out.println(userService);
}
}
3.2.4 Setter 注入 Bean 对象
在需要的类中先创建一个要被注入的对象,然后创建该类的 Setter 方法,将要被注入的对象进行赋值,并在 Setter方法上加上 @Autowired 或 @Resource 注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码1: 使用 @Autowired 注解
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void getUSerService(){
System.out.println(userService);
}
}
示例代码2: 使用 @Resource 注解
@Controller
public class UserController {
private UserService userService;
@Resource
public void setUserService(UserService userService) {
this.userService = userService;
}
public void getUSerService(){
System.out.println(userService);
}
}
3.2.5 三种注入方式的优缺点
注入方式 | 优点 | 缺点 |
---|
属性注入 | 写法最简单,可读性高。 | 只适应 IoC 容器,移植性差。 | 构造方法注入 | 可以适应非 IoC 容器,通用性好,代码移植性高。 | 如果有多个注入,代码会显得比较臃肿。 | Setter 注入 | 只有对象是需要被注入时,才会注入依赖,而不是在这个类初始化的时候就注入。可以适应非 IoC 容器,通用性好,代码移植性高。 | 不能将对象设为 final。 |
3.2.6 同一类型多个 @Bean 报错问题
当出现多个 Bean 返回的对象类型都相同时,如果注入的注解不设置具体注入的哪个 Bean,则会报错。对于这个问题有两种解决方式:
- 对于 @Autowired 注解,可以搭配一个 @Qualifuer 来指定要注入的 Bean
- 对于 @Resource 注解,可以设置一个 name 属性来指定要注入的 Bean
代码情景:
创建了一个 User 类,里面包含了 name 和 password 两个字段。再创建一个 UserService 类这个类通过两个 @Bean 注解的方法注册两个类型相同,名字不同的 Bean 存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 User 这个对象注入到该类中,并创建了一个 printUser 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 printUser 方法。
示例代码1: 对于 @Autowired 的解决方式
@Controller
public class UserController {
@Autowired
@Qualifier(value = "user1")
private User user;
public void printUser(){
System.out.println(user);
}
}
示例代码2: 对于 @Resource 的解决方式
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void printUser(){
System.out.println(user);
}
}
4. Bean 的作用域
当在 Spring 中定义一个 bean 时,你可以声明该 bean 的作用域。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype,即多例作用域。同理,如果你想让 Spring 在每次需要时都返回同一个 bean 实例,你应该声明 bean 的作用域的属性为 singleton,即单例作用域。
在 Spring 中 Bean 默认情况下是单例状态,即所有人拿到的 Bean 都是同一个对象。设置为单例减少了新生成实例的消耗、减少了 JVM 垃圾回收、能够快速获取到 Bean,也就是说能够很大程度的提高性能。
4.1 Bean 的六种作用域
Spring 容器在初始化一个 Bean 实例时,同时会指定该实例的作用域。Spring 有六种作用域,前两种是基于 整个 Spring 生态生效的,后四种是基于 Spring MVC 生效的。
4.1.1 单例作用域 singleton
- 官方说明: (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
- 描述: 该作?域下的 Bean 在 IoC 容器中只存在?个实例:获取 Bean(即通过 applicationContext.getBean等?法获取)及装配 Bean(即通过 @Autowired 注?)都是同?个对象。
- 场景: 通常?状态的 Bean 使?该作?域。?状态表示 Bean 对象的属性状态不需要更新。
- 备注: Spring 默认选择该作?域
4.1.2 原型作用域/多例作用域 prototype
- 官?说明: Scopes a single bean definition to any number of object instances.
- 描述: 每次对该作?域下的 Bean 的请求都会创建新的实例:获取 Bean(即通过 applicationContext.getBean 等?法获取)及装配Bean(即通过 @Autowired 注?)都是新的对象实例。
- 场景: 通常有状态的 Bean 使?该作?域
4.1.3 请求作用域 request
- 官?说明: Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述: 每次 HTTP 请求会创建新的 Bean 实例,类似于 prototype
- 场景: ?次 HTTP 的请求和响应的共享 Bean
- 备注: 限定 Spring MVC 中使?
4.1.4 会话作用域 session
- 官?说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述: 在?个 HTTP Session 中,定义?个 Bean 实例
- 场景: ?户会话的共享 Bean,?如:记录?个?户的登陆信息
- 备注: 限定 Spring MVC 中使?
4.1.5 全局作用域 application
- 官?说明: Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述: 在?个 http servlet Context 中,定义?个 Bean 实例
- 场景: Web 应?的上下?信息,?如:记录?个应?的共享信息
- 备注: 限定 SpringMVC 中使?
4.1.6 HTTP WebSocket 作用域 websocket
- 官?说明: Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
- 描述: 在?个 HTTP WebSocket 的?命周期中,定义?个 Bean 实例
- 场景: WebSocket 的每次会话中,保存了?个 Map 结构的头信息,将?来包裹客户端消息头。第?次初始化后,直到 WebSocket 结束都是同?个 Bean。
- 备注: 限定 Spring WebSocket 中使?
4.2 单例作用域和全局作用域的区别
- singleton 是 Spring Core 的作用域;application 是 Spring Web 的作用域
- singleton 作用于 IoC 容器;application 作用于 Servlet 容器
4.3 设置作用域的方法
设置 Bean 的作用域有两种方式
- 方式一:在 Spring 配置文件中,对于注册的每一个 Bean 可以增加一个 scope 属性来设置 Bean 的作用域
- 方式二:通过
@Scope 注解就可以声明 Bean 的作用域,它既可以修饰方法也可以修饰类
示例代码1: 方式一
代码示例2: 方式二
5. Bean 的原理分析
5.1 Spring 的执行流程
Spring 的执行流程可以分为以下几步:
- 启动 Spring 容器(在 main 方法中启动)
- 实例化 Bean(加载 Spring 配置文件,实例化并申请内存)
- 将 Bean 注册到 Spring 中(将添加了和 Spring 注册 Bean 相关的注解的对象存储到容器中)
- 将 Bean 装配到需要的类中
5.2 Bean 的生命周期
Bean 的生命周期可以分为以下几个部分:
-
实例化 Bean(为 Bean 分配内存空间) -
设置 Bean 属性(依赖注入和装配,必须在初始化 Bean 之前,因为初始化的时候可能会用到 Bean 对象) -
初始化 Bean(初始化 Bean 时会按顺序执行以下几小步)
- 实现各种 Aware 通知的方法,如
BeanNameAware 、BeanFactoryAware 、ApplicationContextAware 的接口方法 - 执行
BeanPostProcessor 初始化前置方法 - 执行
@PostConstruct 初始化方法,依赖注入操作之后被执行 - 判断是否继承了
InitializingBean 接口(调用 afterPropertiesSet 方法) - 执行自己的
init-method 初始化方法 - 执行
BeanPostProcessor 初始化后置方法 -
使用 Bean -
销毁 Bean
- 执行
@PreDestory 销毁前的方法 - 判断是否继承了
DisposableBean 接口(调用 - 执行自己的
destory-method 销毁前的方法
接下来可以通过一段代码来验证 Bean 的生命周期的过程,示例代码如下:
@Controller
public class BeanLife implements BeanNameAware, InitializingBean, DisposableBean {
@Override
public void setBeanName(String s) {
System.out.println("执行 BeanNameAware 接口的方法:" + s);
}
@PostConstruct
public void postConstruct(){
System.out.println("执行 @PostConstruct");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("继承了 InitializingBean");
}
public void myInit(){
System.out.println("执行 init-method");
}
@PreDestroy
public void preDestroy(){
System.out.println("执行 @PreDestroy");
}
public void destroy(){
System.out.println("继承了 DisposableBean");
}
public void myDestroy(){
System.out.println("执行 destroy-method");
}
}
|