带你深入了解spring框架

本文详细介绍了Spring框架的核心概念,包括IOC、AOP,以及BeanFactory和ApplicationContext的区别。探讨了SpringBean的生命周期,强调了其非线程安全的特性,并分析了不同作用域下的线程安全问题。此外,还讨论了Bean的循环依赖和Spring拦截器与Servlet过滤器的区别,帮助读者深入理解Spring的内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

spring


首先,带大家回顾一下spring,相关知识点后期还会再更新。

spring复习

spring是一个轻量级的,非侵入式的,IOC,AOP,一站式的,简化企业级开发而生的。
它的核心包非常小。业务代码中不侵入框架代码。

  • IOC:控制反转。 将生成对象的权利反转给了spring框架 依赖注入DI为属性注入值
  • AOP:面向切面编程,将一些与业务代码无关的公共部分抽取出来,使用时,通过代理对象调用,从而达不修改源代码的基础上增加功能,代码的耦合度降低。
  • 一站式框架:数据持久层,web控制层…
那么如何搭建它呢?
  1. 创建spring配置文件 spring.xml文件
  2. 在spring.xml文件中配置需要让spring管理的类 bean下面有 id 、class 、 scope …
  3. spring框架读取xml文件,解析xml
  4. 通过工厂模式+反射机制 去 创建对象+代理模式
  5. 在需要使用对象时,从spring容器注入对象即可。
spring中常用的注解标签

@component
@Service
@controller

spring+jdbc

  1. JdbcTemplate
  2. 管理数据源(阿里巴巴 Druid)
  3. 事务管理
    事务管理的最基本的原理是使用AOP。
    事务传播行为是spring框架自身对事物进行功能上的增强。

BeanFactory 和 ApplicationContext

在spring容器中,BeanFactory接口是IOC容器要实现的最基础的接口,定义了管理bean的最基本的方法,例如获取实例、基本的判断等,如下图:

在这里插入图片描述
BeanFactory有多个子接口来进一步扩展bean相关的功能。
ApplicationContext也间接继承了BeanFactory,如果说BeanFactory是spring的心脏,那么ApplicationContext就是完整的身躯了。它们都可以当做spring的容器,spring容器是生成Bean实例的工厂,并管理容器中的Bean。
在这里插入图片描述
区别:

  1. BeanFactory是spring框架的基础设施,面向spring本身,是spring中比较原始的Factory,它不支持AOP、Web等spring插件。而ApplicationContext不仅包含了BeanFactory的所有功能,还支持spring的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。
  2. BeanFactory:需要等到获取某个bean的时候才会创建该bean,即延迟初始化。
  3. ApplicationContext:在启动spring容器时就会创建所有的bean(Web应用中推荐)

小结

总结:BeanFactory 和 ApplicationContext 区别

new User() 创建了一个User对象

把由spring框架创建的对象称为一个bean.

BeanFactory 接口是spring框架中最基础的接口, 定义了如何获取bean的方法.

ApplicationContext接口间接的继承了BeanFactory 接口,在此基础之上扩展功能.

BeanFactory 和ApplicationContext

BeanFactory 是最基础的,面向spring框架本身的. 在使用对象时才去创建

ApplicationContext是面向应用的,增加了功能(支持AOP 事务), 适合与web应用程序,在服务器启动时就创建.


springBean 的生命周期

一个对象什么时候创建 什么时候销毁
宏观上来讲,springBean的生命周期可以分为五个阶段:

  1. 实例化Instantiation( 创建一个原始的对象 例如 new对象 通过反射机制实现的(框架可以读到类名))
  2. 属性赋值 Populate(为对象的属性进行赋值)
  3. 初始化 Initialization(最早在Serlvet 先创建一个对象 然后调用init()进行初始化,我们的bean如果实现了某些接口,就可以去执行这些方法,用来初始化我们的对象重点在于对类进行功能提升. )
  4. 将 bean 对象放入到容器中使用(完整的 bean 创建好,将 bean 对象放入到容器中,可以使用)
  5. 销毁 Destruction(如果 Bean 实现 DisposableBean 执行 destroy)
    详解
    1.instantiate bean 对象实例化
    2.populate properties 属性赋值
    3.1 如果 Bean 实现 BeanNameAware 执行 setBeanName
    3.2 如果 Bean 实现 BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext 对象. 初始化 …
    3.3 如果存在类实现 BeanPostProcessor(AOP) ,执行 postProcessBeforeInitialization
    3.4 如果 Bean 实现 InitializingBean 执行 afterPropertiesSet 如果配置了自己的初始化方法<bean init-method="init">
    3.5 如果存在类实现 BeanPostProcessor(AOP) ,执行 postProcessAfterInitialization
    4.完整的 bean 创建好,将 bean 对象放入到容器中,可以使用
    5.销毁 如果 Bean 实现 DisposableBean 执行 destroy 如果配置了自己的销毁方法<bean destroy method="customerDestroy">指定销毁方法 customerDestroy.
    在这里插入图片描述
    在这里插入图片描述

