Spring入门基础

目录

1.Spring的相关概念

2.Spring的核心与设计思想

3.Spring创建与使用

4.更简单地去使用Spring

5.Bean的作用域与生命周期


1.Spring相关概念

1.1 什么是Spring

我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞 ⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的 应⽤程序开发起来更简单。

而Spring的本质其实就是是包含了众多⼯具⽅法的 IoC 容器

这里我们有两个关键字,一个是IoC ,另一个则是容器。

1.2 什么是容器

容器是⽤来容纳某种物品的(基本)装置。

比如我们Java中常用的List/Map就是一种数据存储容器

而我们常用于web开发的Tomcat就是一种web容器

1.3 什么是 IoC 

前面我们提到Spring是一个IoC容器,而IoC(Inversion of Control 翻译成中⽂是“控制反转”的意思)。当然光这样理解还是比较抽象的,我们下面通过一个例子来理解。

2.Spring的核心与设计思想 

2.1 传统程序开发

假如我们需要在程序内构建一辆”车“,那么我们传统的思想一般是下面这样的。

 我构建一辆汽车,首先要依赖于车身,而车身又依赖于地盘。而底盘需要轮胎。最终我们得到下面的代码。

public class Main {public static void main(String[] args) {Car car = new Car();car.init();}/*** 汽⻋对象*/static class Car {public void init() {// 依赖⻋身Framework framework = new Framework();framework.init();}}/*** ⻋身类*/static class Framework {public void init() {// 依赖底盘Bottom bottom = new Bottom();bottom.init();}}/*** 底盘类*/static class Bottom {public void init() {// 依赖轮胎Tire tire = new Tire();tire.init();}}/*** 轮胎类*/static class Tire {// 尺⼨private int size = 30;public void init() {System.out.println("轮胎尺⼨:" + size);}}
}

 虽然这段代码的确能够满足需求,但是我们不难发现一个特点,就是它的耦合性非常高。假如我们需要更改轮胎的尺寸,我们需要将轮胎以及所有依赖轮胎的组件的构造方法全部更改一遍,加上size参数,例如下面那样。

public class Main {public static void main(String[] args) {Car car = new Car(20);car.run();}/*** 汽⻋对象*/static class Car {private Framework framework;public Car(int size) {framework = new Framework(size);}public void run() {// 依赖⻋身framework.init();}}/*** ⻋身类*/static class Framework {private Bottom bottom;public Framework(int size) {bottom = new Bottom(size);}public void init() {// 依赖底盘bottom.init();}}/*** 底盘类*/static class Bottom {private Tire tire;
public Bottom(int size) {tire = new Tire(size);}public void init() {// 依赖轮胎tire.init();}}/*** 轮胎类*/static class Tire {// 尺⼨private int size;public Tire(int size) {this.size = size;}public void init() {System.out.println("轮胎尺⼨:" + size);}}
}

 而在实际开发过程中,需求变更是很常见的。而我们不难看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修改。这在实际开发过程中就会严重拖慢我们的进度,那么有什么办法能够解决这个问题呢。

我们看到下面的代码

public class Main {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}static class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;}public void run() {framework.init();}}static class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;}public void init() {bottom.init();}}static class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;}public void init() {tire.init();}}
static class Tire {private int size;public Tire(int size) {this.size = size;}public void init() {System.out.println("轮胎:" + size);}}
}

 可以看到我们先创建了所有的下级依赖类,然后再通过参数传递的方式注入。这样我们不需 要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本身也⽆需修改任 何代码,这样就完成了程序的解耦

 以上就是所谓的控制反转式程序开发。此时⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间的解耦,从⽽实现了更加灵活、通⽤的程序设计了。

2.2 简单对比 

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire

改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car

 

这里我们可以发现:通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 创建并创建了 Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是上级对象创建并控制下级对象了,⽽是下级对象把注⼊将当前对象中,下级的控制权不再由上级类控制了,这样即使下级类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。 

2.3 理解Spring IoC 

既然 Spring 是⼀个 IoC(控制反转)容器,重点还在“容器”⼆字上

那么它就具备两个最基础的功能:

将对象存⼊到容器;

从容器中取出对象。

也就是说学 Spring 最核⼼的功能,就是学如何将对象存⼊到 Spring 中,再从 Spring 中获取对象的过程。

 将对象存放到容器中的好处:

将对象存储在 IoC 容器相当于将以后可能⽤的所有⼯具制作好都放到仓库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。⽽ new 对象的⽅式相当于,每次需要⼯具了,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做,这就是 IoC 容器和普通程序开 发的区别。

Spring 是⼀个 IoC 容器,说的是对象的创建和销毁的权利都交给 Spring 来管理了,它本身⼜具备了存储对象和获取对象的能⼒。 

2.4 DI与IoC 

说到 IoC 不得不提的⼀个词就是“DI”,DI 是 Dependency Injection 的缩写,翻译成中⽂是“依赖注 ⼊”的意思。

所谓依赖注⼊,就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。而依赖注⼊(DI)和控制反转(IoC)只是从不同的角度的描述的同⼀件事情。

IoC是一种思想,而DI是实际的落地执行方案。IoC是指导DI去执行的

3.Spring的创建与使用

3.1 Spring的创建

有关Spring项目的创建,我们需要使用到maven,类似于我们创建servlet项目。主要分为以下三个步骤:

1. 创建⼀个普通 Maven 项⽬。

2. 添加 Spring 框架⽀持(spring-context、spring-beans)。

3. 添加启动类。


这里我只讲一些关键的步骤

首先我们需要引入Spring框架支持,我们需要在pom.xml文件中引入下面的代码

<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>

之后后在创建好的项⽬ java ⽂件夹下创建⼀个启动类,包含 main ⽅法即可

public class App {public static void main(String[] args) {}
}

3.2 Bean对象的存储

这里我们需要明确Bean对象其实就是Java中的一个普通对象,大家不需要感觉陌生。

 存储 Bean 分为以下 2 步:

1. 存储 Bean 之前,先得有 Bean 才⾏,因此先要创建⼀个 Bean。

2. 将创建的 Bean 注册到 Spring 容器中。

 第一步我们只要任意创建一个对象就好,参考下图

package com.beans;public class User {public User() {System.out.println("加载了 User");}public void sayHi(String name) {System.out.println("你好:" + name);}
}

之后我们需要将Bean注册到Spring容器内,具体步骤如下:

首先在创建好的项⽬中添加 Spring 配置⽂件 spring-config.xml(名字可以随便取,但建议规范化命名),将此⽂件放到 resources 的根⽬录下,如 下图所示:

 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>

接下来,再将 User 对象注册到 Spring 中就可以,具体操作是在 中添加如下配置:

<beans><bean id="user" class="com.beans.User"></bean>
</beans>

这里我们可以把String看成一个Map容器。类似于Map<String[beanName],Object>。

注意:id可以不等于class里面的类名,但是要尽量符合规范。

类名使用大驼峰,bean id通常使用小驼峰。

假设类名是UserInfo,那么bean id就应该是 userInfo

3.3 获取并使用 Bean 对象 

获取并使⽤ Bean 对象,分为以下 3 步:

1. 得到 Spring 上下⽂对象,因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么 就得先得到 Spring 的上下⽂。

2. 通过 Spring 上下⽂,获取某⼀个指定的 Bean 对象。

3. 使⽤ Bean 对象。

3.3.1 创建 Spring 上下文

Spring 上下⽂对象可使⽤ ApplicationContext,实现代码如下:

// 1.得到 Spring 的上下⽂对象,创建的时候需要配置 Spring 配置信息
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

这里尤其需要注意Spring的配置信息不能错,很多同学容易写错配置文件名字导致报错,比如下面这种错误。

 除了 ApplicationContext 之外,我们还可以使⽤ BeanFactory 来作为 Spring 的上下⽂,如下代码所示:

BeanFactory beanFactory = new XmlBeanFactory(new
ClassPathResource("spring-config.xml"));

ApplicationContext 和 BeanFactory 效果是⼀样的,ApplicationContext 属于 BeanFactory 的⼦类, 它们的区别如下。

ApplicationContext 与 BeanFactory区别(常见面试题) 

1)继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和 ApplicationContext。 其中 BeanFactory 提供了基础的访问容器的能⼒,而 ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化⽀持、 资源访问⽀持、以及事件传播等⽅⾯的⽀持。

2)从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽ BeanFactory 是需要那个才去加载那个,因此更加轻量。


PS:⽽ ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,是通过 xml 的配置来获取所有的 Bean 容器的。 

