IOC和DI

控制反转(IoC)

将对象的创建和依赖关系的管理交给Spring IoC容器,极大地提高了代码的模块化和可维护性。

Spring IoC容器负责管理应用程序中对象的生命周期和依赖关系。它的主要职责包括:

对象的创建:根据配置文件或注解创建对象。

依赖注入:将对象的依赖注入到相应的对象中。

对象的销毁:在适当的时候销毁对象,释放资源。

配置方式

XML配置:通过XML文件定义Bean及其依赖关系。

Java配置:通过Java类和注解定义Bean及其依赖关系。

注解配置:通过注解(如@Component,@Autowired)自动扫描和注入Bean。

依赖注入(DI)

依赖注入是实现控制反转的一种方式。

构造函数注入:通过构造函数将依赖对象传递给被依赖对象。

Setter方法注入:通过Setter方法将依赖对象注入到被依赖对象中。

字段注入:直接在字段上使用注解进行注入。

BeanFactory 和 ApplicationContext 的区别?

BeanFactory和ApplicationContext都是用于管理Bean的容器接口

BeanFactory

BeanFactory是Spring框架的核心接口之一,负责管理和配置应用程序中的Bean。它提供了基本的Bean容器功能,但功能相对简单。BeanFactory提供了Bean的创建、获取和管理功能。它是Spring IoC容器的最基本接口。

BeanFactory默认采用懒加载(lazy loading),即只有在第一次访问Bean时才会创建该Bean。这有助于提升启动性能。

因为功能较为基础,BeanFactory通常用于资源受限的环境中,比如移动设备或嵌入式设备。

java
1
2
3
>Resource resource = new ClassPathResource("beans.xml");
>BeanFactory beanFactory = new XmlBeanFactory(resource);
>MyBean myBean = (MyBean) beanFactory.getBean("myBean");

ApplicationContext

ApplicationContext是BeanFactory的子接口,提供了更丰富的功能和更多的企业级特性。

不仅提供了BeanFactory的所有功能,还提供了更多高级特性,如事件发布、国际化、AOP、自动Bean装配等。

ApplicationContext默认会在启动时创建并初始化所有单例Bean(除非显式配置为延迟初始化)。这有助于在应用启动时尽早发现配置问题。

ApplicationContext支持自动装配Bean,可以根据配置自动注入依赖对象。

java
1
2
>ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
>MyBean myBean = (MyBean) context.getBean("myBean");

ApplicationContext有多种实现,如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext等,适用于不同的配置方式和场景。

ClassPathXmlApplicationContext:从类路径下加载XML配置文件。

java
1
>ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

FileSystemXmlApplicationContext:从文件系统路径加载XML配置文件。

java
1
>ApplicationContext context = new FileSystemXmlApplicationContext("C:/path/to/applicationContext.xml");

AnnotationConfigApplicationContext:从Java配置类(使用@Configuration注解的类)加载配置。

java
1
2
3
4
5
6
7
8
9
>@Configuration
>public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
>}

>ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

XmlWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Web应用的上下文中加载XML配置文件。

web.xml

java
1
2
3
4
5
6
7
><context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
></context-param>
><listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
></listener>

AnnotationConfigWebApplicationContext:专门为Web应用设计的ApplicationContext实现类,从Java配置类加载配置

java
1
2
3
4
5
6
7
8
>public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
container.addListener(new ContextLoaderListener(context));
}
>}

GenericGroovyApplicationContext:从Groovy脚本配置文件加载配置。

java
1
>ApplicationContext context = new GenericGroovyApplicationContext("applicationContext.groovy");

区别

BeanFactory ApplicationContext
初始化时机 延迟初始化,只有在第一次访问Bean时才创建该Bean。 立即初始化,在容器启动时就创建并初始化所有单例Bean。
特性 功能较为基础,只提供Bean的创建、获取和管理功能。 提供更多企业级特性,如事件发布、国际化、AOP、自动装配等。
使用场景 适用于资源受限的环境,或者需要延迟初始化的场景。 适用于大多数企业级应用

spring bean标签的属性和生命周期

id:Bean的唯一标识符。

name:Bean的别名,可以为Bean定义一个或多个别名。

class:Bean的全限定类名。