那么spring中的bean是线程安全的吗?

不是线程安全的
Spring 容器中的 Bean 是否线程安全,容器本身并没有提供 Bean 的线程安全策 略,因此可以说 Spring 容器中的 Bean 本身不具备线程安全的特性,但是具体 还是要结合具体 scope 的 Bean 情况。
Spring 的 bean 作用域(scope)类型:

  1. singleton:单例,默认作用域。
  2. prototype:原型,每次创建一个新对象。
  3. request:请求,每次 Http 请求创建一个新对象,适用于WebApplication 环境下。
  4. Session 会话,同一个会话共享一个实例,不同会话使用不同的实例。
  5. Global-session 全局会话,所有会话共享一个实例。
    线程安全这个问题,要从单例与原型 Bean 分别进行说明。
    原型 Bean:
    对于原型 Bean,每次创建一个新对象,也就是线程之间并不存在 Bean 共享,自 然是不会有线程安全的问题。
    单例 Bean:
    对于单例 Bean,所有线程都共享一个单例实例 Bean,因此是存在资源的竞争。
    Bean的状态
  • 有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。

  • 无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。
    Bean 又分为

  • 有状态就是有数据存储功能(例如包含成员变量)

  • 无状态就是不会保存数据

如果单例 Bean,是一个无状态 Bean,也就是线程中的操作不会对 Bean 的成员执行查 询以外的操作,那么这个单例 Bean 是线程安全的。比如 Spring mvc 的 Controller、 Service、Dao 等,这些 Bean 大多是无状态的,只关注于方法本身。
但是如果 Bean 是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单 的办法就是改变 bean 的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求 Bean 就相当于是 new Bean() 这样就可以保证线程的安全了。
controller、service 和 dao 层本身并不是线程安全的,只是如果只是调用里面的方法, 而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存, 是安全的。

接下来,我们详细了解Bean的线程安全问题

关于Bean的线程安全问题,要从单例与多实例Bean分别进行说明。多实例Bean每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

对于单例Bean,所有线程都共享一个单例Bean实例,因此是存在资源的竞争。

如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。

对于有状态的bean,Spring官方提供的bean,一般提供了通过ThreadLocal去解决线程安全的方法,比如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等。

使用ThreadLocal的好处:在多线程场景下,多个线程对这个单例Bean的成员变量并不存在资源的竞争,因为ThreadLocal为每个线程保存线程私有的数据。这是一种以空间换时间的方式。

当然也可以通过加锁的方法来解决线程安全,这种以时间换空间的场景在高并发场景下显然是不实际的。

Spring中的Controller ,Service,Dao是不是线程安全的?controller、service和dao层本身并不是线程安全的,如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己线程的工作内存,是安全的。

下面以Controller为例,Service和Dao也是同样的道理。

@RestController
@Scope(value = "prototype") // 加上@Scope注解,他有2个取值:单例-singleton 多实例-prototype
public class TestController {
    private int var = 0;
    private static int staticVar = 0;
 
    @GetMapping(value = "/test_var")
    public String test() {
        System.out.println("普通变量var:" + (++var)+ "---静态变量staticVar:" + (++staticVar));
        return "普通变量var:" + var + "静态变量staticVar:" + staticVar;
    }
}

请求API三次,得到如下结果:
普通变量var:1—静态变量staticVar:1
普通变量var:1—静态变量staticVar:2
普通变量var:1—静态变量staticVar:3


