首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring基础

Spring基础

原创
作者头像
羽毛球初学者
发布2024-10-22 17:05:32
发布2024-10-22 17:05:32
2480
举报
文章被收录于专栏:JAVA基础知识JAVA基础知识

Spring框架概述

Spring框架是一个全面的、企业应用开发一站式的解决方案,贯穿表现层、业务层、持久层,主要包括以下七个模块:

  • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
  • Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务;
  • Spring AOP:AOP服务;
  • Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器;
  • Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现;
  • Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
  • Spring ORM:对现有的ORM框架的支持;

Spring-core

Spring-core 提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用 IOC 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

IOC

控制反转(Inversion of Control)指的是创建对象的控制权发生转移,以前创建对象的主动权和创建时机由应用程序把控,而现在这种权利转交给 IoC 容器。

在Spring中,ApplicationContext 接口表示 Spring IoC 容器,负责实例化、配置和装配 bean。容器通过读取配置元数据获取关于实例化、配置和组装对象的指令。配置元数据可以用 XML、 Java 注释或 Java 代码表示,它允许用户表达组成应用程序的对象以及这些对象之间丰富的相互依赖关系。ApplicationContext继承了BeanFactory接口。

DI

依赖注入 (Dependency Injection) 是 IoC 的具体实现方式。在 Spring 中,我们可以通过构造函数注入、Setter 注入和接口注入等多种方式为对象注入依赖。DI的核心是DIP(依赖倒置原则),即:

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

也就是说,将对具体类的引用转换成对其接口的引用,具体类只引用接口(引用==依赖,接口==接口或抽象类)。事实上我们调用具体类的时候,只关心其提供的API而非实现,DIP则通过在设计和重构阶段在技术手段上保证了解耦。依赖注入有三种方式:

  • setter注入:通过属性的set方法注入值,被注入类需要实现属性的setter方法。
代码语言:xml
复制
<!--  这里反射默认调用的是无参构造方法,如果不存在无参构造方法就会报错  -->
<bean id = "user" class="实体类的路径(包含到类名)">
    <!--  如果成员变量是简单类型(基本类型+String)用value赋值;如果是自定义引用类型用ref赋值  -->
    <property name="name" value="张三"/>
    <property name="userDao" ref="ud"/>
</bean>

<bean id = "ud" class="UserDao的路径(包含到类名)" />
  • 构造方法注入:通过属性的构造方法注入值,被注入类需要实现有参的构造方法。
代码语言:xml
复制
<!-- 这里反射调用的是有参构造方法,其中:
     index 为构造方法的第几个参数进行配置
     name 为构造方法的哪个名字的参数进行配置
     index 和 name 可以配置任何一个或同时配置,但要求一旦配置必须正确(推荐优先使用index方式配置,防止没有源码造成name无法匹配到对应参数)
     type 为该构造方法参数的类型
     value 为该构造方法参数的值 ,用来指定基本值
     ref 为该构造方法参数的值,用来指定引用其他bean的值
-->
<bean id = "user" class="实体类的路径(包含到类名)">
	<constructor-arg index="0" type="java.lang.String" name="name" value="李四"></constructor-arg>
    <constructor-arg index= "1" name="userDao" ref="ud"/>
</bean>

<bean id = "ud" class="UserDao的路径(包含到类名)" />
  • 注解注入:注解注入分为@Autowired注入和@Resource 注入,这种方式容易出现循环依赖问题。
    • @Autowired:自动装配,可标注在构造、接口、方法上默认根据类型注入(byType)
    • @Resource:默认按照名称装配,可以标注在字段或属性的setter方法上。默认按照字段的名称(byName)去Spring容器中找依赖对象,如果没有找到,退回到按照类型查找。

Spring 三级缓存

为了解决以来循环问题,Spring使用了三级缓存技术。在jdk源码中,三级缓存是这样定义的:

代码语言:java
复制
// 一级缓存Map 存放完整的Bean(流程跑完的)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

// 二级缓存Map 存放不完整的Bean(只实例化完,还没属性赋值、初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

// 三级缓存Map 存放一个Bean的lambda表达式(也是刚实例化完)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

那么他们三个是如何解决循环依赖问题的呢?