scope:Bean的作用域,常见值包括(默认)、prototype、request、session、globalSession、application。

singleton:默认作用范围,整个Spring容器中只有一个实例,所有对该Bean的引用都指向同一个实例。适用于无状态的Bean。

prototype:每次请求该Bean时都会创建一个新的实例。适用于有状态的Bean。
request:每次HTTP请求都会创建一个新的实例,仅适用于Web应用。

session:每个HTTP会话都会创建一个新的实例,仅适用于Web应用。

globalSession:每个全局HTTP会话都会创建一个新的实例,仅适用于Portlet应用。

application:每个ServletContext会创建一个新的实例,适用于Web应用。

init-method:Bean初始化时调用的方法。

destroy-method:Bean销毁时调用的方法。

factory-method:用于创建Bean实例的静态工厂方法。
factory-bean:用于创建Bean实例的工厂Bean的名称。

constructor-arg:用于构造函数注入。

property:用于Setter方法注入。

autowire:自动装配模式,常见值包括no(默认)、byName、byType、constructor、autodetect。

depends-on:指定Bean的依赖关系,即在初始化当前Bean之前需要先初始化的Bean。

lazy-init:是否延迟初始化,默认值为false。

primary:当自动装配时,如果有多个候选Bean,可以将某个Bean标记为主要候选者。

生命周期

image.png

实例化(Instantiation)
Spring容器根据配置创建Bean实例。
属性设置(Property Population)
Spring容器进行依赖注入,设置Bean的属性。
初始化(Initialization)
如果Bean实现了InitializingBean接口,Spring会调用其afterPropertiesSet()方法。
如果在XML配置中指定了init-method属性,Spring会调用指定的初始化方法。
如果Bean使用了@PostConstruct注解,Spring会调用标注的方法。
使用(Usage)
Bean处于就绪状态,可以被应用程序使用。
销毁(Destruction)
如果Bean实现了DisposableBean接口,Spring会调用其destroy()方法。
如果在XML配置中指定了destroy-method属性,Spring会调用指定的销毁方法。
如果Bean使用了@PreDestroy注解,Spring会调用标注的方法。

解决Spring 循环依赖

构造器注入的循环依赖

Spring 无法直接解决通过构造器注入引起的循环依赖,因为在这种情况下,Spring 无法创建任何一个 Bean 实例而不先创建另一个 Bean 实例。这会导致一个无限循环。因此,通常建议避免在构造器注入中引入循环依赖。

Setter 注入的循环依赖

对于通过 setter 方法注入引起的循环依赖,Spring 采用三级缓存机制来解决问题。

三级缓存机制

  1. 一级缓存(singletonObjects):用于存储完全初始化好的单例 Bean。类型:Map
  2. 二级缓存(earlySingletonObjects):用于存储早期暴露的 Bean 实例,部分初始化的 Bean。Map
  3. 三级缓存(singletonFactories):用于存储 Bean 工厂,主要用于创建 Bean 的代理对象。Map>

解决循环依赖的过程

我们拿 A 依赖 B,B 依赖 A 来进行举例

1、在创建 A 对象放入到 spring 容器的过程,先看一级缓存,能否可以直接获取到 A,如果可以,直接获取,如果不可以,则开始创建 A 对象,A创建过程中发现需要属性 B,查找发现 B 还没有在一级缓存中,于是先将 A 放到三级缓存中,此时的 A 不完整,没有属性,但是可以引用。接下来就去实例化B。

2、B 实例化的过程,也是先从一级缓存,看自己有没有,没有的话,开始创建,此时发现需要A,于是B先查一级缓存寻找A,如果没有,再查二级缓存,如果还没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

3、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态)。然后回来接着创建A,此时B已经创建结束,可以直接从一级缓存里面拿到B,去完成A的创建,并将A放到一级缓存。

假设我们没有三级缓存,只有一级缓存,那么我们会怎样进行处理呢?

