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 容器)

image-20241116165855203

什么是Spring Bean

由Spring管理的对象叫做Bean,对象的生命周期(创建、初始化、使用、消耗等过程)完全由Spring管理

定义Bean

XML配置

早期的Spring应用通常通过XML文件定义Bean,使用标签来指定类、构造器参数和依赖关系。

注解

类级别的注解:@Component@Controller@Service@Repository@Configuration

方法级别的注解:@Bean

区别

@Component:这是一个通用的注解,用于将任意类注册为Spring容器中的Bean。它没有特定的语义,适用于任何需要Spring管理的类。
@Controller:这是一个专门用于Spring MVC中的控制器(Controller)层的注解。用于处理HTTP请求,并将结果返回给客户端
@Service:用于标识业务逻辑层的类。它具有明确的语义,表明该类承担业务操作,主要用于编写服务类。
@Repository:用于数据访问层(DAO)的类,与数据库交互。

Bean的生命周期

image-20241118101634533

注意细节,这幅图的颜色和上面那副有对应关系的

4cf3ce7de95a56013ca1951ea40c4a29

  • 实例化

    Spring容器启动时,根据配置文件或者注解,通过Java反射API来创建Bean实例

  • 依赖注入

    Spring容器通过构造器、setter等方法为Bean注入各种资源,如属性和其他Bean依赖等

  • 初始化

  • 初始化前

    1. 如果Bean类实现了aware类型的接口,则会执行aware注入。如实现BeanNameAware设置Bean的名字

    2. 如果Bean类实现了BeanPostProcessor接口,则可以重写其中的postProcessBeforeInitialization方法来自定义初始化前的操作

  • 初始化

    调用InitializingBean接口的afterPropertiesSet()方法或通过init-method属性指定的初始化方法。

  • 初始化后

    如果Bean类实现了BeanPostProcessor接口,则可以重写其中的postProcessAfterInitialization方法来自定义初始化后的操作

  • 使用

  • 销毁

  • 销毁前

    如果Bean实现了DisposableBean接口或Bean类中某个自定义方法使用了@PreDestroy注解,Spring会在容器关闭时调用销毁方法

  • 销毁

image-20241116141109954

Bean的作用域

一共6个

  1. 单例(Singleton)

    在Spring中,Bean的默认作用域是Singleton,IoC容器中只有唯一的bean实例

  2. 原型(prototype)

​ 多例。每次从容器中获取Bean时都会创建一个新的实例

  1. request:

    每个请求都会新建一个属于自己的Bean实例,这种作用域仅存在Spring Web应用中

  2. session

    作用范围为http会话,在同一个http会话中公用同一个bean,这种作用域仅存在Spring Web应用中

  3. applicantion:

    每个 Web 应用在启动时创建一个 Bean,这种作用域仅存在Spring Web应用中

  4. 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 中,确保线程独立。

  • 使用同步机制: 利用 synchronizedReentrantLock 来进行同步控制,确保线程安全。

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方法进行代理

image-20241117172224555

Spring AOP和AspectJ

Spring AOP

​ Spring AOP是Spring框架提供的AOP实现,基于动态代理实现,在运行时,对类进行代理增强

AspectJ

​ AspectJ是一个独立的AOP框架,基于静态代理实现,可以在编译时、类加载时、运行时对类进行代理增强

在编译阶段对程序源代码进⾏修改,⽣成了静态的 AOP代理类(⽣成的 *.class ⽂件已经被改掉了,需要使⽤特定的编译器)。

image-20241117174111489

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单轻量

五、Spring MVC

MVC

Model-View-Controller

  • 模型层

    模型代表应用程序的数据和业务逻辑。它独立于用户界面和用户交互,负责处理数据的存取、操作和验证。

  • 视图层

    视图是用户界面的呈现方式,负责展示数据

  • 控制器层

    控制器充当模型和视图之间的中介,负责处理用户输入、更新模型数据并选择合适的视图进行展示

工作流程

image-20241118092411968

前端控制器就像一个指挥官,所有的请求都要经过他调度

  1. 前端控制器接收请求

  2. 处理器映射器中查询handler,handler中包含了处理这个请求所需要调用的方法信息如controller、service等等

    处理器映射器是一个map结果,key为请求路径,value为处理这条请求路径所需要执行的方法信息

  3. 处理器映射器返回 处理器执行链前端控制器

    因为有拦截器的存在,所以执行一个请求的时候,并不是 能够直接调用对应的方法的,而是先经过一系列拦截器后才能执行对应的方法,并且执行完对应的方法后,后续还是有一些操作

    所以处理器映射器返回的是 处理器执行链,而不是直接返回所需要执行的方法

    处理器执行链中包含了,处理这条请求前,需要做什么,处理完这条请求后又需要做什么 的逻辑

  4. 前端控制器寻找处理器适配器,适配器处理请求参数(如路径参数,或请求体中的参数),让这些参数能够跟代码中的参数对应上

    image-20241118094532174

  5. 处理器适配器根据handler寻找具体的方法去执行

  6. handler执行完毕后,返回ModelAndView给处理器适配器处理器适配器对返回值进行处理,让返回值变成前端认识的形式

  7. 处理器适配器返回ModelAndView给前端控制器

  8. 前端控制器根据返回的ModeAndView使用视图解析器将逻辑视图解析为实际的视图

  9. 视图渲染引擎根据数据模型渲染视图,并将生成的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中采用了三级缓存的方式来解决循环依赖

