Spring作为Java开发中最常用的框架,其核心知识主要涉及五个方面:IOC控制反转、AOP面向切面编程、事务管理、事务的传播行为和bean的生命周期。
Spring的核心思想是解耦和简化。
![[Pasted image 20241104081833.png]]
IOC控制反转
控制反转(Inversion of Control,缩写为 IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称 DI)。简单来说,就是本来bean和bean之间的互相调用,被IOC容器给统一管理了,防止了业务bean相互代码侵入。
注意:虽然控制反转这种设计原则非常好用,能大大的降低耦合度但是不是所有系统都能应用这个设计原则,假设你的系统只需要一次编写后再也不修改,你大可以直接对系统类之间的引用写死,毕竟每引用一个技术,整体应用的复杂度又会上一个台阶。
依赖注入的方式主要就分为两种(其实还有一种值注入的方式但是不推荐):
setter注入和构造器注入。
setter注入
与传统的 JavaBean 的写法更相似,程序开发人员更容易理解、接受。通过 setter 方法设定依赖关系显得更加直观、自然。但是,对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring 在创建 Bean 实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
示例:
Java1@Component 2public class UserService { 3private UserRepository userRepository; 4@Autowired 5public void setUserRepository(UserRepository userRepository) { 6this.userRepository = userRepository;7}8}
构造器注入
- 构造器注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
- 对于依赖关系无需变化的 Bean ,构造注入更有用处。因为没有 setter 方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏。
- 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
示例:
Java1@Component 2public class UserService { 3private final UserRepository userRepository; 4@Autowired 5public UserService(UserRepository userRepository) { 6this.userRepository = userRepository; 7}8}
AOP面向切面编程
AOP全名 Aspect-Oriented Programming ,中文直译为面向切面编程,当前已经成为一种比较成熟的编程思想,可以用来很好的解决应用系统中分布于各个模块的交叉关注点问题。在轻量级的J2EE中应用开发中,使用AOP来灵活处理一些具有 横切性质 的系统级服务,如事务处理、安全检查、缓存、对象池管理等,已经成为一种非常适用的解决方案。
为什么我们需要AOP
当我们需要对系统添加一些功能,但这些功能并不影响实际业务功能,我们也不想因为新的功能影响业务功能,于是面向切面编程就应运而生了。 所以,AOP 以横截面的方式插入到主流程中,Spring AOP 面向切面编程能帮助我们无耦合的实现:
- 性能监控,在方法调用前后记录调用时间,方法执行太长或超时报警。
- 缓存代理,缓存某方法的返回值,下次执行该方法时,直接从缓存里获取。
- 软件破解,使用 AOP 修改软件的验证类的判断逻辑。
- 记录日志,在方法执行前后记录系统操作日志。
- 工作流系统,工作流系统需要将业务代码和流程引擎代码混合在一起执行,那么我们可以使用AOP将其分离,并动态挂接业务。
- 权限验证,方法执行前验证是否有权限执行当前方法,没有则抛出没有权限执行异常,有业务代码捕捉。
- 等等
AOP 其实就是从应用中划分出来了一个切面,然后在这个切面里面插入一些 “增强”,最后产生一个增加了新功能的 代理对象,注意,是代理对象,这是Spring AOP 实现的基础。这个代理对象只不过比原始对象(Bean)多了一些功能而已,比如 Bean预处理、Bean后处理、异常处理 等。 AOP 代理的目的就是 将切面织入到目标对象。
示例:
Java1@Aspect2@Component3public class LoggingAspect {4 5 @Before("execution(* com.example.service.*.*(..))")6 public void logBefore(JoinPoint joinPoint) {7 System.out.println("Method " + joinPoint.getSignature().getName() + " starting");8 }9 10 @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")11 public void logAfter(JoinPoint joinPoint, Object result) {12 System.out.println("Method " + joinPoint.getSignature().getName() + " completed");13 }14}
bean的生命周期
其实bean的生命周期理解起来很简单,只要理解了Spring的核心思想:解耦和简化其实也就明白了bean的生命周期。
bean的生命周期分为四部分,定义、实例、增强和销毁。
定义
从 xml 定义 bean关系,properties、yaml、json定义 属性,关系和属性这就能撑起一个基础的bean的定义
实例
通过定义的BeanDefinitionReader扫描定义信息生成bean的定义对象BeanDefinition。
增强
对BeanDefinition添加特殊的业务能力实现增强操作。
销毁
执行DisposableBean接口的destroy方法,然后执行自定义的销毁方式。
总结
- 实例化Bean
- 设置属性值
- 执行BeanNameAware接口的setBeanName方法
- 执行BeanFactoryAware接口的setBeanFactory方法
- 执行ApplicationContextAware接口的setApplicationContext方法
- 执行BeanPostProcessor的postProcessBeforeInitialization方法
- 执行InitializingBean接口的afterPropertiesSet方法
- 执行自定义初始化方法
- 执行BeanPostProcessor的postProcessAfterInitialization方法 ---使用Bean---
- 执行DisposableBean接口的destroy方法
- 执行自定义销毁方法
事务管理
开发中比较频繁使用的就是事务管理,不过在了解Spring的事务管理前,我们需要对事务有基础的理解。
事务
事务是逻辑上的一组操作,要么都执行,要么都不执行。
假设你到外面操作,大概能分为两个操作,一个是吃饭,一个是付钱,万一在这两个操作之间突然出现错误比如银行系统崩溃或者网络故障,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
事务的特性
- 原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- 一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
- 隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
Spring的事务管理
对于单机版事务,ORM 框架对事务的支持都是通过数据库的连接(Connection)来实现的,无论我们用的是 JDBC、JDBCTemplate、Hiberate、Mybatis 他们的底层都是基于 Connection 去做的各种封装,例如 Hiberate 封装了 Transaction 对象,MyBatis 封装了 SqlSession 对象。但本质上底层最终都会调用下面这段代码
Java1// 关闭自动提交,开启事务 2connection.setAutoCommit(false);3// 正常完成提交事务 4connection.commit(); 5// 遇到异常的时候回滚 6connection.rollback();
spring的事务管理主要有两种方式编程式事务和声明式事务。
示例:
Java1 2// 1. 声明式事务3@Service4public class UserService {5 @Transactional(rollbackFor = Exception.class)6 public void createUser(User user) {7 // 业务逻辑8 }9}10 11// 2. 编程式事务12@Service13public class UserService {14 @Autowired15 private TransactionTemplate transactionTemplate;16 17 public void createUser(User user) {18 transactionTemplate.execute(new TransactionCallbackWithoutResult() {19 @Override20 protected void doInTransactionWithoutResult(TransactionStatus status) {21 // 业务逻辑22 }23 });24 }25}
事务的传播行为
Spring 的事务传播属性主要用于解决多个事务方法之间相互调用时,事务如何进行传播和管理的问题。当一个事务方法调用另一个事务方法时,事务传播属性定义了被调用方法应该如何处理事务。大事务中嵌套了很多小事务,它们彼此影响,最终导致最外层大的事务丧失了事务的原子性。
事务传播属性可以控制事务的边界和范围,以确保数据的一致性和完整性。它解决了以下几个问题:
嵌套事务:当一个事务方法内部调用另一个事务方法时,是否创建一个新的事务或者加入已存在的事务。嵌套事务允许在一个事务中存在多个子事务,每个子事务都有自己的保存点,可以独立地进行提交或回滚。通过事务传播属性,可以控制是否开启新的嵌套事务。 事务边界:当一个事务方法被另一个非事务方法调用时,是否开启新的事务。如果事务方法被非事务方法调用,那么根据事务传播属性的设置,可以选择开启新的事务或者不开启事务。 多事务方法协作:当多个事务方法相互协作完成一个复杂的业务逻辑时,事务传播属性可以确保这些方法都在同一个事务中执行,以保证数据的一致性。通过将事务传播属性设置为 REQUIRED,可以要求被调用方法必须在一个已存在的事务中执行,如果不存在事务,则会开启新的事务。 事务的隔离性:事务传播属性还可以影响事务的隔离级别。当一个事务方法被另一个事务方法调用时,事务传播属性可以决定被调用方法使用的事务隔离级别。例如,如果将事务传播属性设置为 REQUIRES_NEW,被调用方法将在一个新的事务中执行,使用独立的隔离级别。
Spring 的事务传播属性可以配置以下七个值,每个值都代表不同的含义和行为:
REQUIRED:如果当前已经存在一个事务,则加入该事务,否则新建一个事务。这是默认值。主要用于增删改的方法。 SUPPORTS:如果当前已经存在一个事务,则加入该事务,否则以非事务的方式执行。主要应用在查询方法。 REQUIRES_NEW:每次都会新建一个事务,如果当前已经存在一个事务,则将当前事务挂起(暂停),始终采用独立事务方法,主要用于日志记录的方法。 MANDATORY:如果当前已经存在一个事务,则加入该事务,否则抛出异常,不常用。 NOT_SUPPORTED:以非事务的方式执行操作,如果当前存在一个事务,则将当前事务挂起(暂停),不常用。 NEVER:以非事务的方式执行操作,如果当前存在一个事务,则抛出异常,不常用。 NESTED:如果当前已经存在一个事务,则在该事务内嵌套一个子事务,否则新建一个事务。如果子事务失败,则只回滚子事务,而不回滚父事务。如果父事务失败,则回滚所有事务。