虽然每次都是单独创建一个Controller,但是扛不住它的类变量呀!故即便是加上@Scope注解也不一定能保证Controller 100%的线程安全。所以是否线程安全在于怎样去定义变量以及Controller的配置。下面总结一下:

1、在@Controller/@Service/@Repository等容器中,默认情况下,scope值是单例-singleton的,也是线程不安全的。

2、尽量不要在@Controller/@Service/@Repository等容器中定义静态变量,不论是单例(singleton)还是多实例(prototype),她都是线程不安全的。

3、一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的。

4、对有状态的 bean 要使用 prototype。

5、作用域对无状态的 bean 使用 singleton 作用域。


小结:
servlet是线程安全的吗? serlvet是线程不安全的

servlet是单例的还是多例的?

是单例 <servlet> <servlet-class>

在服务器启动时由服务器创建,只创建了一个

class UserController{User user; //是单例bean  把一次请求看做是一个线程  很多个请求,那就是有多个线程 多个线程共享一个 bean//原型bean 是每次使用时,会创建一个对象,不共享  是线程安全UserService userService;

}

单例bean就是线程不安全的, 单例bean,多个线程共享同一个.

bean分为 有状态bean(这个变量可以存储数据.) 和 无状态bean(不存储数据的bean)

假设UserController,User,UserService 都是单例的

class UserController{User user; //User作为数据存储的对象,存储用户信息的  是有状态的,不安全UserService userService; //不存储数据, 是无状态的, 安全的.
}

原型bean是线程安全的.


Bean 循环依赖

JAVA 中循环依赖场景 ,很简单,就是 A 对象依赖了 B 对象,B 对象依赖了 A 对象。
在这里插入图片描述
那么循环依赖是个问题吗?
如果不考虑 Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的 事情。

 	static class A{
       B b;    //此时B对象有可能还没有创建 ?
    }

    static class B{
       A a;
    }
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        a.b = b;
        b.a = a;
        
    }

这样,A.B 就依赖上了
但是,在 Spring 中循环依赖就是一个问题了,为什么?
因为,在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的 Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。当然,在 Spring 中,出现循环依赖的场景很多,有的场景 Spring 自动帮我们解决了,而有的场景则需要程序员来解决。
产生循环依赖的问题,主要是:A 创建时–>需要 B----> B去创建—>需要 A,从而产生了循环。
在这里插入图片描述
spring 内部有三级缓存:

  1. singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的 bean 实例
  2. earlySingletonObjects 二级缓存,用于保存实例化完成的 bean 实例
  3. singletonFactories 三级缓存,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。
    A,B 循环依赖,先初始化 A,先暴露一个半成品 A,再去初始化依赖的 B,初 始化 B 时如果发现 B 依赖 A,也就是循环依赖,就注入半成品 A,之后初始化 完毕 B,再回到 A 的初始化过程时就解决了循环依赖,在这里只需要一个 Map 能缓存半成品 A 就行了,也就是二级缓存就够了,但是这个二级缓存存的是 Bean 对象,如果这个对象存在代理,那应该注入的是代理,而不是 Bean,此时二级 缓存无法及缓存 Bean,又缓存代理,因此三级缓存做到了缓存 工厂 ,也就是 生成代理,这我的理解:总结起来:二级缓存就能解决缓存依赖,三级缓存解决 的是代理。
    在这里插入图片描述

Servlet 的过滤器与 Spring 拦截器区别

(1)实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则 是基于 Java 的反射机制(动态代理)实现的。
(2)使用范围不同
我们看到过滤器 ,实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。
(3)触发时机不同
过滤器 和 拦截器的触发时机也不同,我们看下边这张图。

在这里插入图片描述
过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。
拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预 处理的,Controller 中渲染了对应的视图之后请求结束。
(4)拦截的请求范围不同
过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对 Controller 中 请求或访问 static 目录下的资源请求起作用。


小结:

总结:Servlet 的过滤器与 Spring 拦截器区别

区别主要在底层实现方式上有所不同:

  • 过滤器实现是依赖于tomcat,请求会先到达过滤器,然后进入Servlet.

  • spring拦截器: 是框架内部封装的, 请求是先到达servlet, 根据映射地址,去匹配拦截器,最终到达控住器


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值