3.3.2 获取指定的 Bean 对象 

// 1.得到 Spring 上下⽂对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.加载某个 bean
User user = (User) context.getBean("user");

这里我们需要注意bean的id需要和xml文件内对应上。

3.3.3 getBean方法的更多用法

1.使用bean name获取bean

User user = (User) context.getBean("user");

2.根据bean type获取bean

User user = (User) context.getBean(User.class);

这种写法简单,一般用于确定该类型只会被注入一次时使用。如果同一类型被注入多次使用该方法则会报错。

3.根据bean name和类型获取bean

User user = (User) context.getBean("user",User.class);

4. 更简单地去使用Spring

在之前我们想要将一个对象注入到Spring容器中需要手动在Spring的配置文件中手动添加,这明显不太方便,我们需要一种更简便的方法来存取bean。

4.1 配置扫描路径(前置工作)

注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的 包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。

在 spring-config.xml 添加如下配置:

<?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.beans"></content:component-scan>
</beans>

下面的就是注册扫描的包:com.beans

 也就是说,即使添加了注解如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。

注意:这里的扫描路径是包含了其子路径的,也就是说如果bean对象是在com.beans的子路径下也是能够被扫描到的

 4.2 添加注解存储 Bean 对象

上面我们提到为了能够更加方便地存取bean我们需要添加扫描包路径,但是仅仅这样是不够的。我们还需要使用注解

我们先来简单了解一下注解的类型:

想要将对象存储在 Spring 中,有两种注解类型可以实现:

1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration。

2. ⽅法注解:@Bean。

4.2.1 注解的相关使用 

我们首先看到@Controller(控制器存储),代码如下

@Controller// 将对象存储到 Spring 中
public class UserController {public void sayHi(){System.out.println("Hello controller");}
}

此时我们先使⽤之前读取对象的⽅式(后续会介绍更简单的)来读取上⾯的 UserController 对象,如下代码所示:

public class Application {public static void main(String[] args) {// 1.得到 spring 上下⽂ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");// 2.得到 beanUserController userController=context.getBean("userController", UserController.class);// 3.调⽤ bean ⽅法userController.sayHi();}
}

其他类注解的用法类似,我就不再过多赘述。大家在使用时只需要更改对应的注解名字即可。这样我们就省去了反复去更改xml配置文件的过程,只需要在首次使用时添加扫描包路径以及添加相应的注解就能将bean对象存入Spring容器内了。

4.3 为什么要有这么多类注解

在解决这个问题之前,我们先看看五大类注解的源代码。

 

 

 

 

 大家通过观察可以发现其实这五大类在功能上并没有本质区别,并且其余的四个类注解都是基于@Component实现的。既然如此我们为什么要搞出几个只是名字有所不同的类注解呢?

 这和为什么每个省/市都有⾃⼰的⻋牌号是⼀样的?

⽐如陕⻄的⻋牌号就是:陕X:XXXXXX,北京的⻋ 牌号:京X:XXXXXX,⼀样。甚⾄⼀个省不同的县区也是不同的,⽐如⻄安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样。这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地。

那么为什么需要怎么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类的⽤途,⽐如: 

@Controller:表示的是业务逻辑层;

@Servie:服务层;

@Repository:持久层;

@Configuration:配置层。

@Component:可以理解成工具组件,独立于其他四种注解

我们通过下面这张图来理解:

 

 配置层就是用于定义配置类,可替换xml配置文件


这里的控制层实际上就是负责校验数据的,类似于保安。他会过滤掉那些不符合规定的数据。比如我们在处理登录系统时,就可以设置一个@Controller,当识别到数据有误时就可以直接返回ERROR,不需要我们反复去写判断逻辑,简化了代码。


服务层负责的就是各种数据组装与接口的调用,它并不负责程序执行的实际逻辑,更像是一个公司的前台,当你去处理业务的时候他会告诉你该去哪调用什么接口去处理。


数据持久层则是真正的业务逻辑代码,它会和数据库进行交互,去实现各种接口的逻辑。

4.4 关于bean对象的命名 

通过上⾯示例,我们可以看出,通常我们 bean 使⽤的都是标准的⼤驼峰命名,⽽读取的时候⾸字⺟⼩ 写就可以获取到 bean 了,如下图所示:

然⽽,当我们⾸字⺟和第⼆个字⺟都是⼤写时,就不能正常读取到 bean 了,如下图所示:


 这时候我们就需要去查看Spring 关于 bean 存储时⽣成的命名规则了。我们可以以在 Idea 中使⽤搜索关键字“beanName”可以得到以下结果。

顺藤摸⽠,我们最后找到了 bean 对象的命名规则的⽅法: 

 它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,源码如下:

public static String decapitalize(String name) {if (name == null || name.length() == 0) {return name;}// 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&Character.isUpperCase(name.charAt(0))){return name;}// 否则就将⾸字⺟⼩写char chars[] = name.toCharArray();chars[0] = Character.toLowerCase(chars[0]);return new String(chars);
}

至此,这也就能够解释我们bean对象命名的原理了。

4.5 方法注解@Bean

public class Users {@Beanpublic User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}
}

然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到:

public class Application {public static void main(String[] args) {ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");User user=context.getBean("user", User.class);System.out.println(user.toString());}
}

那么为什么直接使用方法注解会报错呢?

答案是方法注解要配合类注解使用。在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如 下代码所示:

@Component
public class Users {@Beanpublic User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}
}

 4.5.1 重命名 Bean

可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所示:

@Component
public class Users {@Bean(name = {"u1"})public User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}
}

此时我们使⽤ u1 就可以获取到 User 对象了,如下代码所示:

class App {public static void main(String[] args) {// 1.得到 spring 上下⽂ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");// 2.得到某个 beanUser user=context.getBean("u1", User.class);// 3.调⽤ bean ⽅法System.out.println(user);}
}

这个重命名的 name 其实是⼀个数组,⼀个 bean 可以有多个名字:

@Bean(name = {"u1", "us1"})
public User user1() {User user = new User();user.setId(1);user.setName("Java");return user;
}

此时我们既可以使用u1获取User对象,也可以使用us1来获取User对象。

4.6 获取Bean 对象(对象装配) 

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊

对象装配(对象注⼊)的实现⽅法以下 3 种:

1. 属性注⼊

2. 构造⽅法注⼊

3. Setter 注⼊

下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中。

4.6.1 属性注入 

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。

Service 类实现如下:

package com.beans;import org.springframework.stereotype.Service;@Service
public class UserService {public void sayHi() {System.out.println("你好,Service");}
}

Controller 类的实现代码如下:

@Controller
public class UserController2 {//属性注入@Autowiredprivate UserService userService;public void sayHi() {userService.sayHi();}
}

测试代码: 

public class App {public static void main(String[] args) {ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");UserController2 userController2=context.getBean("userController2", UserController2.class);userController2.sayHi();}
}

结果如下:

 属性注入的核心如下:

 4.6.2 构造方法注入

构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:

public class UserController3 {private UserService userService;@Autowiredpublic UserController3(UserService userService){this.userService=userService;}public void sayHi() {userService.sayHi();}
}

4.6.3 Setter注入

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解,如下代码所示:

public class UserController4 {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService=userService;}
}

4.6.4 三种注入方式的优缺点分析

1.属性注⼊的优点是简洁,使用方便;缺点是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只 有在使⽤的时候才会出现 空指针异常(这会导致不能及时发现bug)。

2.构造⽅法注⼊是 Spring 推荐的注⼊⽅式,它的缺点是如果有多个注⼊会显得⽐较臃肿,但出现这种 情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了,它的优点是通⽤性,在使⽤之 前⼀定能把保证注⼊的类不为空。

3.Setter ⽅式是 Spring 前期版本推荐的注⼊⽅式,但通⽤性不如构造⽅法(因为不同语言Setter并不完全一样),所有 Spring 现版本已经 推荐使⽤构造⽅法注⼊的⽅式来进⾏类注⼊了。

PS:虽然Spring官方推荐使用构造方法注入,但实际上Spring很多使用的都是属性注入,并且在实际项目中属性注入也是使用的比较多的一种方式

4.6.5  @Resource:另⼀种注⼊关键字 

在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊,如下代码所示:

@Controller
public class UserController2 {//属性注入@Resourceprivate UserService userService;public void sayHi() {userService.sayHi();}
}

@Resource用法和@Autowired基本一致,稍有区别我们会在下面提到

4.6.7 @Autowired 和 @Resource 的区别

出身不同:@Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解。

用法不同:@Autowired支持属性注入,构造方法注入和Setter注入。而@Resource不支持构造方法注入。

支持的参数不同:@Resource支持更多的参数设置,比如name,type设置。而@Autowired只支持required参数设置。

4.7 同⼀类型多个 @Bean 报错 

当出现以下多个 Bean,返回同⼀对象类型时程序会报错,如下代码所示:

@Component
public class Users {@Beanpublic User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}@Beanpublic User user2() {User user = new User();user.setId(2);user.setName("MySQL");return user;}
}
@Controller
public class UserController4 {// 注⼊@Resourceprivate User user;public User getUser() {return user;}
}