首先A对象进行实例化,A要进行属性填充B。但是B还没有创建,于是开始B进行实例化,同样的B也要进行属性填充,发现他需要A。然而我们的一级缓存的Map里面还没有A,所以他有创建A,于是就产生了死循环。循环往复,最后栈溢出。那么小伙伴们会问,我的A我不进行属性填充,我直接扔一级缓存里面,那不就可以了吗?这样就会造成map里面存的A是个假A,缺胳膊少腿,当你真正用到他的时候,啪一个空指针异常。而且我们的一级缓存规定是完全初始化好的bean。给我们的程序进行使用。那么大家这会都理解了,一级缓存行不通

二级缓存解决了什么问题?

首先我们还是实例化A开始,注意这个地方,我们实例化后,还没有进行属性填充的时候,就把A对象的引用放入到了map2备用。然后进行属性填充,A去填充B,发现B没有实例化,于是B同样实例化后,把自己的半成品放入到map2。B开始进行填充,发现Map1中没有A,又去Map2中进行寻找,发现map2里面有。于是B直接拿到map2中的A使自己变得非常完整。这个时候B就把自己放入Map1。并把Map2的半成品删除了。回到刚才A的阶段,A发现Map1中已经有B了。那么A也就完成了属性的创建。于是双方都完成了自己的创建。这就是二级缓存解决的问题。

不需要三级缓存可以吗?

主要是因为Spring的Aop机制所产生的代理对象问题。首先要了解一个前置就是Spring的代理对象产生阶段是在填充属性后才进行的,原理通过后置处理器BeanPostProcessor来实现。如果说我们用二级缓存来解决,那么就要在属性填充的时候,将代理对象生成好,放入二级缓存。那么就与我们spring的对象生命周期相悖。所以这种方式不好,于是我们引入了三级缓存。

@ComponentScan注解

@ComponentScan是用于自动扫描指定的包及其子包中的组件类,并将它们注册为 Spring 容器管理的 Bean。这个过程被称为组件扫描(Component Scanning)。

basePackages属性:用于指定要扫描的包。可以指定一个或多个包路径。

basePackageClasses属性:用于指定一个或多个类,Spring 会扫描这些类所在的包。

includeFilters和excludeFilters属性: 用于指定包含或排除的过滤器。可以根据注解、类型、正则表达式等进行过滤。

lazyInit属性:用于指定是否延迟初始化扫描到的 Bean。默认值为false,表示立即初始化。

@Autowired和@Resource的区别

@Autowired

来源:Spring 框架。
注入方式:默认按类型注入。
用法:可以用于字段、构造器、Setter 方法或其他任意方法。
可选性:可以与@Qualifier一起使用,以指定具体的 Bean。
处理机制:Spring 的AutowiredAnnotationBeanPostProcessor处理@Autowired注解。

@Resource

来源:由 Java EE 提供。
注入方式:默认按名称注入,如果按名称找不到,则按类型注入。
用法:可以用于字段或 Setter 方法。
属性:可以指定name和type属性。
处理机制:Spring 的CommonAnnotationBeanPostProcessor处理@Resource注解。

@Component和@Bean的区别

@Component

用途:用于将一个类标记为 Spring 组件类,使其被 Spring 容器自动扫描并注册为 Bean。
使用场景:通常用于标记那些需要自动检测和注册为 Bean 的类。
位置:直接标记在类上。
自动扫描:需要配合@ComponentScan注解使用,Spring 会自动扫描指定包及其子包中的所有类,找到带有@Component注解的类,并将它们注册为 Bean。

@Bean

用途:用于定义一个方法,该方法返回一个要注册为 Spring 容器管理的 Bean。
使用场景:通常用于显式定义 Bean,特别是当需要一些复杂的初始化逻辑或需要从第三方库创建 Bean 时。
位置:标记在方法上,方法所在的类需要用@Configuration注解标记
显式配置:通过显式的 Java 配置方式定义 Bean,而不是通过类路径扫描。

AOP

AOP(面向切面编程)是一种编程范式,它旨在通过分离横切关注点来提高代码的模块化。AOP 在传统的面向对象编程(OOP)基础上,提供了一种处理系统级关注点(如日志记录、事务管理、安全性等)的机制,而这些关注点通常会散布在多个模块中。

AOP 的基本概念

Aspect(切面):切面是模块化的横切关注点。它可以包含多个 advice(通知)和 pointcut(切入点)。

