一、为什么学习Spring是Java开发的必经之路?
1.1 Spring框架的历史地位与革命意义
Spring发展历程回顾:
2003年:Rod Johnson发布《Expert One-on-One J2EE Design and Development》,提出轻量级J2EE解决方案
2004年:Spring 1.0正式发布,引入IoC容器
2006年:Spring 2.0发布,支持XML命名空间和注解
2009年:Spring 3.0发布,全面支持Java 5+和注解配置
2013年:Spring 4.0发布,支持Java 8和WebSocket
2017年:Spring 5.0发布,支持响应式编程
2022年:Spring 6.0发布,支持Java 17+和Native Image
Spring解决的问题清单:
传统J2EE开发的痛点:
- EJB过度复杂:配置繁琐、学习曲线陡峭
- 代码耦合度高:难以测试和维护
- 事务管理困难:需要手动控制
- 资源管理复杂:连接池、线程池等
- 开发效率低下:重复代码多
Spring提供的解决方案:
✓ 轻量级:无需重型应用服务器
✓ 松耦合:依赖注入降低耦合
✓ 声明式事务:@Transactional简化事务管理
✓ 模板化操作:JdbcTemplate等简化数据库操作
✓ AOP支持:面向切面编程解耦横切关注点
1.2 Spring核心模块全景图(含版本对比)
Spring 5.x vs Spring 6.x 核心变化:
java
// Spring 5.x 特性
@Nullable // 支持可空注解
@NonNullApi // 非空API
// Spring 6.x 新特性
// 1. 支持Java 17+(最低要求)
// 2. 支持Jakarta EE 9+(包名javax→jakarta)
// 3. 响应式编程增强
// 4. Native Image支持(GraalVM)
二、IoC容器:Spring框架的"心脏"
2.1 控制反转(IoC)与依赖注入(DI)深入理解
IoC的三种实现方式对比:
java
// 方式1:构造器注入(Spring推荐)
@Service
public class UserService {
private final UserRepository userRepository;
// 构造器注入
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
// 方式2:Setter注入
@Service
public class OrderService {
private PaymentService paymentService;
// Setter注入
@Autowired
public void setPaymentService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
// 方式3:字段注入(不推荐,仅演示)
@Service
public class ProductService {
@Autowired // 字段注入(存在隐患)
private InventoryService inventoryService;
}
// 方式4:方法注入(任意方法)
@Component
public class NotificationService {
private EmailService emailService;
@Autowired // 任意方法注入
public void prepare(EmailService emailService) {
this.emailService = emailService;
}
}
四种注入方式的对比分析表:
注入方式 优点 缺点 使用场景
构造器注入 1. 依赖不可变(final)
-
完全初始化的对象
-
保证必需依赖 参数较多时代码冗长 推荐首选,特别是必需依赖
Setter注入 1. 可选依赖
-
灵活性高
-
可重新注入 1. 对象可能部分初始化
-
依赖可能为null 可选依赖或需要重新配置
字段注入 1. 代码简洁
-
无setter方法 1. 不能声明final
-
难以测试
-
隐藏依赖关系 不推荐使用,仅遗留代码
方法注入 1. 灵活性高
-
可在任意方法 1. 可能被多次调用
-
不如构造器明确 特殊初始化逻辑
2.2 Bean生命周期全流程解析(配时序图)
Bean从创建到销毁的完整历程:
java
@Component
public class LifecycleBean implements
BeanNameAware,
BeanFactoryAware,
ApplicationContextAware,
InitializingBean,
DisposableBean {
private String name;
// 1. 构造器(实例化)
public LifecycleBean() {
System.out.println("1. 构造器执行: 实例化Bean");
}
// 2. 设置属性(依赖注入)
@Value("${app.name}")
public void setName(String name) {
System.out.println("2. 属性注入: name = " + name);
this.name = name;
}
// 3. BeanNameAware接口
@Override
public void setBeanName(String name) {
System.out.println("3. BeanNameAware: bean名称 = " + name);
}
// 4. BeanFactoryAware接口
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. BeanFactoryAware: 设置BeanFactory");
}
// 5. ApplicationContextAware接口
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5. ApplicationContextAware: 设置ApplicationContext");
}
// 6. BeanPostProcessor前置处理
// 由BeanPostProcessor实现类完成
// 7. @PostConstruct注解方法
@PostConstruct
public void postConstruct() {
System.out.println("7. @PostConstruct: 初始化后执行");
}
// 8. InitializingBean接口
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("8. InitializingBean.afterPropertiesSet: 属性设置完成后");
}
// 9. 自定义init-method
public void customInit() {
System.out.println("9. 自定义init-method: 执行自定义初始化");
}
// 10. BeanPostProcessor后置处理
// 由BeanPostProcessor实现类完成
// 11. Bean准备就绪,可以使用
// 12. @PreDestroy注解方法
@PreDestroy
public void preDestroy() {
System.out.println("12. @PreDestroy: 销毁前执行");
}
// 13. DisposableBean接口
@Override
public void destroy() throws Exception {
System.out.println("13. DisposableBean.destroy: 执行销毁");
}
// 14. 自定义destroy-method
public void customDestroy() {
System.out.println("14. 自定义destroy-method: 执行自定义销毁");
}
}
// 配置类
@Configuration
public class LifecycleConfig {
@Bean(initMethod = "customInit", destroyMethod = "customDestroy")
public LifecycleBean lifecycleBean() {
return new LifecycleBean();
}
// BeanPostProcessor示例
@Bean
public static BeanPostProcessor myBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("6. BeanPostProcessor前置: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("10. BeanPostProcessor后置: " + beanName);
return bean;
}
};
}
}
Bean生命周期时序图总结:
Bean创建流程:
- 实例化(构造器)
- 属性赋值(依赖注入)
- 设置BeanName
- 设置BeanFactory
- 设置ApplicationContext
- BeanPostProcessor前置处理
- @PostConstruct
- InitializingBean.afterPropertiesSet
- 自定义init-method
- BeanPostProcessor后置处理
→ Bean准备就绪
Bean销毁流程:
- @PreDestroy
- DisposableBean.destroy
- 自定义destroy-method
→ Bean被销毁
2.3 Bean作用域深度解析(含实战应用)
Spring支持的6种Bean作用域:
java
// 1. singleton(默认):单例,整个Spring容器只有一个实例
@Scope("singleton") // 或 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Component
public class SingletonService {
// 线程不安全!需要特别注意
private int counter = 0;
public synchronized void increment() {
counter++;
}
}
// 2. prototype:原型,每次获取都创建新实例
@Scope("prototype")
@Component
public class PrototypeService {
private final UUID id = UUID.randomUUID();
public UUID getId() {
return id;
}
}
// 3. request:Web请求作用域,每个HTTP请求一个实例
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class RequestScopedBean {
private final LocalDateTime created = LocalDateTime.now();
public LocalDateTime getCreated() {
return created;
}
}
// 4. session:Web会话作用域,每个HTTP Session一个实例
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class UserPreferences {
private String theme = "light";
private Locale locale = Locale.CHINA;
// getter/setter省略
}
// 5. application:Web应用作用域,整个Web应用一个实例
@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
@Component
public class ApplicationConfig {
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
}
// 6. websocket:WebSocket会话作用域
@Scope(scopeName = "websocket")
@Component
public class WebSocketSessionBean {
private WebSocketSession session;
// getter/setter省略
}
作用域选择指南:
场景 推荐作用域 原因 注意事项
无状态服务类 singleton 节省内存,提高性能 确保线程安全
有状态业务对象 prototype 避免状态污染 注意性能开销
请求相关数据 request 请求隔离 仅在Web环境可用
用户偏好设置 session 会话隔离 注意Session超时
应用级缓存 application 全局共享 注意线程安全
实时通信 websocket 连接隔离 WebSocket专用
三、AOP编程:Spring的"魔术"
3.1 AOP核心概念大白话解读
AOP解决了什么问题?
传统OOP的痛点:
- 代码重复:日志、事务、安全等代码散落各处
- 关注点耦合:业务逻辑与非业务逻辑混杂
- 维护困难:修改横切逻辑需要改动多处
AOP的核心思想:
"横向切割"关注点,将通用功能模块化
现实比喻:
假设业务代码是"汉堡包"的面包和肉饼
AOP就是添加的"生菜、番茄、酱料"
你可以在不改变面包和肉饼的情况下,统一添加配料
3.2 Spring AOP实战:5个最常见切面应用
切面1:方法执行日志
java
/**
-
日志切面:记录方法入参、出参、执行时间
*/
@Aspect
@Component
@Slf4j
public class LoggingAspect {
/**
- 切入点定义:拦截所有Service层方法
/
@Pointcut("execution( com.example.service.. (...))")
public void serviceLayer() {}
/**
-
前置通知:记录方法入参
*/
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("【方法调用】{}.{}() - 参数: {}",
className, methodName, Arrays.toString(args));
}
/**
-
后置通知:记录方法返回值
*/
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
if (result != null) {
// 大型对象只记录摘要
if (result instanceof Collection) {
Collection<?> collection = (Collection<?>) result;
log.info("【方法返回】{}() - 返回集合,大小: {}",
methodName, collection.size());
} else if (result.getClass().isArray()) {
log.info("【方法返回】{}() - 返回数组,长度: {}",
methodName, Array.getLength(result));
} else {
log.info("【方法返回】{}() - 返回值: {}", methodName, result);
}
} else {
log.info("【方法返回】{}() - 返回null", methodName);
}
}
/**
- 异常通知:记录异常信息
*/
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
log.error("【方法异常】{}() - 异常类型: {}, 异常信息: {}",
methodName, ex.getClass().getName(), ex.getMessage(), ex);
}
/**
-
环绕通知:计算方法执行时间(最强大)
*/
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
// 执行目标方法
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - startTime; log.info("【方法执行】{}() - 执行时间: {}ms", methodName, elapsedTime); return result;} catch (IllegalArgumentException ex) {
log.error("【参数非法】{}() - 参数错误: {}", methodName, joinPoint.getArgs());
throw ex;
}
}
}
切面2:声明式事务管理
- 切入点定义:拦截所有Service层方法
java
/**
-
事务管理切面(简化版,Spring已有@Transactional)
-
演示自定义事务逻辑
*/
@Aspect
@Component
@Slf4j
public class TransactionAspect {
@Autowired
private PlatformTransactionManager transactionManager;
@Pointcut("@annotation(com.example.annotation.MyTransactional)")
public void transactionalPointcut() {}
@Around("transactionalPointcut()")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
TransactionStatus status = null;
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
definition.setTimeout(30); // 30秒超时
try { // 开启事务 status = transactionManager.getTransaction(definition); log.info("【事务开始】方法: {}", joinPoint.getSignature().getName()); // 执行目标方法 Object result = joinPoint.proceed(); // 提交事务 transactionManager.commit(status); log.info("【事务提交】方法: {}", joinPoint.getSignature().getName()); return result; } catch (Exception ex) { // 回滚事务 if (status != null && !status.isCompleted()) { transactionManager.rollback(status); log.error("【事务回滚】方法: {} - 原因: {}", joinPoint.getSignature().getName(), ex.getMessage()); } throw ex; }}
}
// 自定义事务注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
}
切面3:接口限流
java
/**
-
限流切面:防止接口被过度调用
*/
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
// 使用Guava的RateLimiter
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Pointcut("@annotation(com.example.annotation.RateLimit)")
public void rateLimitPointcut() {}
@Around("rateLimitPointcut()")
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
// 生成限流器的key:类名+方法名 String key = String.format("%s.%s", method.getDeclaringClass().getName(), method.getName()); // 获取或创建限流器 RateLimiter limiter = limiters.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.value())); // 尝试获取许可 if (limiter.tryAcquire(rateLimit.timeout(), rateLimit.timeUnit())) { return joinPoint.proceed(); } else { log.warn("【接口限流】{} 被限流,当前速率: {}/秒", key, rateLimit.value()); throw new RateLimitException("请求过于频繁,请稍后再试"); }}
}
// 限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
double value(); // 每秒允许的请求数
long timeout() default 0; // 等待超时时间
TimeUnit timeUnit() default TimeUnit.MILLISECONDS; // 时间单位
}
切面4:缓存切面
java
/**
-
缓存切面:方法级缓存
*/
@Aspect
@Component
@Slf4j
public class CacheAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存(一级缓存)
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
@Pointcut("@annotation(com.example.annotation.Cacheable)")
public void cacheablePointcut() {}
@Around("cacheablePointcut()")
public Object cache(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Cacheable cacheable = method.getAnnotation(Cacheable.class);
// 生成缓存key String cacheKey = generateCacheKey(joinPoint, cacheable); // 1. 尝试从本地缓存获取 Object result = localCache.getIfPresent(cacheKey); if (result != null) { log.debug("【缓存命中】本地缓存 - key: {}", cacheKey); return result; } // 2. 尝试从Redis获取 result = redisTemplate.opsForValue().get(cacheKey); if (result != null) { log.debug("【缓存命中】Redis缓存 - key: {}", cacheKey); // 回填本地缓存 localCache.put(cacheKey, result); return result; } // 3. 执行目标方法 log.debug("【缓存未命中】执行方法 - key: {}", cacheKey); result = joinPoint.proceed(); if (result != null) { // 4. 写入缓存 long ttl = cacheable.ttl(); if (ttl > 0) { redisTemplate.opsForValue().set(cacheKey, result, ttl, TimeUnit.SECONDS); } else { redisTemplate.opsForValue().set(cacheKey, result); } localCache.put(cacheKey, result); } return result;}
private String generateCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
StringBuilder keyBuilder = new StringBuilder(cacheable.prefix());
Object[] args = joinPoint.getArgs(); if (args.length > 0) { keyBuilder.append(":"); // 使用参数生成key for (Object arg : args) { if (arg != null) { keyBuilder.append(arg.toString()).append("_"); } } } // 添加方法签名 keyBuilder.append(":").append(joinPoint.getSignature().toShortString()); return keyBuilder.toString();}
}
切面5:权限校验
java
/**
-
权限校验切面
*/
@Aspect
@Component
@Slf4j
public class PermissionAspect {
@Autowired
private HttpServletRequest request;
@Pointcut("@annotation(com.example.annotation.RequiresPermission)")
public void permissionPointcut() {}
@Before("permissionPointcut()")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RequiresPermission requiresPermission = method.getAnnotation(RequiresPermission.class);
// 从请求中获取用户信息(实际项目从Session或Token获取) String currentUser = (String) request.getSession().getAttribute("currentUser"); if (currentUser == null) { throw new AuthenticationException("用户未登录"); } // 获取用户权限(这里简化为从配置获取,实际从数据库查) Set<String> userPermissions = getUserPermissions(currentUser); String[] requiredPermissions = requiresPermission.value(); // 检查是否拥有所有必需权限 for (String requiredPermission : requiredPermissions) { if (!userPermissions.contains(requiredPermission)) { throw new AuthorizationException( String.format("用户[%s]没有权限[%s]", currentUser, requiredPermission)); } } log.info("【权限校验通过】用户: {}, 方法: {}", currentUser, method.getName());}
private Set getUserPermissions(String username) {
// 模拟从数据库获取用户权限
Set permissions = new HashSet<>();
if ("admin".equals(username)) {
permissions.addAll(Arrays.asList("user:add", "user:delete", "user:update", "user:query"));
} else if ("user".equals(username)) {
permissions.add("user:query");
}
return permissions;
}
}
四、Spring配置方式演进史
4.1 XML配置 vs 注解配置 vs Java配置
三种配置方式对比实例:
java
// ===================== 方式1:XML配置(传统方式) =====================
// applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.example"/>
<!-- 数据源配置 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="${db.driver}"/>
<property name="jdbcUrl" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
<property name="maximumPoolSize" value="${db.maxPoolSize}"/>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
// ===================== 方式2:注解配置(混合方式) =====================
@Configuration
@ComponentScan(basePackages = "com.example")
@PropertySource("classpath:application.properties")
@EnableTransactionManagement
public class AppConfig {
@Value("${db.driver}")
private String driverClassName;
@Value("${db.url}")
private String url;
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setMaximumPoolSize(20);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// ===================== 方式3:Java配置(现代方式) =====================
@Configuration
public class DataSourceConfig {
@ConfigurationProperties(prefix = "spring.datasource.hikari")
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
}
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
配置方式演进路线图:
演进路径:XML → 注解+XML → Java配置+注解 → 自动配置
阶段1:Spring 1.x-2.x
主要方式:XML配置
优点:集中管理、与代码分离
缺点:冗长、类型不安全、重构困难
阶段2:Spring 2.5-3.x
主要方式:注解+XML混合
核心注解:@Component, @Autowired, @Service
优点:简化配置、类型安全
缺点:配置分散
阶段3:Spring 4.x-5.x
主要方式:Java配置为主
核心注解:@Configuration, @Bean
优点:类型安全、IDE友好、条件化配置
缺点:需要理解Java配置
阶段4:Spring Boot时代
主要方式:自动配置
核心理念:约定优于配置
优点:开箱即用、极简配置
缺点:黑盒、需要理解自动配置原理
4.2 条件化配置:智能Bean注册
java
// 根据环境注册不同的Bean
@Configuration
public class EnvironmentConfig {
// 条件1:当存在某个类时
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
// 条件2:根据配置属性
@ConditionalOnProperty(prefix = "app.cache", name = "type", havingValue = "redis")
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.create(factory);
}
@ConditionalOnProperty(prefix = "app.cache", name = "type", havingValue = "caffeine")
@Bean
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return cacheManager;
}
// 条件3:根据环境激活的profile
@Profile("dev")
@Bean
public DataSource devDataSource() {
// 开发环境使用H2内存数据库
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema-dev.sql")
.build();
}
@Profile("prod")
@Bean
public DataSource prodDataSource(
@Value("${db.url}") String url,
@Value("${db.username}") String username,
@Value("${db.password}") String password) {
// 生产环境使用MySQL
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
// 条件4:根据Bean是否存在
@ConditionalOnMissingBean(name = "defaultService")
@Bean
public DefaultService defaultService() {
return new DefaultService();
}
// 条件5:自定义条件
@Conditional(OnWindowsCondition.class)
@Bean
public WindowsService windowsService() {
return new WindowsService();
}
@Conditional(OnLinuxCondition.class)
@Bean
public LinuxService linuxService() {
return new LinuxService();
}
}
// 自定义条件类
public class OnWindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("windows");
}
}
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String os = context.getEnvironment().getProperty("os.name");
return os != null && os.toLowerCase().contains("linux");
}
}
五、Spring最佳实践与常见坑点
5.1 必须掌握的10个最佳实践
实践1:使用构造器注入
java
// ✅ 正确做法
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
// 构造器注入
public UserService(UserRepository userRepository, EmailService emailService) {
this.userRepository = userRepository;
this.emailService = emailService;
}
}
// ❌ 错误做法
@Service
public class UserService {
@Autowired // 字段注入
private UserRepository userRepository;
@Autowired // Setter注入(不必要)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
实践2:合理使用Bean作用域
java
// 无状态服务使用singleton
@Scope("singleton")
@Service
public class CalculatorService {
// 线程安全的方法
public int add(int a, int b) {
return a + b;
}
}
// 有状态对象使用prototype
@Scope("prototype")
@Component
public class ShoppingCart {
private List items = new ArrayList<>();
public void addItem(Product product) {
items.add(product);
}
}
实践3:正确配置事务传播行为
java
@Service
@Transactional
public class OrderService {
// REQUIRED(默认):如果当前没有事务,就新建一个事务
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 业务逻辑
}
// REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateInventory(Long productId, int quantity) {
// 库存更新,需要独立事务
}
// NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,把当前事务挂起
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void generateReport() {
// 生成报表,不需要事务
}
// NESTED:如果当前存在事务,则在嵌套事务内执行
@Transactional(propagation = Propagation.NESTED)
public void processPayment(Payment payment) {
// 支付处理
}
}
实践4:异常处理策略
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
ErrorResponse error = new ErrorResponse(
ex.getErrorCode(),
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
// 处理数据验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.toList());
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
"参数验证失败",
errors
);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
// 处理未捕获异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleAllException(Exception ex) {
log.error("未捕获异常: ", ex);
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"系统内部错误,请稍后重试",
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
5.2 必须避免的10个常见坑点
坑点1:循环依赖问题
java
// ❌ 错误的循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // 依赖B
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // 依赖A,形成循环
}
// ✅ 解决方案1:使用构造器注入(Spring会检测)
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
// ✅ 解决方案2:使用@Lazy延迟加载
@Service
public class ServiceA {
@Lazy
@Autowired
private ServiceB serviceB;
}
// ✅ 解决方案3:重构设计,打破循环
@Service
public class ServiceA {
// 不直接依赖B,通过接口或中间层
}
@Service
public class ServiceB {
// 同样不直接依赖A
}
@Component
public class ServiceC {
// 协调A和B的交互
}
坑点2:事务失效场景
java
@Service
public class TransactionService {
// 场景1:方法不是public
@Transactional
private void privateMethod() { // ❌ 事务不会生效
// ...
}
// 场景2:异常被catch吞掉
@Transactional
public void methodWithCatch() {
try {
// 数据库操作
throw new RuntimeException("业务异常");
} catch (Exception e) {
// ❌ 异常被catch,事务不会回滚
log.error("发生异常", e);
}
}
// 场景3:异常类型不匹配
@Transactional(rollbackFor = RuntimeException.class)
public void methodWithException() throws Exception {
throw new Exception("检查异常"); // ❌ Exception不是RuntimeException
}
// 场景4:自调用问题
public void selfInvocation() {
this.transactionalMethod(); // ❌ 自调用,事务失效
}
@Transactional
public void transactionalMethod() {
// ...
}
// ✅ 正确做法
@Transactional(rollbackFor = Exception.class)
public void correctMethod() {
try {
// 业务操作
} catch (BusinessException e) {
// 业务异常,需要回滚
throw e;
} catch (Exception e) {
// 其他异常,记录日志后重新抛出
log.error("系统异常", e);
throw new RuntimeException(e);
}
}
}
坑点3:Bean的线程安全问题
java
// ❌ 错误示例:singleton Bean中定义可变成员变量
@Service
public class UnsafeService {
private List data = new ArrayList<>(); // 可变的成员变量
public void addData(String item) {
data.add(item); // ❌ 多线程下不安全
}
}
// ✅ 解决方案1:使用ThreadLocal
@Service
public class SafeService1 {
private ThreadLocal<List> threadLocalData =
ThreadLocal.withInitial(ArrayList::new);
public void addData(String item) {
threadLocalData.get().add(item); // ✅ 线程隔离
}
public void clearThreadLocal() {
threadLocalData.remove(); // 重要:使用后清理,防止内存泄漏
}
}
// ✅ 解决方案2:使用方法局部变量
@Service
public class SafeService2 {
// 不定义成员变量,所有数据通过方法参数传递
public List processData(List input) {
List result = new ArrayList<>(); // ✅ 局部变量,线程安全
// 处理逻辑
return result;
}
}
// ✅ 解决方案3:使用并发集合
@Service
public class SafeService3 {
private final ConcurrentHashMap<String, Object> cache =
new ConcurrentHashMap<>(); // ✅ 线程安全的集合
public void putToCache(String key, Object value) {
cache.put(key, value);
}
}
六、Spring学习路线图与实战建议
6.1 从入门到精通的5个阶段
阶段1:核心基础(1-2周)
学习重点:
✓ IoC容器原理与使用
✓ Bean生命周期与管理
✓ 依赖注入的三种方式
✓ 常用注解:@Component, @Autowired, @Service等
实战项目:学生管理系统
- 使用XML配置实现
- 使用注解配置重构
- 使用Java配置再重构
阶段2:AOP与事务(1-2周)
学习重点:
✓ AOP核心概念与实现
✓ 通知类型与使用场景
✓ 声明式事务管理
✓ 事务传播行为与隔离级别
实战项目:银行转账系统
- 实现转账业务
- 添加事务管理
- 使用AOP添加日志和监控
阶段3:数据访问(2-3周)
学习重点:
✓ Spring JDBC与JdbcTemplate
✓ Spring Data JPA
✓ 多数据源配置
✓ 缓存集成
实战项目:电商商品系统
- 实现CRUD操作
- 集成Redis缓存
- 配置读写分离
阶段4:Web开发(2-3周)
学习重点:
✓ Spring MVC架构
✓ RESTful API设计
✓ 参数绑定与验证
✓ 异常处理与统一响应
实战项目:博客系统API
- 设计RESTful接口
- 实现用户认证
- 添加API文档
阶段5:高级特性(3-4周)
学习重点:
✓ Spring Security安全框架
✓ 响应式编程WebFlux
✓ 消息队列集成
✓ 微服务与Spring Cloud
实战项目:微服务架构系统
- 拆分微服务
- 实现服务间通信
- 添加服务监控
6.2 学习资源推荐
官方文档:
Spring Framework Documentation
Spring Guides
经典书籍:
《Spring实战(第5版)》- Craig Walls
《Spring Boot编程思想》- 小马哥
《Spring源码深度解析》- 郝佳
视频教程:
B站:尚硅谷Spring5框架最新版教程
慕课网:Spring MVC+Spring+MyBatis整合开发
极客时间:Spring Boot与Kubernetes云原生微服务实践
实战练习平台:
GitHub:搜索Spring相关开源项目
LeetCode:刷Spring相关面试题
自己动手:从0到1搭建完整项目
6.3 面试常见问题与回答
Q1:Spring框架的核心是什么?
A:Spring框架的核心是IoC(控制反转)和AOP(面向切面编程)。
IoC通过依赖注入实现对象的创建和依赖关系的管理,降低了组件间的耦合度。
AOP通过将横切关注点(如日志、事务)模块化,提高了代码的复用性和可维护性。
Q2:BeanFactory和ApplicationContext有什么区别?
A:BeanFactory是Spring的基础IoC容器,提供基本的依赖注入功能。
ApplicationContext是BeanFactory的子接口,提供了更多企业级功能:
- 国际化支持
- 事件发布机制
- 资源加载便利
- AOP集成
在实际开发中,通常使用ApplicationContext。
Q3:Spring Bean的作用域有哪些?
A:Spring支持6种Bean作用域:
- singleton:默认,每个Spring容器中只有一个实例
- prototype:每次请求都创建新实例
- request:每个HTTP请求创建一个实例(Web环境)
- session:每个HTTP Session创建一个实例(Web环境)
- application:每个ServletContext创建一个实例(Web环境)
- websocket:每个WebSocket会话创建一个实例(WebSocket环境)
结语:开启Spring之旅
Spring框架作为Java企业级开发的基石,掌握它不仅是为了应对工作需求,更是为了构建可维护、可扩展的软件系统。学习Spring的过程可能会遇到一些挑战,但记住:
每个遇到的问题都是成长的机会,每个解决的bug都是经验的积累。
下一步行动建议:
立即动手:不要只看不练,从今天开始写代码
由浅入深:先掌握核心概念,再学习高级特性
实践为主:通过实际项目巩固理论知识
参与社区:在CSDN、GitHub上与其他开发者交流
持续学习:Spring生态在不断发展,保持学习状态
学习宣言:
从今天起,做一个Spring开发者
关心IoC和AOP
我有一个项目,面向对象,春暖花开
资源福利:
关注我并私信"Spring入门",获取:
本文完整代码示例
Spring学习脑图PDF
常见问题解决方案集
实战项目模板
让我们在Spring的世界里,一起编写优雅的代码,构建强大的系统!🚀
下期预告:《SpringBoot入门:让Spring开发飞起来》
将带你了解:
SpringBoot如何简化Spring配置
自动配置原理揭秘
快速构建RESTful API
生产级应用的最佳实践
互动问题:
你在学习Spring过程中遇到的最大困难是什么?
在评论区分享,我将选取3位同学的问题进行详细解答!
今日行动:
点赞收藏本文,方便随时查阅
关注我,获取更多Spring系列教程
在本地环境运行第一个Spring程序
在评论区打卡你的学习进度
祝学习顺利,coding愉快! 💪