运行以上代码就会报错,原因是⾮唯⼀的 Bean 对象。

解决同⼀个类型,多个 bean 的解决⽅案有以下两个:

使⽤ @Resource(name="user1") 定义。(通过重命名来区分不同的对象)

使⽤ @Qualifier 注解定义名称。(此注解类似于过滤器的功能)

① 使⽤ @Resource(name="XXX")

@Controller
class UserController4 {// 注⼊@Resource(name = "user1")private User user;public User getUser() {return user;}
}

 ② 使⽤ @Qualifier

@Controller
public class UserController5 {// 注⼊@Autowired@Qualifier(value = "user2")private User user;public User getUser() {return user;}
}

5.bean的作用域与生命周期

假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,然⽽在使⽤的途中 A ⽤户却“悄悄”地修改 了公共 Bean 的数据,导致 B ⽤户在使⽤时发⽣了预期之外的逻辑错误。

我们预期的结果是,公共 Bean 可以在各⾃的类中被修改,但不能影响到其他类。但实际上公共的bean数据却被修改了。

 这里我们就不放实例代码了,大家只需要知道结果就行。

原因分析:

操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton 单例模式。

5.1 作用域定义

限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。

而 Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,比如 singleton 单例作用域,就表 示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个人读取到的就是被修改的值。

5.2 Bean 的 6 种作用域

Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。

Spring有 6 种作⽤域, 最后四种是基于 Spring MVC ⽣效的

1. singleton:单例作⽤域

2. prototype:原型作⽤域(多例作⽤域)

3. request:请求作⽤域

4. session:回话作⽤域

5. application:全局作⽤域

6. websocket:HTTP WebSocket 作⽤域

注意后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种。 

5.2.1 singleton 

描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例

获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀ 个对象。

场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新

备注:Spring默认选择该作⽤域

5.2.2 prototype 

描述:每次对该作⽤域下的Bean的请求都会创建新的实例

获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的 对象实例。

场景:通常有状态的Bean使⽤该作⽤域

5.2.3 request

描述:每次http请求会创建新的Bean实例,类似于prototype

场景:⼀次http的请求和响应的共享Bean

备注:限定SpringMVC中使⽤

5.2.4 session

描述:在⼀个http session中,定义⼀个Bean实例

场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息

备注:限定SpringMVC中使⽤

5.2.5 application(了解)

描述:在⼀个http servlet Context中,定义⼀个Bean实例

场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息

备注:限定SpringMVC中使⽤

5.2.6 websocket(了解)

描述:在⼀个HTTP WebSocket的生命周期中,定义⼀个Bean实例

场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息 头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。

备注:限定Spring WebSocket中使⽤

5.2.7 单例作用域(singleton)和全局作用域(application)区别

singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;

singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。

5.2.8 设置 bean的作用域 

为了解决我们开始提出的那个问题,我们可以将bean的作用域由单例作用域(singleton)设置成原型作用域(prototype)。这里我们需要使用@Scope注解。

@Scope("prototype")//直接设置值
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//使用枚举设置

这里我们比较常使用第二种方式,因为有idea提示不容易错。其余作用域的设置也是类似的。

5.3 Bean的生命周期 

5.3.1 bean的执行流程

启动 Spring 容器 -> 实例化 Bean(分配内存空间,从⽆到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)

5.3.2 bean的生命周期 

所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命 周期。 Bean 的⽣命周期分为以下 5 ⼤部分:

1.实例化 Bean(为 Bean 分配内存空间)

2.设置属性(Bean 注⼊和装配)

3.Bean 初始化

a)执行各种通知(Aware)

b)执行初始化前置方法