Advice(通知):通知是切面在特定的切入点执行的动作。通知有几种类型:

  • Before:在方法执行之前执行。
  • After:在方法执行之后执行。
  • AfterReturning:在方法成功执行之后执行。
  • AfterThrowing:在方法抛出异常后执行。
  • Around:包围一个方法的执行,能够控制方法的执行前后。

Pointcut(切入点):切入点定义了通知应该应用到哪些连接点上。连接点是程序执行的特定点,如方法调用或异常抛出。

Join Point(连接点):连接点是程序执行过程中可以插入切面的一个点。通常,连接点是方法的调用或执行。AOP 框架允许在这些连接点上插入额外的行为。

Weaving(织入):将切面应用到目标对象创建代理对象的过程。织入可以在编译时、类加载时、运行时进行。

AOP 的作用和优势

模块化横切关注点:AOP 允许将横切关注点(如日志记录、事务管理、安全性等)从业务逻辑中分离出来,从而提高代码的模块化和可维护性。

减少重复代码:通过将通用功能提取到切面中,可以减少代码的重复,提高代码的可读性和可维护性。

提高代码的可维护性:由于横切关注点被模块化为切面,任何修改只需要在切面中进行,而不需要修改业务逻辑代码,从而提高了代码的可维护性。

动态代理:AOP 使用动态代理机制,可以在不修改源代码的情况下为现有代码添加功能。

增强代码的可测试性:由于横切关注点被分离成切面,业务逻辑代码变得更加简洁和专注,从而提高了代码的可测试性。

AOP 的应用场景

日志记录:在方法调用前后记录日志。

事务管理:在方法执行前开启事务,在方法执行后提交事务,在方法抛出异常时回滚事务。

安全性检查:在方法执行前进行权限检查。

性能监控:在方法执行前后记录执行时间。

缓存:在方法调用前检查缓存,在方法调用后更新缓存。

定义切面

plaintext
1
2
3
4
5
6
7
8
9
10
11
12
>import org.aspectj.lang.annotation.Aspect;
>import org.aspectj.lang.annotation.Before;
>import org.springframework.stereotype.Component;

>@Aspect
>@Component
>public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}
>}

配置 Spring 应用

plaintext
1
2
3
4
5
6
7
8
9
>import org.springframework.context.annotation.ComponentScan;
>import org.springframework.context.annotation.Configuration;
>import org.springframework.context.annotation.EnableAspectJAutoProxy;

>@Configuration
>@ComponentScan(basePackages = "com.example")
>@EnableAspectJAutoProxy
>public class AppConfig {
>}

使用 AOP 的服务类

plaintext
1
2
3
4
5
6
7
8
>import org.springframework.stereotype.Service;

>@Service
>public class MyService {
public void performTask() {
System.out.println("Performing task");
}
>}

主应用

plaintext
1
2
3
4
5
6
7
8
9
10
>import org.springframework.context.ApplicationContext;
>import org.springframework.context.annotation.AnnotationConfigApplicationContext;

>public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = context.getBean(MyService.class);
myService.performTask();
}
>}

LoggingAspect切面会在每次调用MyService中的方法时记录日志,而无需修改MyService的源代码。 AOP 通过分离横切关注点来提高代码的模块化和可维护性。

动态代理

动态代理是一种在运行时动态生成代理类的机制,它允许我们在不修改原始类的情况下增强或修改其行为。动态代理是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现的。

动态代理的基本概念

代理类(Proxy Class):一个代理类是一个实现了一个或多个接口的类,它可以在运行时动态生成。代理类的实例可以用来代替原始对象,并在调用方法时执行额外的逻辑。

调用处理器(Invocation Handler):调用处理器是一个实现了InvocationHandler接口的类,它定义了代理类的方法调用逻辑。每次代理对象的方法被调用时,调用处理器的invoke方法都会被执行。

动态代理的作用和优势

1、 解耦业务逻辑和通用功能:动态代理允许将通用功能(如日志记录、事务管理、安全性检查等)从业务逻辑中分离出来,从而提高代码的模块化和可维护性。

2、 灵活性:动态代理在运行时生成代理类,不需要在编译时确定代理类,因此具有很大的灵活性。

3、 减少代码重复:通过动态代理,可以将通用功能集中到一个地方,从而减少代码重复。

