目录
1.DI的核心概念
依赖注入(DI)是一种设计模式,用于实现对象间的依赖关系的自动满足。在传统的编程模式中,每个对象负责管理其依赖项的创建和绑定,而在使用DI的模式中,这些依赖项是由外部系统(在Spring中是IoC容器)在创建对象时自动注入的。
1.1优势
- 降低耦合度:对象不需要知道如何创建它们所需的依赖对象。
- 增强模块化:便于管理大型应用中的依赖关系。
- 提高可测试性:依赖可以在测试中被替换或模拟。
2. Spring中的DI实现
Spring提供了多种方式来实现DI,包括构造器注入、设置器注入和字段注入。
2.1 构造器注入
通过构造器注入,依赖项在对象创建时通过构造函数传入。这是推荐的注入方式,因为它可以保证所需的依赖项在使用前就被正确提供。
@Component
public class ProductService {
private final InventoryService inventoryService;
public ProductService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
}
在Spring 4.3之后,如果目标组件只定义了一个构造器,Spring会自动将其作为用于依赖注入的构造器,无需显式使用@Autowired
注解。然而,在更早的版本中或者当存在多个构造器时,@Autowired
是必须的,以标识哪个构造器应该被用来自动注入。
2.1.2 优势和缺点
优点:
- 依赖不可变,确保所需的依赖在使用前已经提供。
- 便于发现缺失的依赖,因为在对象创建时就会出错。
- 最适合用于必需依赖。
缺点:
- 如果依赖项很多,构造器可能会显得笨重。
- 对于可选依赖不是很灵活。
2.2 设置器注入
设置器注入,顾名思义,是通过类的设置方法(setter方法)来注入依赖。这种方式的主要特点是它不强制在构造对象时立即提供依赖,而是允许在对象构造后、使用前的任何时间点注入依赖。这提供了更大的灵活性,尤其是在需要重新配置或更换依赖的场景中。
2.2.1 如何使用设置器注入
在Spring中使用设置器注入非常简单。首先,你需要在你的类中提供一个公开的设置方法,然后使用@Autowired
注解标注这个方法。Spring容器在创建这个类的Bean时,会自动调用这个设置方法,将相应的依赖注入进去。
2.2.2 示例代码
假设你有一个ProductService
类,它依赖于一个名为InventoryService
的服务。下面是如何通过设置器注入来注入这个依赖:
@Component
public class ProductService {
private InventoryService inventoryService;
// 使用@Autowired注解标注设置方法
@Autowired
public void setInventoryService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
public void processProduct() {
// 使用inventoryService进行一些操作
}
}
2.2.3优势和使用场景
优势:
- 灵活性:可以在对象的生命周期中的任何时点注入依赖,甚至可以重新注入不同的依赖。
- 适合可选依赖:如果某个依赖是可选的,使用设置器注入可以避免在构造函数中处理复杂的逻辑。
- 易于重新配置:适用于那些可能需要重新配置的组件。
使用场景:
- 当依赖项可能在运行时改变时,例如基于某些业务规则更换策略对象。
- 在需要通过配置来调整依赖关系的企业应用中,设置器注入提供了一种简单的方式来调整这些依赖。
- 当某些依赖项是可选的,不是必需的,这时使用设置器注入可以避免在对象创建时就必须提供这些依赖。
潜在的缺点
- 可能的不完整状态:如果在依赖注入之前就使用了对象,那么可能会导致运行时错误。
- 过度使用可能导致代码混乱:如果一个类有很多通过设置器注入的依赖,这可能会使类变得难以管理和理解。
2.3 字段注入
定义:字段注入是最简单的注入方式,直接在需要的字段上标注@Autowired
。这个也是我们最常见的使用,不过现在如果使用的是新的版本的话,idea更支持的是构造器注入了
代码示例:
@Component
public class ProductService {
@Autowired
private InventoryService inventoryService;
}
优点:
- 写法简单,减少了样板代码。
缺点:
- 测试困难,因为没有公开的方法可以用来替换依赖。
- 可能导致代码难以追踪,特别是在存在多个依赖的情况下。
2.4 方法注入
2.4.1 方法注入的概念
方法注入主要用于当一个单例Bean需要引用一个原型Bean的实例时。由于单例Bean在Spring容器的生命周期内只创建一次,其依赖的原型Bean也只会注入一次。这就导致了单例Bean不能再次获取原型Bean的新实例,除非使用方法注入。
Spring提供了一种方法注入的机制来解决这个问题。它主要通过两种方式实现:
- 查找方法注入:使用
@Lookup
注解来标注一个方法,这告诉Spring每次调用这个方法时都要在容器中查找并返回新的Bean实例。 - 方法替换:使用
replaced-method
元素来动态替换方法的实现。
2.4.2 找方法注入
@Component
public abstract class ProductService {
public void processProduct() {
PricingService pricingService = createPricingService();
// 对pricingService执行一些操作
}
@Lookup
protected abstract PricingService createPricingService();
}
这里的 @Lookup
注解标注在 createPricingService()
方法上。Spring在运行时将动态生成 ProductService
的一个子类,并覆盖这个方法以从Spring容器中返回PricingService
的新实例。
2.4.3 @Lookup
注解的作用
@Lookup
注解告诉Spring容器在每次调用该方法时,都应从Spring IoC容器中获取指定Bean的新实例。这对于处理原型依赖于单例Bean的情况非常有用。
2.4.4 结论和最佳实践
方法注入特别适合于需要将设计模式如工厂方法模式整合到Spring管理的Bean中的场景。虽然方法注入在某些特定场景下非常有用,但应当谨慎使用,因为它增加了配置的复杂性并可能对性能产生影响。
2.5 总结
尽管字段注入在某些情况下看似方便,但从长远来看,它可能会引入多种问题,特别是在大型项目中。 构造器注入是推荐的方式,特别是对于必需依赖,因为它能够确保依赖在使用前被正确设置。这也是最符合依赖注入原理的方法,因为它支持不可变性并且确保了对象始终处于有效状态。
3.Spring事务管理概述
Spring的事务管理支持提供了一致的编程模型,既适用于声明式事务管理也适用于编程式事务管理。它与底层的事务基础设施进行了良好的抽象,使得开发者可以不用关心具体的事务管理API,就能实现跨不同事务管理API的事务操作。
3.1 声明式事务管理
声明式事务管理是Spring推荐的事务管理方式。它将事务管理代码从业务代码中分离出来,通过配置方式来管理事务。这种方式使用@Transactional
注解,非常简单而且高效。
3.1.1 使用@Transactional
注解
在Spring中,你可以通过在类或方法上添加@Transactional
注解来声明一个事务。这个注解可以告诉Spring哪些方法是事务性的,以及这些事务应该如何被管理。
@Service
public class ProductService {
@Transactional
public void updateProductStock(Long productId, int newStock) {
// 业务逻辑代码,例如更新库存
// 这个方法中的所有数据库操作都会在同一个事务中执行
}
}
3.1.2 @Transactional
注解的参数解释
-
propagation
:定义了事务的传播行为。这决定了事务方法是如何相互作用的。例如,Propagation.REQUIRED
表示当前方法必须在一个具有事务的上下文中执行,如果当前没有事务上下文,就会启动一个新的事务。 -
isolation
:定义了事务的隔离级别,这是一个关键设置,因为它防止了多个事务同时执行时可能产生的问题,如脏读、不可重复读和幻读。常用的隔离级别包括READ_COMMITTED
、REPEATABLE_READ
等。 -
timeout
:定义了事务在被回滚前可以运行的时间(秒)。如果事务超过这个时间还没有完成,就会自动回滚。这有助于防止长时间运行的事务占用资源。 -
readOnly
:标记事务是否为只读事务。设置为true
可以帮助数据库优化事务。对于只查询数据的事务,设置为只读可以提高性能。 -
rollbackFor
:定义了哪些异常类会触发事务回滚。默认情况下,事务只对RuntimeException
和Error
进行回滚。如果业务逻辑中检测到其他异常也需要回滚事务,需要显式设置。 -
noRollbackFor
:定义了哪些异常类不会触发事务回滚。这在特定情况下非常有用,比如当某些异常不应该导致事务失败时。
3.1.3 Spring Data JPA 和事务
`Spring Data JPA简化了基于JPA的数据访问层的开发。事务管理是这一模块的核心组成部分,因为任何JPA操作几乎都需要在事务的上下文中执行。
事务的关键应用:
- Repository层事务:Spring Data JPA的Repository接口默认就支持声明式事务。通常不需要手动配置,但可以通过
@Transactional
注解自定义事务行为。这里很需要注意 - 复杂查询:对于执行复杂的数据操作或查询,合适的事务管理确保了在执行过程中数据的一致性和完整性。
3.2 事务传播行为
事务传播行为定义了一个事务性方法如何相对于调用它的方法事务进行交互。Spring定义了几种传播行为,以下是最常用的几种:
REQUIRED
(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。REQUIRES_NEW
: 创建一个新事务,如果当前存在事务,则暂停当前事务。SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。NOT_SUPPORTED
: 以非事务方式执行操作,如果当前存在事务,则暂停当前事务。MANDATORY
: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。NEVER
: 以非事务方式执行,如果当前存在事务,则抛出异常。NESTED
: 如果当前存在事务,则执行一个嵌套的子事务;如果当前没有事务,则表现如REQUIRED
。
3.3事务失效的场景
Spring事务管理虽然强大,但在某些情况下可能会失效,导致事务不生效。以下是一些常见的场景:
-
私有方法调用:在同一个类中,一个非事务性的公共方法内部调用了一个标注为
@Transactional
的私有方法,事务是不会生效的,因为Spring事务是通过代理实现的,只能应用于公共接口方法。 -
自调用问题:一个类中的一个方法调用另一个标注为
@Transactional
的方法,如果是通过this
方法调用的,事务是不会启动的,因为事务的开启需要通过代理对象来实现。 -
异常类型不正确:
@Transactional
注解中可以指定一个异常类型,事务只有在抛出指定异常时才会回滚。如果抛出的异常类型不是事务注解中配置的回滚异常,事务也不会回滚。 -
数据库支持不足:如果数据库或数据库驱动不支持事务或当前隔离级别,那么标注了
@Transactional
的方法也无法正确管理事务。 -
方法是非公开的:如前所述,由于Spring的事务管理是通过AOP代理实现的,只有目标方法是公开的接口方法时,代理才能管理其事务。
4.总结
依赖注入是Spring框架中一个核心的功能,它不仅提高了代码的可维护性和灵活性,还极大地简化了企业级应用的开发。理解和正确使用DI可以帮助开发者构建出更加健壮、可测试且易于维护的Java应用程序。