只有同时满足以下两点,才能解决循环依赖问题

  1. 依赖的Bean必须都是单例的

    如果Bean不是单例的话,A依赖B,B依赖新的A,新的A又依赖新的B,新的B又依赖新的新的A,无穷无尽,没有形成闭环而是无穷无尽的依赖新的对象,这种无法打破

  2. 注入的方式,必须不全是构造器注入、

    因为缓存生效的范围是在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缓存的生效范围

image-20241118104819035

  • 一级缓存

    缓存已经完全初始化了的单例Bean

    一级缓存并不能解决循环依赖问题,因为只能缓存已经完全初始化了的Bean,如果两个Bean相互依赖,那么两个Bean就都不能初始化,都不会把自身暴露给对方,双方就都无法完成初始化

  • 二级缓存

    缓存尚未完全初始化已实例化的Bean(已经调用了构造函数),用于提前暴露对象,避免循环依赖问题

    image-20241118105753010

    当对象创建成功放入一级缓存/单例池 中后,就会把二级缓存中的对象给清除掉

    二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP)时,直接使用二级缓存不做任何处理会导致我们拿到的Bean是未代理的原始对象。

  • 三级缓存

​ 缓存对象对应的对象工厂,对象工厂可以生成普通的对象,也可以生成代理对象

当三级缓存生成 的对象注入完毕进入二级缓存后,三级缓存中的工厂就会被清理

当二级缓存中的半成品对象注入完毕进入一级缓存后,二级缓存中的半成品对象就会被清理

image-20241118111244711

image-20241118111341123

总结

  • 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的代理

image-20241118115250121

七、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,使它们共用同一个事务,我起了个名字叫做重入事务

  1. Propagation.REQUIRED:require(需要),如果当前中存在事务,则加入该事务,如果不存在事务,则新建一个事务

    Spring默认的事务传播级别

  2. Propagation.REQUIRES_NEW:requires_new,创建新的事务,如果当前中存在事务,则先挂起当前的事物

  3. Propagation.SUPPORTS:supports(支持),如果当前中存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行

    如果事务调用他修饰的方法,那么该方法就会加入调用他的事务

    如果调用他的不是事务,而是普通的调用,则该方法也会以普通的方式运行

  4. Propagation.NOT_SUPPORTS:not_supports,不支持事务,始终以非事务的方式执行

    如果有外部事务调用了他,则他会先把外部事务挂起来,自己以非事务的方式运行完了,再恢复外部事务

  5. Propagation.MANDATORY:mandatory(强制的),如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常

  6. Propagation.NEVER:never(从不),不允许当前中存在事务,如果存在则抛出异常、

不允许事务调用他修饰的方法,如果有事务调用他修饰的方法,他就会报出异常

  1. 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
}

事务失效的场景

  1. rollbackFor没设置对,默认只有在遇到RuntimeException(运行时异常) 或者 Error 时才会回滚事务,如果出现了其他异常就不会回滚

  2. 在写代码的时候把异常catch了,但是没有throw,这样事务也无法检测到异常,不会回滚

  3. 同类之间的事务方法相互调用,被调用的方法的事务不会生效

    因为Spring的事物是基于动态代理技术实现了,而同类之间的方法调用,是普通的方法调用,不会触发spring的动态代理技术

    而事务的传播机制是在

    @Service
    public class MyService {
    
       @Transactional
       public void A() {
           B();
       }
    
       @Transactional
       public void B() {
        System.out.println("方法B执行了....");
       }
    
    
    }
    
  4. @Transactional应用在finalstatic非public、方法上时,事务也不会生效

    因为spring 的事物是基于动态代理实现了,

    非public方法的访问有限制,不能被代理,所以无法让事务生效

    final修饰的方法无法被子类重写, 所以也不能被代理,无法让事务生效

    static修饰的方法,属于类级别的方法,也不能被继承到代理对象中重写,无法代理,无法生效事务

  5. 事务传播属性的指定设计,例如以下的代码(伪代码,忽略同一个类中的方法调用影响代理的情况

    在设计了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);
    }
    
  6. 多线程环境下,也无法保存事务的一致性

    因为@Transactional是基于ThreadLocal存储上下文的,每个代理对象都会绑定到当前线程的事务上下文中,多线程情况下每个线程都有自己的上下文,事务之间无法保持一致性

  7. 如果数据库的引擎不支持事务的话,用@Transactional也没用

TODO:Spring的启动流程

  1. 加载配置文件,初始化容器

    Spring会读取配置文件(xml,java config等),包括配置数据库连接这些配置

  2. 实例化容器

    根据配置信息创建ApplicationContext,实例化BeanFactory,加载容器中的BeanDefinitions

  3. 解析BeanDefinition

    解析配置文件中的BeanDefinitions,即声明的Bean元数据,包括Bean的作用域、依赖关系等信息。

  4. Bean生命周期

    实例化、依赖注入、初始化

  5. 发布事件

  6. 启动完成

@Autoware和@Resoure的区别

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。

  • Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。

  • 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的 Bean。

    Autowired 可以通过 @Qualifier 注解来显式指定名称,@Resource可以通过 name 属性来显式指定名称。

  • @Autowired 支持在构造函数、方法、字段和参数上使用。@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

文章作者: 落叶知秋
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 落叶知秋
喜欢就支持一下吧