4、 增强现有代码:动态代理允许在不修改现有代码的情况下增强其功能。

动态代理的应用场景

1、AOP(面向切面编程):动态代理是实现 AOP 的核心技术之一,通过动态代理可以在方法执行前后添加横切关注点(如日志记录、事务管理等)。

2、 远程方法调用(RMI):动态代理可以用来实现客户端和服务器之间的远程方法调用。

3、 装饰器模式:动态代理可以用来实现装饰器模式,在不修改原始类的情况下增强其功能。

4、 框架和中间件:许多框架和中间件(如 Spring、Hibernate 等)都使用动态代理来实现其核心功能。

动态代理通过在运行时生成代理类,提供了一种灵活且强大的方式来增强现有代码的功能,而无需修改原始代码。

常用的两种方式

动态代理常用的两种方式是基于接口的动态代理(JDK 动态代理)和基于类的动态代理(CGLIB 动态代理)。

JDK 动态代理

JDK 动态代理是 Java 标准库提供的一种动态代理机制,它依赖于接口来创建代理对象。JDK 动态代理通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。当你有一个接口并希望为该接口的实现类创建代理时,可以使用 JDK 动态代理。

实现:JDK 动态代理主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。

步骤:

定义接口:定义需要代理的接口。

java
1
2
3
>public interface MyService {
void performTask();
>}

实现接口:创建接口的实现类。

plaintext
1
2
3
4
5
6
>public class MyServiceImpl implements MyService {
@Override
public void performTask() {
System.out.println("Performing task");
}
>}

创建调用处理器:实现InvocationHandler接口,并在invoke方法中定义代理逻辑。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>import java.lang.reflect.InvocationHandler;
>import java.lang.reflect.Method;

>public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;

public LoggingInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Logging before method execution: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("Logging after method execution: " + method.getName());
return result;
}
>}

创建代理对象:通过Proxy.newProxyInstance方法创建代理对象。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>import java.lang.reflect.Proxy;

>public class MainApp {
public static void main(String[] args) {
// 创建目标对象
MyService myService = new MyServiceImpl();

// 创建调用处理器
LoggingInvocationHandler handler = new LoggingInvocationHandler(myService);

// 创建代理对象
MyService proxyInstance = (MyService) Proxy.newProxyInstance(
myService.getClass().getClassLoader(),
myService.getClass().getInterfaces(),
handler
);

// 调用代理对象的方法
proxyInstance.performTask();
}
>}
CGLIB 动态代理

CGLIB(Code Generation Library)是一个强大的高性能代码生成库,它通过生成子类的方式来为目标类创建代理对象。CGLIB 动态代理不需要接口,可以直接代理类。当你没有接口,只有具体类时,可以使用 CGLIB 动态代理。

步骤:

引入 CGLIB 库:确保在项目中添加 CGLIB 依赖。

java
1
2
3
4
5
><dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
></dependency>

创建目标类:定义需要代理的具体类。

java
1
2
3
4
5
>public class MyService {
public void performTask() {
System.out.println("Performing task");
}
>}

创建方法拦截器:实现MethodInterceptor接口,并在intercept方法中定义代理逻辑。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>import net.sf.cglib.proxy.MethodInterceptor;
>import net.sf.cglib.proxy.MethodProxy;

>import java.lang.reflect.Method;

>public class LoggingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Logging before method execution: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("Logging after method execution: " + method.getName());
return result;
}
>}

创建代理对象:通过Enhancer类创建代理对象。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>import net.sf.cglib.proxy.Enhancer;

>public class MainApp {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();

// 设置目标类为代理类的父类
enhancer.setSuperclass(MyService.class);

// 设置方法拦截器
enhancer.setCallback(new LoggingMethodInterceptor());

// 创建代理对象
MyService proxyInstance = (MyService) enhancer.create();

// 调用代理对象的方法
proxyInstance.performTask();
}
>}
比较

依赖性

JDK 动态代理依赖于接口,如果没有接口就无法使用。

CGLIB 动态代理可以直接代理类,不需要接口。

性能

JDK 动态代理由于需要通过反射调用方法,性能可能会有所影响。

CGLIB 动态代理通过生成字节码来创建代理类,性能通常比 JDK 动态代理更高,但生成字节码的过程会稍微多占用一些内存。