c)执行构造方法,两种执行方式,一种是执行@PostConstruct(优先执行),另一种实质性inti-method(在xml配置文件中设置的)。这两种构造方法实际上是两个时代的产物。

d)执行初始化后置方法

4.使用bean

5.销毁bean

a)执行@PreDestroy

b)重写DisposableBean接口方法

c)destroy-method

 5.3.3 实例化和初始化的区别

实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可人工干预和修改;⽽初始化给开发者提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理。

5.3.4 为什么要先设置属性在进行初始化 

也就是我们能否将步骤二和步骤三的顺序调换呢?我们看到下面一段代码。

@Service
public class UserService {public UserService(){System.out.println("调⽤ User Service 构造⽅法");}public void sayHi(){System.out.println("User Service SayHi.");}
}
@Controller
public class UserController {@Resourceprivate UserService userService;@PostConstructpublic void postConstruct() {userService.sayHi();System.out.println("执⾏ User Controller 构造⽅法");}
}

对于上述的代码,假如我们先执行@PostConstruct(构造方法),再去设置属性(@Resource注入)。那么我们会发现构造方法会用到userService这个属性,而假如这个属性还未被注入此时执行构造方法就会引发空指针异常,所以要先设置属性在进行初始化 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/250887.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Spring入门程序

目录 一、Spring概述二、Spring的体系结构三、Spring的核心容器四、实现步骤 一、Spring概述 spring是当前主流的JavaWeb框架&#xff0c;它是为了解决应用开发复杂性问题而产生的。对于一个Java应用程序员来说&#xff0c;掌握spring框架的使用&#xff0c;是我们基本的一项技…

Spring入门这一篇就够了

前言 前面已经学习了Struts2和Hibernate框架了。接下来学习的是Spring框架…本博文主要是引入Spring框架… Spring介绍 Spring诞生&#xff1a; 侵入式概念 Spring是一种非侵入式的框架… 侵入式 非侵入式 松耦合 前面我们在写程序的时候&#xff0c;都是面向接口编程&#xff…

Spring入门,看这篇就够了

Spring入门&#xff0c;看这篇就够了 文章目录 Spring入门&#xff0c;看这篇就够了一、 初识Spring二、Spring Framework系统架构三、核心概念1.IoC&#xff08;inversion of control&#xff09;控制反转2.Bean3.DI&#xff08;dependency injection&#xff09;依赖注入 四、…

Spring 入门教程

Spring 入门教程 1、参考资料 尚硅谷-Spring5框架最新版教程&#xff08;idea版&#xff09;雷丰阳spring、springmvc、mybatis、spring一站式学习 项目地址&#xff1a;Oneby / spring-learn 2、Spring 概述 2.1、Spring 框架概述 Spring 是轻量级的开源的 JavaEE 框架 Sp…

一文学会Spring,Spring最简单的入门教程(万字好文)

1.Spring概述 1.1 Spring框架是什么 ​ Spring是与2003年兴起的一个轻量级的Java开发框架&#xff0c;它是为了解决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IOC)和面向切面编程(AOP)。Spring是可以在Java SE/EE中使用的轻量级开源框架。 ​ Spring的主要作用…

阿里云的内容识别技术可以实现哪些场景下的智能化应用?

阿里云的内容识别技术可以实现哪些场景下的智能化应用&#xff1f; [本文由阿里云代理商[聚搜云]撰写]   随着人工智能技术的快速发展&#xff0c;阿里云借助自身的技术和资源优势&#xff0c;开发了一种名为“内容识别”的技术。这项技术能够高效、准确地识别出图片、视频、…

装饰器模式:实现类功能的动态扩展

一&#xff0c;简介 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许在不修改原有类结构的情况下&#xff0c;给一个对象动态添加额外的职责。通常情况下&#xff0c;扩展一个类的功能我们首先会想到用继承方式来实现&#xff0c…

One2Multi Graph Autoencoder for Multi-view Graph Clustering

One2Multi Graph Autoencoder for Multi-view Graph Clustering | Proceedings of The Web Conference 2020 (acm.org) 目录 Abstract 1 Introduction 2 Model 2.1 Overview 2.2 One2Multi Graph Convolutional Autoencoder Informative graph convolutional encoder M…

linux常见的二十多个指令

