Spring原理
一、Spring的核心模块
Core Container
Core Container(核心容器)
Spring Core
:提供了依赖注入(DI)、控制反转(IOC)的实现,是Spring 框架基本的核心工具类,别的模块都会依赖此模块。Spring Beans
:负责对Bean的创建和管理Spring Context
:提供对国际化、事件传播、资源加载等功能Spring Expression Language
:一个强大的表达式语言,用于在运行时查询和操作对象的值
AOP
AOP(面向切面编程)
Spring AOP:
提供面向切面编程的功能,可以在方法执行前后或抛出异常时动态插入额外的逻辑,比如日志记录、权限校验、事务管理等
Data Access
Data Access(数据访问)
Spring JDBC
:提供了原生JDBC操作,Java程序只需要和JDBC API交互就能管理数据库连接Spring ORM:
支持与主流的ORM框架集成,简化开发。(如Hibernate、JPA、MyBatis等)Spring Transaction(事务管理):
提供声明式和编程式的事务管理机制,与数据库操作密切结合
Web
Spring Web
:对Web功能的实现提供一些最基础的支持。比如Servlet API的集成Spring MVC
:实现了Model—View—Controller(MVC)模式的框架,用于构建基于HTTP请求的Web应用。它是一个常用的模块,支持注解驱动的Web开发。Spring WebFlux
:提供基于Reactive Streams的响应式编程模型,专为高并发的异步非阻塞请求设计。
二、Spring、Spring MVC、Spring Boot
Spring
包含了很多模块,其中最重要的是Spring Core模块,Spring的其他模块(如Spring MVC)都依赖于他
Spring MVC
是Spring中的一个模块,依赖于Spring。用于构建基于MVC模式的Web应用程序
SpringBoots
是基于Spring构建的,他整合了Spring和Spring MVC,简化了Spring 的很多配置,能让开发者能够快速的构建应用
Spring框架提供了基础功能
Spring MVC用于Web开发
Spring Boot则是一个工具,旨在简化Spring应用程序的构建和部署过程
三、Spring IoC
IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
为什么叫控制反转?
控制:指的是对象创建(实例化、管理)的权力
反转:控制权交给外部环境(Spring 框架、IoC 容器)
什么是Spring Bean
由Spring管理的对象叫做Bean,对象的生命周期(创建、初始化、使用、消耗等过程)完全由Spring管理
定义Bean
XML配置
早期的Spring应用通常通过XML文件定义Bean,使用<bean>标签来指定类、构造器参数和依赖关系。
注解
类级别的注解:@Component
、@Controller
、@Service
、@Repository
、@Configuration
方法级别的注解:@Bean
区别
@Component
:这是一个通用的注解,用于将任意类注册为Spring容器中的Bean。它没有特定的语义,适用于任何需要Spring管理的类。 @Controller
:这是一个专门用于Spring MVC中的控制器(Controller)层的注解。用于处理HTTP请求,并将结果返回给客户端 @Service
:用于标识业务逻辑层的类。它具有明确的语义,表明该类承担业务操作,主要用于编写服务类。 @Repository
:用于数据访问层(DAO)的类,与数据库交互。
Bean的生命周期
注意细节,这幅图的颜色和上面那副有对应关系的
实例化
Spring容器启动时,根据配置文件或者注解,通过Java反射API来创建Bean实例
依赖注入
Spring容器通过构造器、setter等方法为Bean注入各种资源,如属性和其他Bean依赖等
初始化
初始化前
如果Bean类实现了
aware
类型的接口,则会执行aware注入。如实现BeanNameAware设置Bean的名字如果Bean类实现了
BeanPostProcessor
接口,则可以重写其中的postProcessBeforeInitialization
方法来自定义初始化前的操作
初始化
调用InitializingBean接口的afterPropertiesSet()方法或通过init-method属性指定的初始化方法。
初始化后
如果Bean类实现了
BeanPostProcessor
接口,则可以重写其中的postProcessAfterInitialization
方法来自定义初始化后的操作
使用
销毁
销毁前
如果Bean实现了DisposableBean接口或Bean类中某个自定义方法使用了@PreDestroy注解,Spring会在容器关闭时调用销毁方法
销毁
Bean的作用域
一共6个
单例(Singleton)
在Spring中,Bean的默认作用域是Singleton,IoC容器中只有唯一的bean实例
原型(prototype)
多例。每次从容器中获取Bean时都会创建一个新的实例
request:
每个请求都会新建一个属于自己的Bean实例,这种作用域仅存在Spring Web应用中
session:
作用范围为http会话,在同一个http会话中公用同一个bean,这种作用域仅存在Spring Web应用中
applicantion:
每个 Web 应用在启动时创建一个 Bean,这种作用域仅存在Spring Web应用中
websocket
每一次 WebSocket 会话产生一个新的 bean,这种作用域仅存在Spring Web应用中
Bean线程安全问题
Bean类是否是线程安全的,取决于其作用域和状态
prototype
作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。singleton
作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)有状态: 是指包含可变的成员变量的对象
// 定义了一个购物车类,其中包含一个保存用户的购物车里商品的 List @Component public class ShoppingCart { private List<String> items = new ArrayList<>(); public void addItem(String item) { items.add(item); } public List<String> getItems() { return items; } }
不过,大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
// 定义了一个用户服务,它仅包含业务逻辑而不保存任何状态。 @Component public class UserService { public User findUserById(Long id) { //... } //... }
对有状态的单例Bean解决线程安全问题
避免可变成员变量: 尽量设计 Bean 为无状态。
使用
ThreadLocal
: 将可变成员变量保存在ThreadLocal
中,确保线程独立。使用同步机制: 利用
synchronized
或ReentrantLock
来进行同步控制,确保线程安全。
public class UserThreadLocal {
private UserThreadLocal() {}
private static final ThreadLocal<SysUser> LOCAL = ThreadLocal.withInitial(() -> null);
public static void put(SysUser sysUser) {
LOCAL.set(sysUser);
}
public static SysUser get() {
return LOCAL.get();
}
public static void remove() {
LOCAL.remove();
}
}
四、Spring AOP
什么是AOP
面向切面编程,就是将那些于业务无关,但是被业务模块公共调用的逻辑封装起来,在原来的业务模块上,进行无侵入的功能增强。在保证开发者不修改源代码的情况下,为系统中业务组件添加某种通用的功能
比如日志管理、事务处理、权限控制、统计
如果把输出日志的逻辑,在每一个业务逻辑中都写一遍,会十分重复繁琐,而且本身也不是和业务有关的逻辑,把日志逻辑写进业务里,会不合适
用aop来写就可以解决上面的问题
AOP原理
Spring AOP基于两种动态代理技术实现:
JDK动态代理:Spring Aop的首选方式。如果要代理的对象,实现了某个接口,那么就会使用这种方式去创建代理对象
JDK动态代理只能对实现了接口的类生成代理,对于没有实现接口的类无法进行代理
JDK动态代理使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来实现代理
CGLIB代理:如果要代理的对象,没有实现接口, 会使用 Cglib 生成一个被代理对象的子类来作为代理
CGLIB通过继承目标类来生成代理类,因此无法对
final
类和final
方法进行代理
Spring AOP和AspectJ
Spring AOP
Spring AOP是Spring框架提供的AOP实现,基于动态代理实现,在运行时,对类进行代理增强
AspectJ
AspectJ是一个独立的AOP框架,基于静态代理实现,可以在编译时、类加载时、运行时对类进行代理增强
在编译阶段对程序源代码进⾏修改,⽣成了静态的 AOP代理类(⽣成的 *.class ⽂件已经被改掉了,需要使⽤特定的编译器)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单轻量
五、Spring MVC
MVC
Model-View-Controller
模型层
模型代表应用程序的数据和业务逻辑。它独立于用户界面和用户交互,负责处理数据的存取、操作和验证。
视图层
视图是用户界面的呈现方式,负责展示数据
控制器层
控制器充当模型和视图之间的中介,负责处理用户输入、更新模型数据并选择合适的视图进行展示
工作流程
前端控制器就像一个指挥官,所有的请求都要经过他调度
前端控制器
接收请求在
处理器映射器
中查询handler,handler中包含了处理这个请求所需要调用的方法信息如controller、service等等处理器映射器
是一个map结果,key为请求路径,value为处理这条请求路径所需要执行的方法信息处理器映射器
返回 处理器执行链 到前端控制器
中因为有拦截器的存在,所以执行一个请求的时候,并不是 能够直接调用对应的方法的,而是先经过一系列拦截器后才能执行对应的方法,并且执行完对应的方法后,后续还是有一些操作
所以
处理器映射器
返回的是 处理器执行链,而不是直接返回所需要执行的方法处理器执行链中包含了,处理这条请求前,需要做什么,处理完这条请求后又需要做什么 的逻辑
前端控制器
寻找处理器适配器
,适配器处理请求参数(如路径参数,或请求体中的参数),让这些参数能够跟代码中的参数对应上处理器适配器
根据handler寻找具体的方法去执行handler执行完毕后,返回ModelAndView给
处理器适配器
,处理器适配器
对返回值进行处理,让返回值变成前端认识的形式处理器适配器
返回ModelAndView给前端控制器
前端控制器
根据返回的ModeAndView使用视图解析器
将逻辑视图解析为实际的视图视图渲染引擎根据数据模型渲染视图,并将生成的HTML响应返回给客户端
六、循环依赖
循环依赖,指多个模块之间相互依赖,形成闭环,导致无法确定和加载初始化顺序。
比如模块A依赖模块B,模块B又依赖模块A,导致无法确定和加载初始化顺序
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
//或者自己依赖自己
@Service
public class A {
@Autowired
private A a;
}
Spring中采用了三级缓存的方式来解决循环依赖
只有同时满足以下两点,才能解决循环依赖问题
依赖的Bean必须都是单例的
如果Bean不是单例的话,A依赖B,B依赖新的A,新的A又依赖新的B,新的B又依赖新的新的A,无穷无尽,没有形成闭环而是无穷无尽的依赖新的对象,这种无法打破
注入的方式,必须不全是构造器注入、
因为缓存生效的范围是在Bean实例化之后,如果用的是构造器来注入其他依赖的话,循环依赖发生的时间段就是在实例化过程中,此时缓存无法生效
@Component public class A{ private B b; public A(B b){ System.out.println("A的构造方法执行了"); this.b=b; } } @Component public class B{ private A a; public B(A a){ System.out.println("B的构造方法执行了"); this.a=a; } }
三级缓存
解决缓存依赖的核心思路是:提前暴露未完全初始化的Bean
在了解三级缓存之前,我们先了解一下Bean缓存的生效范围
一级缓存
缓存已经完全初始化了的单例Bean
一级缓存并不能解决循环依赖问题,因为只能缓存已经完全初始化了的Bean,如果两个Bean相互依赖,那么两个Bean就都不能初始化,都不会把自身暴露给对方,双方就都无法完成初始化
二级缓存
缓存尚未完全初始化但已实例化的Bean(已经调用了构造函数),用于提前暴露对象,避免循环依赖问题
当对象创建成功放入一级缓存/单例池 中后,就会把二级缓存中的对象给清除掉
二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP)时,直接使用二级缓存不做任何处理会导致我们拿到的Bean是未代理的原始对象。
三级缓存
缓存对象对应的对象工厂,对象工厂可以生成普通的对象,也可以生成代理对象
当三级缓存生成 的对象注入完毕进入二级缓存后,三级缓存中的工厂就会被清理
当二级缓存中的半成品对象注入完毕进入一级缓存后,二级缓存中的半成品对象就会被清理
总结
Spring首先创建Bean实例,并将其加入三级缓存中(Factory)。
当一个Bean依赖另一个未初始化的Bean时,Spring会从三级缓存中获取Bean的工厂,并生成该Bean的代理对象。
代理对象存入二级缓存,解决循环依赖。
一旦所有依赖Bean被完全初始化,Bean将转移到一级缓存中。
懒加载
缓存作用的范围是在Bean生命周期中,实例化之后的部分,也就是调用了构造函数之后
那么如果在Bean实例化的时候就出现了循环依赖问题,我们可以使用懒加载来解决
@Component
public class A{
private B b;
public A(B b){
System.out.println("A的构造方法执行了");
this.b=b;
}
}
@Component
public class B{
private A a;
public B(A a){
System.out.println("B的构造方法执行了");
this.a=a;
}
}
只需要在参数中添加上@Lazy
注解即可
加上这个注解后,在初始A的时候,Spring会创建一个B的代理
七、Spring事务
Spring实现事务的两种方式
编程式事务:在代码中硬编码(用的少)
通过
TransactionTemplate
或者TransactionManager
手动管理事务声明式事务:在 XML 配置文件中配置或者直接基于注解
实际是通过 AOP 实现(基于
@Transactional
的全注解方式使用最多)
事务的传播行为
事务的传播行为(机制),主要是 定义和管理事务边界。
尤其是一个事务方法调用另一个事务方法时,事务如何传播的问题。它解决了多个事务方法嵌套执行时,是否要开启新事务、复用现有事务或者挂起事务等复杂情况。
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRED) //选择事务的传播机制
public void doSomething() {
// Your business logic here
}
}
注意: 以下说的“当前事务”指的是上一个方法的事务,是别人传递过去的,类似于重入锁,A方法和B方法都有事务,A方法调用B方法,A的事务会传递给B,使它们共用同一个事务,我起了个名字叫做重入事务
Propagation.REQUIRED
:require(需要),如果当前中存在事务,则加入该事务,如果不存在事务,则新建一个事务Spring默认的事务传播级别
Propagation.REQUIRES_NEW
:requires_new,创建新的事务,如果当前中存在事务,则先挂起当前的事物Propagation.SUPPORTS
:supports(支持),如果当前中存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行如果事务调用他修饰的方法,那么该方法就会加入调用他的事务
如果调用他的不是事务,而是普通的调用,则该方法也会以普通的方式运行
Propagation.NOT_SUPPORTS
:not_supports,不支持事务,始终以非事务的方式执行如果有外部事务调用了他,则他会先把外部事务挂起来,自己以非事务的方式运行完了,再恢复外部事务
Propagation.MANDATORY
:mandatory(强制的),如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常Propagation.NEVER
:never(从不),不允许当前中存在事务,如果存在则抛出异常、
不允许事务调用他修饰的方法,如果有事务调用他修饰的方法,他就会报出异常
Propagation.NESTED
:nested(嵌套的),如果当前事务存在,则在嵌套事务中执行,如果当前不存在事务,则新建事务
内层事务依赖外层事务,如果外层失败,则会回滚内层,内层失败不影响外层。
事务的隔离级别
5种
DEFAULT
(默认):使用底层数据库的默认隔离级别。如果数据库没有特定的设置,通常默认为READ_COMMITTED。
READ UNCOMMITTED
(读未提交):最低的隔离级别,允许事务读取尚未提交的数据,可能会导致脏读、不可重复读和幻读
READ COMMITTED
(读已提交):仅允许读取已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读问题。
REPEATABLE READ
(可重复读):确保在同一个事务内的多次读取结果一致,避免脏读和不可重复读,但可能会有幻读问题
SERIALIZABLE
(可串行化):最高的隔离级别,通过强制事务按顺序执行,完全避免脏读、不可重复读和幻读,代价是性能显著下降。
在Spring中,使用@Transactional
注解可以方便地设置事务的隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void method() {
// 事务逻辑
}
@Transactional(rollbackFor = Exception.class)
Exception
分为运行时异常 RuntimeException
和非运行时异常。
@Transactional
注解默认回滚策略是只有在遇到RuntimeException
(运行时异常) 或者 Error
时才会回滚事务,而不会回滚 Checked Exception
(受检查异常)。这是因为 Spring 认为RuntimeException
和 Error 是不可预期的错误,而受检异常是可预期的错误,可以通过业务逻辑来处理。
如果想要所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void someMethod() {
// some business logic
}
如果想要指定某些特定异常不会滚(以CustomException为例)
@Transactional(noRollbackFor = CustomException.class)
public void someMethod() {
// some business logic
}
事务失效的场景
rollbackFor
没设置对,默认只有在遇到RuntimeException
(运行时异常) 或者Error
时才会回滚事务,如果出现了其他异常就不会回滚在写代码的时候把异常catch了,但是没有throw,这样事务也无法检测到异常,不会回滚
同类之间的事务方法相互调用,被调用的方法的事务不会生效
因为Spring的事物是基于动态代理技术实现了,而同类之间的方法调用,是普通的方法调用,不会触发spring的动态代理技术
而事务的传播机制是在
@Service public class MyService { @Transactional public void A() { B(); } @Transactional public void B() { System.out.println("方法B执行了...."); } }
当
@Transactional
应用在final
、static
、非public
、方法上时,事务也不会生效因为spring 的事物是基于动态代理实现了,
非public方法的访问有限制,不能被代理,所以无法让事务生效
final修饰的方法无法被子类重写, 所以也不能被代理,无法让事务生效
static修饰的方法,属于类级别的方法,也不能被继承到代理对象中重写,无法代理,无法生效事务
事务传播属性的指定设计,例如以下的代码(伪代码,忽略同一个类中的方法调用影响代理的情况)
在设计了
propagation = Propagation.REQUIRES_NEW
的事物传播机制下,1中调用了2,但是2是新开了一个事务,那么1和2 ,其实就是两个相互独立的事物,互不影响
因为两个事务之间无法保持一致性
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void addUserAndAddress(User user,Address address) throws Exception { //1 userMapper.save(user); addAddress(address); //2 } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void addAddress(Address address) { //2 addressMapper.save(address); }
多线程环境下,也无法保存事务的一致性
因为
@Transactional
是基于ThreadLocal存储上下文的,每个代理对象都会绑定到当前线程的事务上下文中,多线程情况下每个线程都有自己的上下文,事务之间无法保持一致性
如果数据库的引擎不支持事务的话,用
@Transactional
也没用
Spring的启动流程
加载配置文件,初始化容器
Spring会读取配置文件(xml,java config等),包括配置数据库连接这些配置
实例化容器
根据配置信息创建ApplicationContext,实例化BeanFactory,加载容器中的BeanDefinitions
解析BeanDefinition
解析配置文件中的BeanDefinitions,即声明的Bean元数据,包括Bean的作用域、依赖关系等信息。
Bean生命周期
实例化、依赖注入、初始化
发布事件
启动完成
@Autoware和@Resoure的区别
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。@Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。