使用场景

JDK 动态代理适用于有接口的情况,适用于大多数常见的业务场景。

CGLIB 动态代理适用于没有接口的情况,适用于需要代理大量具体类的场景。

Spring中的单例Beans不是线程安全

在 Spring 中,单例(Singleton)Beans 是默认的 Bean 范围(Scope)。这意味着 Spring 容器在整个应用程序生命周期内只会创建一个 Bean 实例,并在需要时重复使用该实例。

单例 Beans 本身并不是线程安全的。Spring 容器不会自动为你处理线程安全问题。如果你的单例 Bean 被多个线程同时访问,并且这些线程执行的操作会修改 Bean 的状态,那么你需要自己确保线程安全。

Spring事务传播行为

这些传播行为通过@Transactional注解的propagation属性来配置

传播行为类型(前三个常用)

REQUIRED(默认值):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

REQUIRES_NEW:总是创建一个新的事务。如果当前存在事务,则挂起当前事务。

SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续执行。

NOT_SUPPORTED:总是以非事务方式执行,如果当前存在事务,则挂起当前事务。

MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

NEVER:总是以非事务方式执行,如果当前存在事务,则抛出异常。

NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。

事务传播行为的选择

REQUIRED:大多数情况下使用的默认传播行为,适用于大多数需要事务管理的方法。

REQUIRES_NEW:适用于需要独立事务的情况,例如记录日志、审计等操作,即使外层事务回滚,这些操作也应该提交。

SUPPORTS:适用于可选事务的情况,例如读取操作,可以在事务内或事务外执行。

NOT_SUPPORTED:适用于不需要事务的情况,例如调用外部服务。

MANDATORY:适用于必须在事务内执行的方法,例如严格依赖事务上下文的操作。

NEVER:适用于必须在非事务上下文中执行的方法。

NESTED:适用于需要嵌套事务的情况,例如需要在一个事务内执行多个子事务,并且可以单独回滚子事务。

Spring事务中的隔离级别有哪几种

Spring 提供了以下几种隔离级别,通过@Transactional注解的isolation属性来配置:

事务隔离级别

DEFAULT:使用底层数据库的默认隔离级别。通常情况下,这个默认值是READ_COMMITTED。

READ_UNCOMMITTED(读未提交):允许一个事务读取另一个事务尚未提交的数据。可能会导致脏读(Dirty Read)、不可重复读(Non-repeatable Read)和幻读(Phantom Read)问题。

READ_COMMITTED(读已提交):保证一个事务只能读取另一个事务已经提交的数据。可以防止脏读,但可能会导致不可重复读和幻读问题。

REPEATABLE_READ(可重复读):保证一个事务在读取数据时不会看到其他事务对该数据的修改。可以防止脏读和不可重复读,但可能会导致幻读问题。

SERIALIZABLE(串行化):最高的隔离级别。保证事务按顺序执行,完全隔离。可以防止脏读、不可重复读和幻读问题,但并发性最低,可能导致性能下降。

隔离级别的选择

READ_UNCOMMITTED:适用于对数据一致性要求不高,且需要最高并发性的场景。

READ_COMMITTED:适用于大多数应用,能够防止脏读,提供较好的并发性和数据一致性平衡。

REPEATABLE_READ:适用于需要防止脏读和不可重复读,但可以容忍幻读的场景。

SERIALIZABLE:适用于对数据一致性要求极高的场景,尽管会牺牲并发性。

Spring中用到了哪些设计模式

工厂模式(Factory Pattern)

Spring 使用工厂模式来创建对象实例。BeanFactory和ApplicationContext是 Spring 框架中实现工厂模式的核心接口。

应用场景:Spring 容器负责创建和管理 bean 的实例。

java
1
2
>ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
>MyBean myBean = (MyBean) context.getBean("myBean");
单例模式(Singleton Pattern)

Spring 默认以单例模式管理 bean,这意味着每个 bean 在 Spring 容器中只有一个实例。

应用场景:默认情况下,Spring 容器中的每个 bean 都是单例的。

代理模式(Proxy Pattern)

Spring AOP(面向切面编程)使用代理模式来创建代理对象,以实现方法拦截和增强功能。

