Spring核心和设计思想
文章目录
- 1.Spring是什么?
- 什么是容器?
- 什么是IoC呢?
- 控制反转程序开发
- 理解Spring IoC
- DI概念
- Spring的创建和使用
- 创建Spring项目
- 添加Spring框架支持
- 创建启动类和main
- 存储Bean对象
- 创建Bean对象
- 将Bean注册到Spring容器中
- 总结
- 更简单的Spring读取和存储对象
- 存储Bean对象
- 配置扫描路径
- 添加注解存储Bean对象
- 注意Bean的命名
- 使用Bean注解来存储对象
- 重命名Bean
- 获取Bean对象(对象装配)
- 属性注入
- 构造方法注入
- Setter注入
- 三种注入的优缺点分析
- @Resource:另⼀种注⼊关键字
- @AutoWired和Resource注解的区别
- 同⼀类型多个 @Bean 报错
- Bean的作用域和生命周期
- 作用域定义
- Bean的六种作用域
- singleton
- prototype(原型对象)
- Request
- session
- application(了解)
- websocket(了解)
- 单例作⽤域(singleton)和全局作⽤域(application)区别
- Bean的原理分析
- Bean的执行流程![在这里插入图片描述](https://img-blog.csdnimg.cn/712852be395049b58ddc74d1a0cff59f.png)
- Bean的生命周期
- 生命周期演示
1.Spring是什么?
我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞 ⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的 应⽤程序开发起来更简单。
⽤⼀句话概括 Spring:Spring 是包含了众多⼯具⽅法的 IoC 容器
由此引发的问题是:什么是容器? 什么又是Ioc容器???
什么是容器?
容纳某种物品的装置
比如前面学数据结构中的List / Map (容纳数据)等等
包括 Tomcat (Web容器)
什么是IoC呢?
Ioc实际上是控制反转的意思 —> Inversion of control
如何理解控制反转?
传统程序开发
假设我们去构建一辆车,利用传统的程序思想进行开发…
构建⼀辆⻋(Car Class),然⽽⻋需要依赖⻋身(FrameWork Class),⽽⻋身需要依赖底盘(Bottom Class),⽽底盘需要依赖轮胎(Tire Class),最终程序的实现代码如下
```java
在这里插入代码片
```public class NewCarExample {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);}}
}
通过代码我们发现了一些规律, Car类依赖于Framework,也就是new Car的时候,Framework也被创建了,并且调用init方法,接下来调用的init方法又自动去new Bottom,因为通过这些类的含义知道,他们之间是相互依赖的,如果你想去建造一辆车,就需要去保证这种依赖关系
但是这种设计思想也有缺陷,当你满足不同客户的需求的时候,轮胎的尺寸不同,你就要去修改参数,又或者你想加别的参数,可能解决方法是 在每个类中加上参数,通过参数传递就行了呀.实际上这种方式问题很大,因为这几个类的依赖性很强,我们不断的因为客户的需求而修改类的代码是很麻烦而且很容易出Bug的行为.
传统程序开发的缺陷:
以上程序中,轮胎的尺⼨的固定的,然⽽随着对的⻋的需求量越来越⼤,个性化需求也会越来越多,这 时候我们就需要加⼯多种尺⼨的轮胎,那这个时候就要对上⾯的程序进⾏修改了
public class NewCarExample2 {public static void main(String[] args) {Car car = new Car();car.init(50, "猛男粉");}/*** 汽车对象*/static class Car {public void init(int size, String color) {// 依赖车身Framework framework = new Framework();framework.init(size, color);}}/*** 车身类*/static class Framework {public void init(int size, String color) {// 依赖底盘Bottom bottom = new Bottom();bottom.init(size, color);}}/*** 底盘类*/static class Bottom {public void init(int size, String color) {// 依赖轮胎Tire tire = new Tire();tire.init(size, color);}}/*** 轮胎类*/static class Tire {// 尺寸
// private int size = 30;public void init(int size, String color) {System.out.println("轮胎尺寸:" + size + " | 颜色:" + color);}}
}
这种就是不断的修改类中的代码,实际上并不能完全解决问题反而会出很大的问题.
从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修 改。
我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃ ⼰也要跟着修改。 此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需 要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本身也⽆需修改任 何代码,这样就完成了程序的解耦。 解决传统开发中的缺陷
PS:解耦指的是解决了代码的耦合性,耦合性也可以换⼀种叫法叫程序相关性。好的程序代码的耦合 性(代码之间的相关性)是很低的,也就是代码之间要实现解耦
实际上IoC就是解耦操作
这就好⽐我们打造⼀辆完整的汽⻋,如果所有的配件都是⾃⼰造,那么当客户需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃身是不需要出⼒的
控制反转程序开发
基于上述思路,之前我们是通过在构造方法中创建依赖类的方法,现在换一种思路就是改为 依赖注入的方法
public class IocCarExample {public static void main(String[] args) {Tire tire = new Tire(50, "红色");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;private String color;public Tire(int size, String color) {this.size = size;this.color = color;}public void init() {System.out.println("轮胎:" + size + " | 颜色:" + color);}}
}
仔细观察这种代码跟上面的有什么区别???
这是很重要的一点
当我们new Car的时候,已经创建好了framework并将framework注入到car的构造方法中,并且 car.run中去执行framework的init方法
每个类的init方法实际上都是去执行下一个类的init方法
反正这种代码是很精妙的,需要自己好好看看,博主在这也说不太懂,毕竟刚学…
但是最重要的一点是,你跟着代码的思路走发现他确确实实解决了上树的问题,无论我们怎么去改或者加多少个参数,都是很简单的事情,只需要在new Tire那里和 Tire类本身进行修改即可
这就是IoC思想,接下里的两张图可以进行对比一下
我们发现了⼀个规:,通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了 Framework,Framework 创建并创建了 Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是 上级对象创建并控制下级对象了,⽽是下级对象把注⼊将当前对象中,下级的控制权不再由上级类控制 了,这样即使下级类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实 现思想。
在传统设计里面,我们new 的是Car,但是改的是Tire,由于改了Tire就要改Bottom等等
在IoC中,我们通过注入依赖的方式,改的还是Tire,但是我们已经将Tire注入到Bottom中去了,所以Bottom也随之改变了,之后也是如此,所以他主要是对之前的控制顺序进行了反转,所以IoC叫做控制反转
IoC的设计思想说完了,那么如何去理解Spring是一个Ioc容器呢???
理解Spring IoC
既然 Spring 是⼀个 IoC(控制反转)容器,重点还在“容器”⼆字上,那么它就具备两个最基础的功能: 将对象存⼊到容器; 从容器中取出对象。 也就是说学 Spring 最核⼼的功能,就是学如何将对象存⼊到 Spring 中,再从 Spring 中获取对象的过 程。
将对象存放到容器中的好处:将对象存储在 IoC 容器相当于将以后可能⽤的所有⼯具制作好都放到仓 库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。⽽ new 对象的⽅式相当于,每次需要⼯具 了,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做,这就是 IoC 容器和普通程序开 发的区别。
Spring 是⼀个 IoC 容器,说的是对象的创建和销毁的权利都交给 Spring 来管理了,它本身⼜具备了存 储对象和获取对象的能⼒。
DI概念
说到 IoC 不得不提的⼀个词就是“DI”,DI 是 Dependency Injection 的缩写,翻译成中⽂是“依赖注 ⼊”的意思。 所谓依赖注⼊,就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。所以,依 赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容 器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。 IoC 是“⽬标”也是⼀种思想,⽽⽬标和思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就 属于具体的实现。
DI就是我们讲的依赖注入的实现,通过将下级类注入进去的具体实现
所以建议大家多去看看这种通过注入方式来实现的代码,这对我们如何去设计代码是很重要的
Spring的创建和使用
Spring作为容器,最重要的两个功能就是:
- 将对象存储到容器中
- 将对象从容器中取出来
实际上这里存放的内容就是对象特叫做Bean
创建Spring项目
接下来使⽤ Maven ⽅式来创建⼀个 Spring 项⽬,创建 Spring 项⽬和 Servlet 类似,总共分为以下 3 步:
- 创建⼀个普通 Maven 项⽬。
- 添加 Spring 框架⽀持(spring-context、spring-beans)。
- 添加启动类。
虽然是Spring项目,但是还是基于maven的,这根Spring Boot不一样的
添加Spring框架支持
在项⽬的 pom.xml 中添加 Spring 框架的⽀持,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>
从上面的依赖可以看到,添加了两个框架:
- spring-context Spring上下文
- spring-beans 管理对象模块
这跟Servlet很像的,都是需要去添加依赖来下载jar包
这里需要去设置依赖源,博主这里设置的国内的阿里源,最重要的是需要勾选住,否则后期下载别的依赖就会很慢(下载国外的源 网络不太行)
创建启动类和main
到这里Spring的配置和创建就完成了
存储Bean对象
存储 Bean 分为以下 2 步:
- 存储 Bean 之前,先得有 Bean 才⾏,因此先要创建⼀个 Bean。
- 将创建的 Bean 注册到 Spring 容器中。
创建Bean对象
将Bean注册到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.bit.User"></bean>
</beans>
在beans里面每一个bean都代表一个注册类,id就不是类名,而是beanName
class代表的是注册类存放的位置
写到这里之后,意味着bean注册成功
然后就可以利用spring-context上下文来将bean对象取出来用
这里通过Application来进行创建的,当然还有别的方法
ApplicationContext 和 BeanFactory 效果是⼀样的,ApplicationContext 属于 BeanFactory 的⼦类, 它们的区别如下。
ApplicationContext VS BeanFactory(常⻅⾯试题):
相同点:都从容器中获取bean,并且都有getBean方法
不同点1、ApplicationContext属于BeanFactory的子类。BeanFactory 只提供了基础访问Bean法,
而ApplicationContext除了拥有BeanFactory的所有功能之外,还提供了更的方法实现,比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持。
2、从性能方面来说二者是不同,BeanFactory 是按需加载Bean,ApplicationContext是饿汉方式,
在创建时会将所有的Bean都加载起来,以备以后使用。
PS:⽽ ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,是通过 xml 的配置来获取所有的 Bean 容器的
第一种里面的"userinfo"要跟当时的bean id对应,并且返回的是一个object类型还需要强转才能拿到
第二种里面直接通过类型进行获取,但是假设同一个类型注册了多次就会出现问题,他要保证注册次数是唯一
这就是注册两次的情况,所以不建议使用
第三种就是双重保险
总结
- 先去创建maven项目并且去pom.xml中注入依赖spring-context 和spring-beans
- 添加配置文件 spring-config.xml
- 创建Bean
- 在spring-config.xml中注入Bean
- 利用spring-context得到Application对象,并且使用getBean()获取对象
- 使用对象
更简单的Spring读取和存储对象
经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们 发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的 ⽅法。
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相 关注解,来存储和读取 Bean 对象。
存储Bean对象
之前我们在注册类的时候,每注册一个类都要写一行配置,现在可以通过注解的方式来更方便的完成
在开始存对象之前,需要准备工作
配置扫描路径
注意:想要将对象成功的存储到 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=" ">
</content:component-scan>
</beans>
base-package就是设置的扫描路径,你可以把想要注册的类都放到这个路径下,他就会自动去扫描注册
也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的
添加注解存储Bean对象
想要将对象存储在 Spring 中,有两种注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
- ⽅法注解:@Bean。
接下来我们分别来看。 使⽤ @Controller 存储 bean 的代码如下所示:
@Controller // 将对象存储到 Spring 中
public class UserController {public void sayHi(String name) {System.out.println("Hi," + name);}
}
更简单的注册结束了,还是利用原来的方式进行上下文的读取
public class Application {public static void main(String[] args) {// 1.得到 spring 上下⽂ApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");// 2.得到 beanUserController userController = (UserController)
context.getBean("userController");// 3.调⽤ bean ⽅法userController.sayHi("Bit");}
}
类注解一共是五类,分别是
- @Controller(控制器)
- @Service(服务)
- @Repository(仓库)
- @Component(组件)
- Configuration(配置)
其实这些类注解的功能都是一样的,加了某一个类注解并且在扫描路径下,就可以完成注册类工作
但是不同的业务逻辑用到的类注解是不一样的,他也是为了提供程序员的开发效率
既然功能是⼀样的,为什么需要这么多的类注解呢?
这和为什么每个省/市都有⾃⼰的⻋牌号是⼀样的?⽐如陕⻄的⻋牌号就是:陕X:XXXXXX,北京的⻋ 牌号:京X:XXXXXX,⼀样。甚⾄⼀个省不同的县区也是不同的,⽐如⻄安就是,陕A:XXXXX,咸 阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样。这样做的好处除了可以节约号码之外,更重要的作 ⽤是可以直观的标识⼀辆⻋的归属地。 那么为什么需要怎么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途,⽐如: @Controller:表示的是业务逻辑层; @Servie:服务层; @Repository:持久层; @Configuration:配置层。
一个完成的程序必然是有很逻辑化的步骤顺序来执行的,直到Resposity这一层才能和数据库进行交互
这些类注解之间的关系通过源码可以看到
注意Bean的命名
通过上⾯示例,我们可以看出,通常我们 bean 使⽤的都是标准的⼤驼峰命名,⽽读取的时候⾸字⺟⼩ 写就可以获取到 bean 了,如下图所示:
实际上小驼峰的类名就是BeanName,因为现在采取更简单的存储Bean方法,所以不需要去一行一行的注册,通过注解的方式来实现第一种取出Bean,但是没有了ID
User user = (User) context.getBean(“user”);
此时getBean里面的参数就是类名,但是这个类名有点古怪
先说结论:
当类似于User的时候,beanName就是 user
当类似于UCompontent的时候,beanName就不变了
我们可以找BeanName命名方式的源码,进行查找
实际上beanName的命名方式是属于jdk而不是spring的jar包的
连续按两次shift出现查找框
使用Bean注解来存储对象
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到
因为Bean注解是方法注解,要配合着类注解一起使用才能生效
@Component
public class Users {@Beanpublic User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}
}
这样就大功告成了…
重命名Bean
在上面代码的基础上加上这样一行:
@Bean(name = {“u1”})就表示重命名了
@Component
public class Users {@Bean(name = {"u1"})public User user1() {User user = new User();user.setId(1);user.setName("Java");return user;}
}
这样做法意味着程序员不一定就要拿引用名去作为getBean的参数,当你觉得这个引用名字不好,你就可以采取这种重命名来设置,一旦你设置了重命名之后,引用名就不行了
此时我们使⽤ u1 就可以获取到 User 对象了,当然了设置name是可以有多个,无论你设置多少个name都可以获取到对象,并且 name={} 可以省略
获取Bean对象(对象装配)
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊
对象装配有三种实现方法:
- 1.属性注入
- 构造方法注入
- Setter注入
下⾯我们按照实际开发中的模式,将 Service(注解) 类注⼊到 Controller(注解) 类中
属性注入
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中
代码如下:
Service类
import org.springframework.stereotype.Service;
@Service
public class UserService {/*** 根据 ID 获取⽤户数据** @param id* @return*/public User getUser(Integer id) {// 伪代码,不连接数据库User user = new User();user.setId(id);user.setName("Java-" + id);return user;}
}
Controller类代码:
因为是在Controlller中注入Service,所以在Controlller中加入了
private UserService userService,@Autowired注解就表示将这个类注入到Controller中, 名字就是userService,通过这个名字去调用注入类的方法和属性
这种注入方式是属性注入, 注入Bean跟前面的注册类是两码事,之前的注册类意思是将Bean存入到Spring仓库中,随取随用.这里的注入类,是将一个类注入到另一个类中,也符合我们之前讲的IoC容器思想和DI
获取 Controller 中的 getUser ⽅法
结果如下;
属性注入的核心就是 在创造注入类的引用前面加上@AutoWired注解
构造方法注入
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:
@Controller
public class UserController2 {// 注⼊⽅法2:构造⽅法注⼊private UserService userService;@Autowiredpublic UserController2(UserService userService) {this.userService = userService;//已经注入了,赋值即可}public User getUser(Integer id) {return userService.getUser(id);}
}
当前类如果只有一个构造方法,就不需要去加注解,有多个构造方法的时候,必须指定某一个方法嘉善注解,才能注入成功
跟上面的属性注入不同的是,构造方法注入的注解是加在构造方法的上面,并且将引用作为参数传了进来,然后就可以在别的方法中去使用注入类了,这也是官方最推荐的一种。
Setter注入
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注 解,如下代码所示:
@Controller
public class UserController3 {// 注⼊⽅法3:Setter注⼊private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public User getUser(Integer id) {return userService.getUser(id);}
}
当然了,Setter如果不加@AutoWired自然是不能注入成功的…
三种注入的优缺点分析
- 属性注⼊的优点是简洁,使⽤⽅便;缺点是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只 有在使⽤的时候才会出现 NPE(空指针异常)。
- 构造⽅法注⼊是 Spring 推荐的注⼊⽅式,它的缺点是如果有多个注⼊会显得⽐较臃肿,但出现这种 情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了,它的优点是通⽤性,在使⽤之 前⼀定能把保证注⼊的类不为空。
- Setter ⽅式是 Spring 前期版本推荐的注⼊⽅式,但通⽤性不如构造⽅法,所有 Spring 现版本已经 推荐使⽤构造⽅法注⼊的⽅式来进⾏类注⼊了。
属性注入收到了容器的限制,只能在Ioc容器中可以注入,但是它很简便
构造方法注入的通用性很强,并且构造方法注入是可以确保使用对象之前,注入对象已经初始化过了,然后开始构造,避免出现空指针,先去注入之后,在执行构造方法,并且构造方法的参数过多时,开发者就要检查自己的代码哦是否符合单一设计原则规范.
Setter注入很单纯,避免了构造方法的注入传多个参数,但是Setter的通用性没构造方法通用性强
虽然官方推荐构造方法注入,但是我们是实践中还是来使用属性注入
@Resource:另⼀种注⼊关键字
在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊,如 下代码所示:
@Controller
public class UserController {// 注⼊@Resourceprivate UserService userService;public User getUser(Integer id) {return userService.getUser(id);}
}
@AutoWired和Resource注解的区别
- 出身不同:@Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解;
- 使⽤时设置的参数不同:相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
- @Autowired 支持三种注入方式,但是@Resource是不支持构造方法注入的
同⼀类型多个 @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的作用域和生命周期
从前⾯的课程我们可以看出 Spring 是⽤来读取和存储 Bean,因此在 Spring 中 Bean 是最核⼼的操作 资源,所以接下来我们深⼊学习⼀下 Bean 对象
通过⼀个案例来看 Bean 作⽤域的问题
假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,然⽽在使⽤的途中 A ⽤户却“悄悄”地修改 了公共 Bean 的数据,导致 B ⽤户在使⽤时发⽣了预期之外的逻辑错误。(说好⼀起到⽩头,你却悄悄 焗了油)。
被修改的Bean
公共Bean
@Component
public class Users {@Beanpublic User user1() {User user = new User();user.setId(1);user.setName("Java"); // 【重点:名称是 Java】return user;}
}
A用户在使用时,进行了修改操作
@Controller
public class BeanScopesController {@Autowiredprivate User user1;public User getUser1() {User user = user1;System.out.println("Bean 原 Name:" + user.getName());user.setName("悟空"); // 【重点:进⾏了修改操作】return user;}
}
B用户再去使用的时候
@Controller
public class BeanScopesController2 {@Autowiredprivate User user1;public User getUser1() {User user = user1;return user;}
}
打印A和B用户使用公共Bean的值
public class BeanScopesTest {public static void main(String[] args) {ApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");BeanScopesController beanScopesController =
context.getBean(BeanScopesController.class);System.out.println("A 对象修改之后 Name:" +
beanScopesController.getUser1().toString());BeanScopesController2 beanScopesController2 =
context.getBean(BeanScopesController2.class);System.out.println("B 对象读取到的 Name:" +
beanScopesController2.getUser1().toString());}
}
说明A在修改之后,B再去打印发现公共Bean也被修改了
原因分析:
操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同 ⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton 单例模式
作用域定义
限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表 示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个 ⼈读取到的就是被修改的值。
Bean的六种作用域
Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域, 最后四种是基于 Spring MVC ⽣效的:
- singleton:单例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
注意后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种
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默认选择该作⽤域
无状态的意思是说明Bean的属性不需要更改,在这种情况下默认就是singleton模式
prototype(原型对象)
- 官⽅说明:Scopes a single bean definition to any number of object instances.
- 描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的 对象实例。
- 场景:通常有状态的Bean使⽤该作⽤域
有状态就是说 这个Bean很可能会被修改或者一定被修改,你就需要设置成prototype作用域,这样他每次都是去创建新的Bean
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
- 备注:限定SpringMVC中使⽤,他跟在Spring中的prototype是很像的,每次请求都会创建新的对象
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, ⽐如:记录⼀个⽤户的登陆信息
- 备注:限定SpringMVC中使⽤
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中使⽤
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中使⽤
单例作⽤域(singleton)和全局作⽤域(application)区别
- singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域
- singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。
Bean的原理分析
Bean的执行流程
Bean 执⾏流程(Spring 执⾏流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从⽆到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)。
Bean的生命周期
所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命 周期。
Bean 的⽣命周期分为以下 5 ⼤部分:
1 实例化Bean(分配内存空间)
2 设置属性(Bean的注入和装配)
3 Bean的初始化
- 实现了各种 Aware 通知的⽅法,如 BeanNameAware、 BeanFactoryAware、 ApplicationContextAware 的接⼝⽅法;
- 执⾏ BeanPostProcessor 初始化前置⽅法;
- 执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏;
- 执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);
- 执⾏ BeanPostProcessor 初始化后置⽅法。
4 使用Bean
5 销毁Bean
生命周期演示
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class BeanLifeComponent implements BeanNameAware {@PostConstructpublic void postConstruct() {System.out.println("执⾏ PostConstruct()");}public void init() {System.out.println("执⾏ init-method");}@PreDestroypublic void preDestroy() {System.out.println("执⾏:preDestroy()");}public void setBeanName(String s) {System.out.println("执⾏了 setBeanName ⽅法:" + s);}
}
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.bit.component">
</content:component-scan><beans><bean id="beanLifeComponent"
class="com.bit.component.BeanLifeComponent" init-method="init"></bean></beans>
</beans>
调用类
import com.bit.controller.BeanLife;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {public static void main(String[] args) {ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring-config.xml");BeanLife life = context.getBean(BeanLife.class);System.out.println("执⾏ main ⽅法");// 执⾏销毁⽅法context.destroy();}
}
这个结果跟上面的不太一样,但是没啥太大区别,主要就是第一条和第四条是没写的,因为第一条是Aware没有重写,然后就是使用bean这是正常方法,也没写其余的顺序是一致的总结:本节课介绍了 Bean 的 6 种作⽤域:
- singleton:单例作⽤域
- prototype:原型作⽤域(多例作⽤域)
- request:请求作⽤域
- session:回话作⽤域
- application:全局作⽤域
- websocket:HTTP WebSocket 作⽤域
其中前两种是 spring 核⼼作⽤域,⽽后 4 种是 spring mvc 中的作⽤域,也介绍了 spring 的执⾏ 流程和 bean 的⽣命周期,其中 bean 的作⽤域是最重要的知识点也是常⻅的⾯试题,⽽ bean ⼤ 的执⾏流程也⼀定要牢记。
Spring核心和设计思想相关推荐
- JavaEE进阶 - Spring 核心 与 设计思想 - 细节狂魔
文章目录 Spring 是什么? 什么是容器? 什么是 IoC?- 通常就是面试中关于框架的第一个问题. 传统程序的开发 总结 理解 Spring IoC DI 总结 Spring 是什么? 我们通常 ...
- Spring为什么这么火 之 Spring蕴含的设计思想
目录 Spring是什么? 1.什么是IoC? 2.传统程序开发 3.控制反转式程序开发 4.理解Spring IoC 5.依赖注入[DI] 总结 前言 很多人说:"Java程序员都是Spr ...
- RocketMQ核心架构设计思想
RocketMQ中NettyRemotingServer的Reactor多线程模型 图1 1.一个 Reactor 主线程(eventLoopGroupBoss)负责监听 TCP网络连接请求建立好连接 ...
- Spring 框架蕴含的设计思想
在 Google Guava 源码讲解中,我们讲到开发通用功能模块的一些比较普适的开发思想,比如产品意识.服务意识.代码质量意识.不要重复早轮子等.今天,我们剖析一下 Spring 框架背后的一些经典 ...
- OSGI框架的功能和设计思想
摘录自InfoQ电子书:<OSGi原理与最佳实践(精选版).pdf> 支持模块化的动态部署 基于 OSGi 而构建的系统可以以模块化的方式(例如 jar 文件等)动态地部署至框架中,从而增 ...
- 从面向对象设计思想出发理解Spring AOP编程
都说AOP是OOP(面向对象)的继承与延续,我觉得理解AOP还是得从OOP出发,经历从暴露问题到解决问题的过程. 目录 一. 面向对象设计思想(OOP) (1)概述 1. POP编程 2. OOP的优 ...
- 《小马哥讲Spring核心编程思想》-第一章学习笔记(1)
<小马哥讲Spring核心编程思想>-第一章学习笔记(1) 一.课程介绍 1.为什么要学习spring? 2.深入学习spring的难点有哪些? 3.课程的设计思路是怎样的? 二.内容综述 ...
- 小马哥spring编程核心思想_小马哥讲Spring核心编程思想
小马哥讲Spring核心编程思想 ├─第01章:Spring Framework总览 (12讲) │ 01丨课程介绍.mp4 │ 02丨内容综述.mp4 │ 03丨课前准 ...
- 透彻理解Spring事务设计思想之手写实现
2019独角兽企业重金招聘Python工程师标准>>> 前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原 ...
最新文章
- socket connec连接超时处理
- servlet核心API的UML图
- leetcode面试题 08.03. 魔术索引(二分)
- Pycharm不能用了
- python中sklearn中的Imputer模块改动
- 启动和停止数据库——停止例程
- Linux环境下分析和排查系统故障
- 前端接收到的Url参数有中文乱码
- jmeter 控制偏离_Jmeter 笔记(1)-安装 基本组件
- 《Web漏洞防护》读书笔记——第7章,访问控制防护
- 供应链业务架构设计概览
- netbsd apache php mysql,NetBSD配置aria2的web前端YAAW笔记
- 欧拉筛素数的应用-漂亮数
- vue获取facebook用户邮箱、头像并登录
- stripe海外支付php教程
- matlab 广义最小二乘,广义最小二乘辨识的matlab实现
- js 万年历农历转阳历 方法_JS实现带阴历的日历功能详解
- 世界上最强大的两个字母的单词
- Blender:Lowpoly手部建模流程(附blender源文件下载)
- OpenStack ironic裸金属部署(裸金属作为独立服务)