Spring基础
什么是spring框架
- Spring 框架是一个用于构建企业级 Java 应用程序的开源框架。【Java项目快速构建轻量级框架】
- 我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。【根据模块说明】
- 作用:【简化开发】【框架整合】
Spring 是一个轻量级Java框架,有七大模块,分别为 数据,Web,切面,工具,消息,核心,测试
- 核心模块有 Beans,Core,Context,Expression , IOC 和 DI⭐
- 切面模块主要有 AOP ,涉及到这个 AOP 编程 ⭐
- Web模块有 Web,WebMVC, WebSocket, WebFlux【MVC】
- 数据模块主要有 ORM 【数据操作】,TX【事务】
方便扩展,比如 整合邮件,缓存,定时任务等
模块有哪些【七大核心模块】
主要关注5个
1.【核心模块】⭐Core Container(关注IOC,DI)
2.【切面模块】⭐AOP
3.【数据模块】⭐Data Access/Integration
4.【Web模块】⭐Spring Web(关注MVC)
5.【测试模块】⭐Spring Test
6.【消息模块】Messaging
7.【工具模块】Instrumentation(没用过)
Spring,Spring MVC,Spring Boot 之间什么关系?
Spring指的是 Spring Framework【核心】
SpringMVC是SpringWeb模块的子模块【属于Spring】
SpringBoot是对Spring框架简化【自动配置,起步依赖】
如何理解spring属于低侵入式设计?
在代码中不需要依赖具体对象,在运行时进行自动注入,【IOC,DI】降低了组件的耦合,
依赖的是接口,而接口的实现类具有拓展性可以自由选择和组装Spring框架的各个功能模块,而不强制要求应用系统的类必须从Spring框架的系统API的某个类来继承或者实现某个接口。
Spring IoC
Spring IOC 实现了什么功能,谈谈你对IOC的理解。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI)装配对象、配置对象)并且管理这些对象的整个生命周期。
功能:
依赖注入(DI):IOC容器要为应用程序去提供运行时所依赖的资源。
生命周期管理:Spring IOC容器还负责管理Bean的生命周期。它会根据配置来创建Bean,初始化Bean,以及在不再需要时销毁Bean。
配置集中管理:Spring IOC容器允许将应用的配置集中到一个或多个地方,使得配置的管理更加方便。
控制反转【Ioc】:将对象的创建权交由外部容器,将Bean加入IOC容器
什么是 Spring Bean?Spring IoC容器和容器实现?
Bean
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
SpringIoC容器介绍
Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。
SpringIoC容器具体接口和实现类
SpringIoc容器接口:
BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象,它是SpringIoC容器标准化超接口!
ApplicationContext 是 BeanFactory 的子接口。
它扩展了以下功能:
- 更容易与 Spring 的 AOP 功能集成
- 消息资源处理(用于国际化)
- 特定于应用程序给予此接口实现,例如Web 应用程序的 WebApplicationContext
简而言之, BeanFactory 提供了配置框架和基本功能,而 ApplicationContext 添加了更多特定于企业的功能。 ApplicationContext 是 BeanFactory 的完整超集!
ApplicationContext容器实现类:
ApplicationContext接口是Spring容器的核心接口,初始化时bean立即加载
将一个类声明为 Bean 的注解有哪些?
怎么声明
注解 说明 位置
@Controller @Component的衍生注解 标注在控制器类上
@Service @Component的衍生注解 标注在业务类上
@Repository @Component的衍生注解 标注在数据访问类上(由于与mybatis整合,用的少)
@Component 声明bean的基础注解 不属于以上三类时,用此注解【自定义类】
@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。
还可以使用 @Configuration 注解的类中,使用 @Bean 注解来定义 Bean。这些 Bean 会被 Spring IOC 容器自动创建和管理。【第三方Bean管理】
@Component 和 @Bean 的区别是什么?
- @Component 注解作用于类,而@Bean注解作用于方法。
- @Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。- @Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
细节:
名称问题
- value属性指定bean的名字
- 如果没有指定,默认为类名首字母小写。
DI 注入的方式有?
依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。
方式:
属性注入,构造器注入,Setter 注入,构造方法参数注入,接口注入
Bean属性赋值:引用类型自动装配 (DI)
前提:一个对象且容器中存在
@Autowired自动装配⭐
- 默认是按照类型进行自动装配的
- 前提:参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。
如果存在多个相同类型的bean对象?@Autowired无法使用
@Primary
当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。
@Qualifier
指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。
- @Qualifier注解不能单独使用,必须配合@Autowired使用
@Resource类型装配⭐
是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。
@Autowired(required = true) + @Qualifier(value = “userServiceImpl”) =@Resource(name=“userServiceImpl”)
拓展
@Autowired 和@Resource使用的比较多一些。
@Autowird 与 @Resource的区别⭐
- @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
- @Autowired 默认是按照类型注入,而@Resource是按照名称注入
- @Resource注解用在属性上、setter方法上。
- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【高于JDK11或低于JDK8需要引入以下依赖】
<dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency>
Bean属性赋值:基本类型属性赋值 (DI)
通常用于注入外部化属性
从外部配置文件(如 .properties 或 .yaml 文件)读取配置值并将其注入到 Bean 属性中的机制。
配置文件(application.properties 或 application.yml):
# application.properties
app.name=MyApplication
app.version=1.0.0
@Value
Java 类(注入配置属性):
- 情况1: ${key} 取外部配置key对应的值!
- 情况2: ${key:defaultValue} 没有key,可以给与默认值
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
// Getter 方法
public String getAppName() {
return appName;
}
public String getAppVersion() {
return appVersion;
}
}
@ConfigurationProperties
另一种方式是使用 @ConfigurationProperties
注解,它允许将配置文件中的属性映射到一个配置类中。这样可以将一组相关的配置属性绑定到一个 Bean 上。
示例代码:
配置文件(application.yml):
app:
name: MyApplication
version: 1.0.0
配置类:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
// Getter 和 Setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
组件管理
组件周期
概述
- bean生命周期:bean从创建到销毁的整体过程
- bean生命周期控制:在bean创建后到销毁前做一些事情
Bean 生命周期的主要阶段:
- Bean 实例化(Instantiation)
Spring 容器通过调用构造器(无论是默认构造器还是带参数的构造器)来实例化 Bean 对象。这是 Bean 生命周期的第一步。
- 属性赋值/填充(Dependency Injection)
在 Bean 实例化之后,Spring 会注入其依赖(即进行依赖注入),通过构造器注入、setter 注入或字段注入等方式来填充 Bean
的属性。
- 初始化(Initialization)
- InitializingBean 接口: 如果 Bean 实现了 InitializingBean 接口,Spring 会调用其 afterPropertiesSet() 方法。
- 初始化方法: 如果 Bean 配置了初始化方法(通过 @PostConstruct 注解或在 XML 配置中定义的 init-method),Spring 会调用这个方法。
- 使用(Usage)
Bean 在这个阶段被完全初始化,可以被应用程序使用。
- 销毁(Destruction)
- DisposableBean 接口: 如果 Bean 实现了 DisposableBean 接口,Spring 会调用其 destroy() 方法。
- 销毁方法: 如果 Bean 配置了销毁方法(通过 @PreDestroy 注解或在 XML 配置中定义的 destroy-method),Spring 会调用这个方法。 件作用域配置
Bean作用域概念
具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定!【控制实例化对象个数】
作用域可选值
Bean 是线程安全的吗?
在Spring框架中,Bean 的线程安全性与其作用域和状态密切相关。下面是关于最常用的两种作用域——singleton 和 prototype——的详细说明:
作用域与线程安全
- prototype 作用域:
- 定义:每次请求都会创建一个新的 Bean 实例。
- 线程安全性:由于每次请求创建的是不同的实例,因此不存在共享状态的问题,线程安全性得以保证。
- 示例:
@Component
@Scope("prototype")
public class PrototypeBean {
// 每次请求都会创建一个新的实例
}
- singleton 作用域:
- 定义:Spring IoC 容器中只有一个 Bean 实例。
- 线程安全性:由于所有线程共享同一个 Bean 实例,如果 Bean 中存在可变的成员变量,就可能会导致资源竞争和线程安全问题。
- 示例:
@Component
@Scope("singleton")
public class SingletonBean {
private int counter; // 可变的成员变量
}
线程安全问题的解决方案
对于 singleton 作用域的有状态 Bean,以下是解决线程安全问题的常见方法:
- 避免定义可变的成员变量:
- 尽量将 Bean 设计为无状态 Bean,不在 Bean 中定义可变的成员变量。
- 示例:
@Component
@Scope("singleton")
public class StatelessBean {
private final String immutableField; // 不可变的成员变量
public StatelessBean(String immutableField) {
this.immutableField = immutableField;
}
}
- 使用 ThreadLocal:
- 对于有状态的 Bean,可以在类中使用 ThreadLocal 成员变量,将可变的状态绑定到当前线程,这样每个线程都有自己的变量副本,避免了线程安全问题。
- 示例:
@Component
@Scope("singleton")
public class ThreadLocalBean {
private static final ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);
public void incrementCounter() {
threadLocalCounter.set(threadLocalCounter.get() + 1);
}
public int getCounter() {
return threadLocalCounter.get();
}
}
总结
- prototype 作用域的 Bean 在每次请求时都会创建新的实例,因此是线程安全的。
- singleton 作用域的 Bean 可能会存在线程安全问题,尤其是当 Bean 有状态时。常见的解决办法是避免可变的成员变量或使用 ThreadLocal 以确保每个线程有独立的状态副本。
通过上述方法,你可以确保在使用 Spring 的 Bean 时,能够处理好线程安全的问题。
Spring AOP
使用场景
方法增强
日志记录
权限控制
什么是AOP?
AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)。
代理模式
代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题!
概述
- 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。(中介)
- 动词:指做代理这个动作,或这项工作
- 名词:扮演代理这个角色的类、对象、方法
- 目标:被代理“套用”了核心逻辑代码的类、对象、方法。(房东)
代理在开发中实现的方式具体有两种:静态代理,[动态代理技术]
静态代理
主动创建代理类:
public class CalculatorStaticProxy implements Calculator {
// 将被代理的目标对象声明为成员变量
private Calculator target;
//使用构造函数传入目标
public CalculatorStaticProxy(Calculator target) {
this.target = target;
}
@Override
public int add(int i, int j) {
// 附加功能由代理类中的代理方法来实现
System.out.println("参数是:" + i + "," + j);
// 通过目标对象来实现核心业务逻辑
int addResult = target.add(i, j);
System.out.println("方法内部 result = " + result);
return addResult;
}
动态代理(代理自由分配)
动态代理技术分类
- JDK动态代理(Java原生):JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)
- cglib(第三方,spring已经集成):通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用
Cglib 生成一个被代理对象的子类来作为代理。动态代理:Spring AOP使用动态代理技术,在目标对象方法执行时将切面的逻辑织入到目标对象的方法中
OOP 和 AOP 的区别是什么?
OOP面向对象编程,纵向结构关系,关注类之间的关系.OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集
合。它导致了大量代码的重复,而不利于各个模块的重用。
AOP可以说是OOP(Object Oriented
Programming,面向对象编程)的补充和完善。AOP面向切面编程,横向结构关系,关注同一层次类的内容剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,使用AOP,可以在不修改原来代码的基础上添加新功能。
AOP的实现用到了哪种设计模式,它有哪些实现方式?
Spring AOP(面向切面编程)的实现主要用到了以下设计模式:
- 代理模式:Spring AOP是围绕着代理模式设计的。这里的代理模式,其实就是指使用一个代理对象来控制对原对象的访问,这个代理对象在原对象的基础上增加了一些额外的功能。
- 工厂模式:在Spring AOP中,工厂模式主要用于创建代理对象和目标对象。
Spring AOP的实现方式主要有以下几种:
- JDK动态代理:JDK动态代理主要针对目标对象的接口进行代理,动态生成接口的实现类。这种方式需要目标对象实现一个或多个接口,否则不能使用JDK动态代理)。
- CGLIB代理:CGLIB代理可以针对类进行代理,生成目标类的子类。这种方式不需要目标对象实现接口,也可以进行代理。
- Spring API实现AOP:通过实现Spring提供的AOP接口,如MethodBeforeAdvice、AfterReturningAdvice、MethodInterceptor等,来实现AOP。
- 自定义类实现AOP:通过自定义切入类和配置,来实现AOP。通过使用Spring的@Aspect、@Pointcut、@Before、@After等注解,来实现AOP
理解什么是连接点、切面、切点、通知、目标对象、织入、引入。
连接点 JoinPoint:要增强的哪些方法(目标方法)
通知 Advice:进行增强的重复逻辑,也就是共性的功能。(增强方法)
通知是切面在连接点处执行的代码,例如在方法调用前、方法调用后、方法抛出异常时执行的代码。通知可以分为
Before、After、AfterReturning、AfterThrowing 和 Around 五种类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
切入点 PointCut:匹配连接点的条件,通知仅会在切入点方法执行时被应用(为共性的功能标记目标方法)
切面 Aspect:,描述通知与切入点的对应关系(通知+切入点)【针对切入点的通知的完整方法】
切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)
目标对象:Target,通知所应用的对象(要增强目标方法所在对象)
织入 weave:指把通知应用到目标上,生成代理对象的过程。
引入(Introduction):引入允许我们向现有类添加新方法或属性
多个切面的执行顺序如何控制?
默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
使用 @Order 注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
Spring TX 事务
概述
事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。Spring中为声明式事务。
Spring事务管理
注:SpringBoot项目会自动配置一个 DataSourceTransactionManager,所以我们只需在方法(或者类)加上
@Transactional 注解,就自动纳入 Spring 的事务管理了。
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
Transactional注解
@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。
@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。
事务属性
isolation 【事务隔离级别】
- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。【推荐设置】
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
rollbackFor【事务异常】
默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。
假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。
拓展
Spring容器是如何启动的?
加载配置文件:Spring 容器会从指定的配置文件中读取配置信息,包括 bean 的定义、依赖关系、AOP 切面等。
创建容器:Spring 容器启动后会创建一个容器实例,容器负责管理 bean 的生命周期和依赖关系。
初始化 bean:容器会按照指定的顺序依次对 bean 进行初始化,包括实例化、属性注入、初始化方法执行等。
设置代理对象:如果 bean 需要被 AOP 切面增强,则容器会为其创建代理对象。
完成容器初始化:所有 bean 初始化完成后,Spring 容器启动完成1.
Spring是如何解决循环依赖问题的?
Spring循环依赖指的是两个或多个Bean之间相互依赖,形成一个环状依赖的情况。简单来说,就是A依赖B,B依赖C,C依赖A,这样就形成了一个循环依赖的环。
Spring循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常。因此,Spring循环依赖是一种需要尽量避免的情况。
Spring循环依赖的解决方法
- 构造函数注入: 在构造函数注入中,Spring会检查循环依赖,并在发现循环依赖时抛出异常,避免死循环。
- 使用@Lazy注解: @Lazy注解可以延迟Bean的实例化,从而避免循环依赖的问题。
- 使用setter方法:在构造函数注入中,Spring会检查循环依赖,并在发现循环依赖时抛出异常,避免死循环
Spring 框架中都用到了哪些设计模式?
- 简单工厂模式:Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象。
- 工厂方法模式:Spring使用工厂模式通过BeanFactory或ApplicationContext创建bean对象。
- 单例模式:Spring中bean的默认作用域就是singleton,也就是说,Spring容器中的bean默认都是单例的。
- 适配器模式:Spring框架的许多地方使用了适配器模式,如Spring MVC。
- 代理模式:Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象。