应用场景:AOP 实现事务管理、日志记录、安全检查等。

java
1
2
3
4
5
6
7
>@Aspect
>public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("Executing method: " + joinPoint.getSignature().getName());
}
>}
模板方法模式(Template Method Pattern)

Spring 提供了多种模板类(如JdbcTemplate、RestTemplate),这些类封装了常见的操作步骤,允许用户只需实现特定的步骤。

应用场景:简化数据库操作、RESTful 服务调用等。

java
1
2
3
4
5
6
7
>@Autowired
>private JdbcTemplate jdbcTemplate;

>public void saveData(String data) {
String sql = "INSERT INTO my_table (data) VALUES (?)";
jdbcTemplate.update(sql, data);
>}
观察者模式(Observer Pattern)

Spring 的事件处理机制使用观察者模式。ApplicationEventPublisher和ApplicationListener是实现观察者模式的核心接口。

应用场景:实现事件驱动的编程模型。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
>}

>@Component
>public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("Received event: " + event.getSource());
}
>}

>@Component
>public class MyEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;

public void publishEvent() {
MyEvent event = new MyEvent(this);
applicationEventPublisher.publishEvent(event);
}
>}
依赖注入模式(Dependency Injection Pattern)

Spring 的核心功能之一就是依赖注入,通过构造函数注入、setter 注入或字段注入,将对象的依赖关系注入到对象中。

应用场景:解耦对象之间的依赖关系,便于测试和维护。

java
1
2
3
4
5
6
7
8
9
>@Component
>public class MyService {
private final MyRepository myRepository;

@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
>}
装饰器模式(Decorator Pattern)

Spring 使用装饰器模式来增强 bean 的功能,特别是在 AOP 中,通过将增强逻辑应用到目标对象上。

应用场景:动态地为对象添加职责,而不影响其他对象。

java
1
2
3
4
5
6
7
8
9
10
11
>@Aspect
>public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println("Method executed in: " + executionTime + "ms");
return result;
}
>}
策略模式(Strategy Pattern)

Spring 中的TransactionManager使用策略模式来定义事务管理的策略,允许在运行时选择不同的事务管理器。

应用场景:定义一系列算法,允许在运行时选择具体的算法。

java
1
2
3
4
5
6
7
>@Configuration
>public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
>}

Spring事务失效场景

非public方法使用@Transactional

场景描述:Spring事务管理是基于AOP实现的,而AOP对于JDK动态代理或CGLib动态代理只会代理public方法。如果事务方法的访问修饰符为非public,SpringAOP无法正确地代理该方法,从而导致事务失效。

示例代码:事务方法的访问修饰符被设置为private、default或protected。

解决方案:将需要事务管理的方法设置为public。

在同类中的非事务方法调用事务方法(常见)

场景描述:Spring的事务管理是通过动态代理实现的,只有通过代理对象调用的方法才能享受到Spring的事务管理。如果在同一个类中,一个没有标记为@Transactional的方法内部调用了一个标记为@Transactional的方法,那么事务是不会起作用的。

解决方案:尽量将事务方法放在不同的类中,或者使用Spring的AopContext.currentProxy()来获取当前类的代理对象,然后通过代理对象调用事务方法。

事务属性设置不当

场景描述:在Spring的事务管理中,如果在一个支持当前事务的方法(比如,已经被标记为@Transactional的方法)中调用了一个需要新事务的方法,如果后者方法抛出了异常,但异常并未被Spring识别为需要回滚事务的异常,那么后者的事务将不会回滚。

异常类型不匹配

场景描述:默认情况下,Spring只有在方法抛出运行时异常或者错误时才会回滚事务。对于检查性异常,即使你在方法中抛出了,Spring也不会回滚事务,除非你在@Transactional注解中显式地指定需要回滚哪些检查性异常。

解决方案:了解Spring事务管理对异常的处理,必要时在@Transactional注解中指定需要回滚的异常类型。

事务拦截器配置错误

场景描述:如果没有正确地配置事务拦截器,例如没有指定切入点或指定了错误的切入点,就会导致Spring事务失效。

事务超时配置错误

场景描述:如果事务超时时间设置得太短,就有可能在事务执行过程中出现超时,从而导致Spring事务失效。