目录 一、指令的概念 二、28个常见的指令 ⭐2.1 ls指令 ⭐2.2 pwd指令 ⭐2.3 cd指令 ⭐2.4tree指令 ⭐2.5 mkdir指令 ⭐2.6 touch指令 ⭐2.7 rmdir指令 ⭐2.8 rm指令 ⭐2.9 clear指令 ⭐2.10 man指令 ⭐2.11 cp指令 ⭐2.12 mv指令 ⭐2.13 cat指令&#xff08;适…

Python画五角星(turtle初识)

Python可以做很多事情&#xff0c;主要归功于python下的各种模块。画图也不例外&#xff0c;通过简单地turtle模块&#xff0c;可以画出各种图案。 首先&#xff0c;使用python画图&#xff0c;需要安装turtle模块。在控制台输入pip install turtle执行 import turtle turtle.…

用python的turtle画五角星

最近开始学python&#xff0c;做作业的时候遇到画五角星并填充颜色。网上搜到的方法是画五条直线&#xff0c;但是作业要求的图形是这样 #用循环结构画五角星 import turtle from turtle import *turtle.delay(2) #定义一个画五角星的函数 def stardraw(start_position,side)…

Python turtle教程一:画箭头、矩形,五角星(亲测,可用)

Turtle库是Python语言中一个很流行的绘制图像的函数库&#xff0c;想象一个小乌龟&#xff0c;在一个横轴为x、纵轴为y的坐标系原点&#xff0c;(0,0)位置开始&#xff0c;它根据一组函数指令的控制&#xff0c;在这个平面坐标系中移动&#xff0c;从而在它爬行的路径上绘制了图…

初学Python画五角星

这学期开的Python课&#xff0c;之前学过java&#xff0c;c还有HTML5/css&#xff0c;不过感觉自己并不是学这些的料&#xff0c;这个是老师布置的作业&#xff0c;让我们自己画个图&#xff0c;就画了个简单的&#xff0c;心血来潮想写个博客&#xff0c;就直接上图了吧&#…

python画多层次五角星

此次用到海龟绘图函数——turtle&#xff0c;下面是程序中可能会用到的方法 forward() #前进 right() #右转 exitonclick() #绘制完退出Next ,show time! 1.创建一个python项目文件 2.分析单个五角星画法 我们默认每次画笔右转144度画五角星的一个角&#xff0c;那么画笔右转…

python画五角星

python画五角星 今天刚学python海龟画图&#xff0c;课堂作业画五角星&#xff0c;顺便分享给大家。 运行图片如下&#xff1a; 代码如下&#xff1a; # 以画布中心为中点&#xff0c;向右为X轴正方向&#xff0c;向上为Y轴正方向 import turtle turtle.setup(500, 500) #…

HTML5-画一个简单五角星

HTML5-画一个简单五角星 <!DOCTYPE HTML><html><body><canvas id"myCanvas" width"200" height"200" style"border:1px solid #c3c3c3;" mce_style"border:1px solid #c3c3c3;">Your browser does …

玩转Python之Turtle画五角星

Turtle库是Python语言中一个很流行的绘制图像的函数库&#xff0c;下面就给大家分享用Turtle画五角星&#xff1a; #codingutf-8 import turtle import time turtle.pensize(5) turtle.pencolor(“yellow”) turtle.fillcolor(“red”) turtle.begin_fill() for _ in range(5…

使用 opencv 画 五角星

// 绘制多边形 void CV120201012Dlg::mutiplyDraw() { // 定义一张背景板 Mat img Mat(Size(800, 800), CV_8UC3); img Scalar(0, 0, 0); // 定义5个顶点坐标 Point p1(439, 126); Point p2(466, 198); Point p3(546, 198); Point p4(482, 244);…

用OpenGL画五角星

第一次写博客&#xff0c;正在上大二&#xff0c;学的计算机专业&#xff0c;因为课很杂&#xff0c;想把自己学过的东西顺便写下来做个记录&#xff0c;写的不好请见谅&#xff01; 这个学期学的图形学&#xff0c;主要是讲OpenGL的东西&#xff0c;老师让我们自己用GL_LINES、…

画七彩五角星

using (Graphics graphics this.CreateGraphics()){graphics.Clear(Color.White); Point[] points {//顺时针点坐标new Point(80, 0), new Point(100, 60), new Point(160, 60), new Point(110, 100),new Point(130, 160), new Point(80, 120), new Point(30, 160), new Poi…