设想一下没有三级缓存的循环依赖问题:ServiceA依赖ServiceB,ServiceB依赖ServiceA。ServiceA实例化,然后在属性填充的时候,发现依赖ServiceB。在Spring容器中找ServiceB,没有找到。ServiceA暂停属性注入,开始实例化ServiceB,然后在属性填充的时候,发现依赖ServiceA,于是又在Spring容器中找ServiceA,同样没有找到,这时候程序就出问题了。

加入了三级缓存后,实例化过程变成这样:ServiceA实例化,之后将实例化的不完整的实例ServiceA放入三级缓存。然后在属性填充的时候,发现ServiceA依赖ServiceB。在Spring容器中的一级缓存、二级缓存和三级缓存中找ServiceB,没有找到。ServiceA暂停属性注入,开始实例化ServiceB,同样将不完整的ServiceB实例放入三级缓存。然后在属性填充的时候,发现依赖ServiceA,于是又在Spring容器中找ServiceA,先找了一级缓存和二级缓存,没找到。在三级缓存中找到了不完整的实例ServiceA,然后将ServiceB从三级缓冲中移除,放入二级缓存,然后成功的对ServiceB进行了属性填充和初始化操作,然后从二级缓存移除,放入一级缓存。ServiceA继续属性注入,依次放入二级缓存和一级缓存。

分析上面场景时发现,要单纯解决循环依赖,其实只要有两级缓存就够了,那么为什么要用三级缓存呢?答案是因为Spring需要支持AOP。在Bean被AOP进行了切面代理之后,三级缓存中的singletonFactory获取到的对象实例是目标Bean的一个代理对象。每次获取到的都是新的代理对象,就破坏了Spring解决循环依赖问题的基础(所有的对象都是单例的)。而加入了二级缓存以后,代理对象也是只获取一次,然后放入二级缓存备用。

Spring事务

在Java应用程序中,事务可以使用 JDBC 或 Java Persistence API(JPA)进行管理。 Spring 框架支持声明式和编程式事务管理。 

  1. 在声明式事务管理中,可以使用注释或XML配置声明如何管理事务。
  2. 在编程式事务管理中,可以使用编程方式管理事务。

事务管理器

事务管理器主要有三个接口:

  • PlatformTransactionManager:提供了管理事务的基本操作,如开始事务,提交事务和回滚事务。
  • TransactionDefinition:提供了事务的定义,如隔离级别,超时和传播行为。
  • TransactionStatus:提供了事务的状态,如是否已提交或已回滚。

Spring 框架提供了许多实现 PlatformTransactionManager 接口的类, 其中包括:

  • DataSourceTransactionManager: 用于在JDBC事务中使用。
  • JpaTransactionManager: 用于在JPA事务中使用。
  • HibernateTransactionManager: 用于在Hibernate事务中使用。

可以根据的需要选择使用哪个事务管理器。

事务传播机制

Spring 框架的事务传播机制用于定义在多个事务之间如何传播事务。例如,如果一个方法正在一个具有事务的上下文中执行,而该方法又调用另一个方法,那么应该如何处理事务? Spring 框架的事务传播机制定义了这种情况下的行为:

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;否则,创建一个新事务。
  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;否则,不使用事务。
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;否则,抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建一个新事务,并挂起当前事务(如果存在)。
  • PROPAGATION_NOT_SUPPORTED:不使用事务;如果当前存在事务,则挂起该事务。
  • PROPAGATION_NEVER:不使用事务,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务中执行;否则,创建一个新事务。

事务传播机制的默认值为 PROPAGATION_REQUIRED。这意味着如果一个方法在一个具有事务的上下文中执行,而该方法又调用另一个方法,则第二个方法将加入该事务。

事务失效场景

在项目中,出现以下情况会使事务失效:

  • service没有被Spring管理。因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。
  • 项目没有配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。
  • 事务方法被static、final关键字修饰,该方法无法被子类重写,无法在该方法上进行动态代理,导致Spring无法生成事务代理对象来管理事务。
  • 同一个类中,方法内部调用,方法A直接调用目标方法B的代码,而不是通过代理类进行调用。
  • 方法访问权限不是public,代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了。
  • 使用的数据库引擎不支持事务。
  • rollBackFor指定异常与方法抛出的异常不一致。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring框架概述
  • Spring-core
    • IOC
    • DI
    • Spring 三级缓存
  • Spring事务
    • 事务管理器
    • 事务传播机制
    • 事务失效场景
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档