Spring知识点全面解析:从基础到面试重点
一、引言
在Java企业级开发领域,Spring框架无疑占据着举足轻重的地位。它以其强大的功能和灵活的设计,为开发者提供了一套全面的解决方案,涵盖了从对象管理到事务处理,再到与其他技术的整合等多个方面。本文将深入探讨Spring的核心概念、重要特性、与其他技术的整合方式,以及在实际开发中需要注意的要点和面试重点。
二、Spring核心概念
2.1 IoC(Inversion of Control)控制反转
2.1.1 概念
IoC是Spring框架的核心思想之一,它将对象的创建和管理从应用程序自身转移到Spring容器中,从而实现了对象之间依赖关系的解耦。在传统的开发方式中,我们常常需要在一个类中手动创建依赖对象,例如:
UserService userService = new UserServiceImpl();
而在Spring的世界里,这些依赖对象由Spring容器来创建并注入到需要它们的类中,大大提高了代码的可维护性和可测试性。
2.1.2 实现方式
IoC主要通过依赖注入(Dependency Injection,DI)来实现,常见的注入方式有以下几种:
- 构造器注入:通过类的构造函数来传递依赖对象。
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
- Setter方法注入:利用类的Setter方法来设置依赖对象。
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
}
- 基于注解的注入:借助
@Autowired
、@Inject
等注解实现依赖对象的自动装配。
public class UserController {
@Autowired
private UserService userService;
}
2.2 DI(Dependency Injection)依赖注入
2.2.1 定义
DI是IoC的具体实现方式,它在运行时将依赖对象传递给目标对象。这种方式使得对象之间的依赖关系更加清晰,并且方便进行单元测试。
2.2.2 优点
- 提高可测试性:在测试时,可以轻松地通过构造器注入一个模拟的依赖对象,而无需在被测试对象内部手动创建真实的依赖实例。例如,在测试
UserController
时,我们可以方便地注入一个模拟的UserService
,从而更容易对UserController
进行单元测试。 - 增强可维护性:依赖关系的解耦使得代码结构更加清晰,修改某个依赖对象的实现时,对其他部分的影响较小。
- 提升可扩展性:当需要添加新的依赖或者修改依赖的实现时,只需要在Spring容器中进行配置,而不需要大量修改业务代码。
2.3 Bean
2.3.1 定义
在Spring中,由Spring容器管理的对象被称为Bean。这些Bean是应用程序的核心组件,它们之间的依赖关系由Spring容器来维护。
2.3.2 配置方式
- XML配置:通过XML配置文件来定义Bean。
<bean id=userService class=com.example.service.UserServiceImpl/>
- Java配置类:利用基于Java的配置类来定义Bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
2.3.3 Bean的作用域
- singleton:这是默认的作用域,在Spring容器中只会存在一个Bean实例。例如,多个
UserController
可能依赖同一个UserService
实例,这个UserService
实例在容器中就是单例的。 - prototype:每次请求获取Bean时,都会创建一个新的实例。适用于有状态的对象,比如在处理多线程请求时,每个请求都需要一个独立的对象实例。
- request:在一次HTTP请求中,会创建并使用同一个Bean实例,请求结束后,该Bean实例会被销毁。主要用于Web应用中,与HTTP请求相关的对象。
- session:在一个HTTP Session中,会创建并使用同一个Bean实例,Session结束后,该Bean实例会被销毁。常用于Web应用中与用户会话相关的对象。
- application:在整个Web应用中,会创建并使用同一个Bean实例,其生命周期与ServletContext相同。
三、Spring AOP(Aspect - Oriented Programming)面向切面编程
3.1 概念
AOP是Spring的另一大核心特性,它将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,从而提高了代码的模块化和可维护性。例如,在多个业务方法中都需要记录日志,如果在每个业务方法中都编写日志记录代码,不仅会导致代码冗余,而且后期维护也会变得困难。使用AOP可以将日志记录逻辑抽取出来,统一进行管理。
3.2 核心术语
- 切面(Aspect):横切关注点的模块化,它包含了一组相关的通知和切入点。例如,一个用于日志记录的切面,可能包含记录方法调用前、调用后日志的通知,以及定义哪些方法需要记录日志的切入点。
- 通知(Advice):在切面的某个特定连接点上执行的动作,包括前置通知(Before Advice)、后置通知(After Advice)、返回后通知(After - returning Advice)、异常通知(After - throwing Advice)和环绕通知(Around Advice)。
- 前置通知:在目标方法调用前执行,比如记录方法调用开始的日志。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Aspect
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Around(@annotation(com.example.annotation.Loggable))
public Object logMethod(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info(Starting method {}, joinPoint.getSignature().getName());
try {
return joinPoint.proceed();
} finally {
logger.info(Finished method {}, joinPoint.getSignature().getName());
}
}
}
- **后置通知**:在目标方法调用后执行,无论方法执行是否成功。
- **返回后通知**:在目标方法成功返回后执行。
- **异常通知**:在目标方法抛出异常时执行。
- **环绕通知**:围绕目标方法执行,可以在方法调用前后执行自定义逻辑,并且可以控制方法是否执行以及返回值。
- 连接点(Join Point):程序执行过程中的某个特定点,如方法调用、异常抛出等。在Spring AOP中,连接点主要指方法调用。
- 切入点(Pointcut):定义了哪些连接点会被通知,通过表达式来匹配方法签名等。例如,
execution(* com.example.service.*.*(..))
表示匹配com.example.service
包下所有类的所有方法。
3.3 实现方式
- 基于代理的AOP:Spring默认使用JDK动态代理或CGLIB代理来实现AOP。如果目标对象实现了接口,Spring会使用JDK动态代理;如果目标对象没有实现接口,Spring会使用CGLIB代理。
- AspectJ:一种功能更强大的AOP框架,Spring也支持集成AspectJ进行AOP编程。
四、Spring事务管理
4.1 事务的特性(ACID)
- 原子性(Atomicity):事务中的操作要么全部成功,要么全部失败回滚。例如,在银行转账操作中,从账户A扣款和向账户B加款这两个操作必须作为一个原子操作,要么都成功,要么都失败。
- 一致性(Consistency):事务执行前后,数据库的完整性约束不会被破坏。比如转账操作前后,账户A和账户B的总金额应该保持不变。
- 隔离性(Isolation):多个并发事务之间相互隔离,不会互相干扰。例如,事务A在修改数据时,事务B不会看到未提交的修改。
- 持久性(Durability):事务一旦提交,其对数据库的修改将永久保存。即使系统崩溃,重启后数据也不会丢失。
4.2 事务管理方式
- 编程式事务管理:通过在代码中手动编写事务管理逻辑,如使用
TransactionTemplate
或PlatformTransactionManager
。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
private final JdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
@Autowired
public UserService(JdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.transactionTemplate = transactionTemplate;
}
public void transferMoney(final int fromUserId, final int toUserId, final double amount) {
transactionTemplate.execute(new TransactionCallback<Void>() {
@Override
public Void doInTransaction(TransactionStatus status) {
jdbcTemplate.update(UPDATE users SET balance = balance -? WHERE user_id =?, amount, fromUserId);
jdbcTemplate.update(UPDATE users SET balance = balance +? WHERE user_id =?, amount, toUserId);
return null;
}
});
}
}
- 声明式事务管理:通过注解(如
@Transactional
)或XML配置来声明事务的边界和属性。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void transferMoney(int fromUserId, int toUserId, double amount) {
// 业务逻辑,数据库操作等
}
}
4.3 事务传播行为
- PROPAGATION_REQUIRED:默认传播行为。如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务。例如,方法A调用方法B,方法A有事务,方法B默认会加入方法A的事务。
- PROPAGATION_REQUIRES_NEW:总是创建一个新的事务。如果当前存在事务,就将当前事务挂起。例如,方法A调用方法B,方法B使用
PROPAGATION_REQUIRES_NEW
,则方法B会在一个新的事务中执行,与方法A的事务相互独立。 - PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作。如果当前存在事务,就将当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。嵌套事务可以独立提交或回滚,但如果外层事务回滚,内层事务也会回滚。
五、Spring与其他技术的整合
5.1 Spring与Hibernate
通过LocalSessionFactoryBean
来配置Hibernate的SessionFactory
,并将其注入到HibernateTemplate
或HibernateDaoSupport
中,从而实现对数据库的操作。
<bean id=sessionFactory class=org.springframework.orm.hibernate5.LocalSessionFactoryBean>
<property name=dataSource ref=dataSource/>
<property name=hibernateProperties>
<props>
<prop key=hibernate.dialect>org.hibernate.dialect.MySQL5Dialect</prop>
<prop key=hibernate.show_sql>true</prop>
</props>
</property>
<property name=mappingResources>
<list>
<value>com/example/entity/User.hbm.xml</value>
</list>
</property>
</bean>
<bean id=userDao class=com.example.dao.UserDaoImpl>
<property name=sessionFactory ref=sessionFactory/>
</bean>
5.2 Spring与MyBatis
使用SqlSessionFactoryBean
来创建SqlSessionFactory
,并通过MapperScannerConfigurer
扫描Mapper接口,将其自动注册为Spring Bean。
<bean id=sqlSessionFactory class=org.mybatis.spring.SqlSessionFactoryBean>
<property name=dataSource ref=dataSource/>
<property name=mapperLocations value=classpath:com/example/mapper/*.xml/>
</bean>
<bean class=org.mybatis.spring.mapper.MapperScannerConfigurer>
<property name=basePackage value=com.example.mapper/>
</bean>
六、注意点
6.1 Bean的生命周期
要深入理解Bean从创建到销毁的整个过程,包括实例化、属性注入、初始化(调用init - method
或实现InitializingBean
接口)、使用以及销毁(调用destroy - method
或实现DisposableBean
接口)。在初始化和销毁方法中,务必注意资源的正确初始化和释放,以避免内存泄漏等问题。
6.2 循环依赖
当两个或多个Bean之间相互依赖时,可能会出现循环依赖问题。Spring通过三级缓存来解决大多数情况下的循环依赖,但对于构造器注入的循环依赖,Spring无法解决,会抛出异常。
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
在这种情况下,需要通过调整设计,如使用Setter注入或重新规划Bean之间的依赖关系来避免循环依赖。
6.3 事务的边界控制
在声明式事务管理中,一定要确保事务的边界设置正确。例如,事务方法不能被同一个类中的其他方法内部调用,否则事务不会生效,因为Spring的事务代理是基于代理对象的,内部调用不会经过代理对象。
6.4 AOP的性能问题
虽然AOP可以显著提高代码的模块化和可维护性,但过多的切面和复杂的切入点表达式可能会对性能产生影响。在使用AOP时,要根据实际需求合理设计切面和切入点,避免不必要的性能损耗。
七、面试重点
- IoC和DI的原理与实现:能够清晰阐述IoC和DI的概念,以及常见的依赖注入方式(构造器注入、Setter注入、注解注入)的优缺点和适用场景。
- Bean的作用域和生命周期:理解不同Bean作用域的特点和应用场景,以及Bean生命周期的各个阶段和相关的回调方法。
- AOP的原理、核心术语和应用场景:掌握AOP的基本概念、核心术语(切面、通知、连接点、切入点),能够举例说明AOP在实际项目中的应用场景,如日志记录、事务管理、权限控制等。
- Spring事务管理:理解事务的ACID特性,掌握声明式事务管理和编程式事务管理的方式,以及事务传播行为的含义和应用场景。
- Spring与其他框架的整合:了解Spring与常见持久层框架(如Hibernate、MyBatis)的整合方式和原理。
- Spring的配置方式:熟悉XML配置和Java配置类两种方式来定义Bean、配置AOP和事务等。
- 解决实际问题的能力:能够分析和解决Spring应用中常见的问题,如循环依赖、事务不生效、AOP切面不执行等问题。
八、总结
Spring框架是Java企业级开发中不可或缺的一部分,掌握其核心概念、特性以及与其他技术的整合方式,对于开发者来说至关重要。通过深入理解本文所介绍的知识点,不仅能够在实际开发中更加得心应手,还能在面试中展现出扎实的技术功底。希望本文能为大家在学习和应用Spring框架的道路上提供有益的帮助。