《深入理解Spring》:AOP面向切面编程深度解析

简介: Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。

一、AOP核心概念:理解横切关注点

1.1 什么是AOP?

面向切面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全控制等。

传统编程 vs AOP编程


// 传统方式:业务逻辑与横切关注点混合
public class UserService {
    public void createUser(User user) {
        // 事务开始
        Transaction transaction = beginTransaction();
        try {
            // 权限检查
            if (!hasPermission()) {
                throw new SecurityException("No permission");
            }
            
            // 业务逻辑
            userRepository.save(user);
            auditService.logAction("CREATE_USER");
            
            // 事务提交
            transaction.commit();
        } catch (Exception e) {
            // 事务回滚
            transaction.rollback();
            throw e;
        }
    }
}
// AOP方式:业务逻辑保持纯净
public class UserService {
    public void createUser(User user) {
        // 纯粹的業務邏輯
        userRepository.save(user);
    }
}

1.2 AOP核心术语

让我们通过一个比喻来理解AOP的核心概念: imagine一个餐厅厨房作为我们的业务系统

对应到AOP术语:

  • 连接点 (Join Point):厨房中的各个工作点(接单、烹饪、装盘)
  • 切点 (Pointcut):选择在哪些工作点进行操作(如"所有烹饪过程")
  • 通知 (Advice):在特定工作点执行的操作(如记录日志、检查库存)
  • 切面 (Aspect):食品安全管理系统(包含所有横切关注点)
  • 目标对象 (Target Object):厨师(执行核心业务逻辑的对象)
  • AOP代理 (AOP Proxy):厨房经理(增强厨师工作的代理)

二、Spring AOP实现机制

2.1 代理模式:Spring AOP的基石

Spring AOP使用代理模式实现AOP功能,主要有两种代理方式:


// 接口代理 - JDK动态代理
public interface UserService {
    void createUser(User user);
}
public class UserServiceImpl implements UserService {
    public void createUser(User user) {
        // 业务实现
    }
}
// 类代理 - CGLIB字节码增强
public class UserService {
    public void createUser(User user) {
        // 业务实现
    }
}
// Spring自动选择代理方式
@Configuration
@EnableAspectJAutoProxy // 启用AOP自动代理
public class AppConfig {
}

2.2 AOP配置的三种方式

2.2.1 XML配置方式(传统)


<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <!-- 定义Bean -->
    <bean id="userService" class="com.example.UserService"/>
    <bean id="loggingAspect" class="com.example.LoggingAspect"/>
    
    <!-- AOP配置 -->
    <aop:config>
        <aop:aspect ref="loggingAspect">
            <aop:pointcut id="serviceMethods" 
                         expression="execution(* com.example.*Service.*(..))"/>
            <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
            <aop:after-returning pointcut-ref="serviceMethods" method="logAfterReturning"/>
        </aop:aspect>
    </aop:config>
</beans>

2.2.2 注解配置方式(推荐)


// 启用AspectJ自动代理
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class AppConfig {
}
// 定义切面
@Aspect
@Component
public class LoggingAspect {
    
    // 定义切点:匹配所有Service层方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    // 前置通知
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("方法执行前: " + methodName + ", 参数: " + Arrays.toString(args));
    }
    
    // 返回后通知
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法执行完成: " + methodName + ", 返回值: " + result);
    }
}

2.2.3 编程方式(高级)


@Configuration
@EnableAspectJAutoProxy
public class ProgrammaticAopConfig implements BeanPostProcessor {
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof UserService) {
            // 创建代理工厂
            ProxyFactory proxyFactory = new ProxyFactory(bean);
            
            // 添加通知
            proxyFactory.addAdvice(new MethodBeforeAdvice() {
                @Override
                public void before(Method method, Object[] args, Object target) throws Throwable {
                    System.out.println("Before method: " + method.getName());
                }
            });
            
            return proxyFactory.getProxy();
        }
        return bean;
    }
}

三、五种通知类型详解

3.1 前置通知(@Before)


@Aspect
@Component
public class SecurityAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    @Before("serviceMethods() && args(user,..)")
    public void checkPermission(JoinPoint joinPoint, User user) {
        String methodName = joinPoint.getSignature().getName();
        
        if (!user.hasPermission(methodName)) {
            throw new SecurityException("用户没有执行 " + methodName + " 的权限");
        }
        
        System.out.println("权限检查通过: " + methodName);
    }
}

3.2 后置通知(@AfterReturning)


@Aspect
@Component
public class MonitoringAspect {
    
    @AfterReturning(
        pointcut = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void monitorPerformance(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + " 执行成功,返回值: " + result);
        
        // 可以在这里记录性能指标、监控数据等
        Metrics.recordSuccess(methodName);
    }
}

3.3 异常通知(@AfterThrowing)


@Aspect
@Component
public class ExceptionHandlingAspect {
    
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "ex"
    )
    public void handleException(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + " 执行异常: " + ex.getMessage());
        
        // 异常处理逻辑:记录日志、发送警报等
        ErrorReporter.reportError(methodName, ex);
        
        // 可以根据异常类型进行不同的处理
        if (ex instanceof DatabaseException) {
            // 数据库异常特殊处理
            DatabaseHealthChecker.checkStatus();
        }
    }
}

3.4 最终通知(@After)


@Aspect
@Component
public class ResourceCleanupAspect {
    
    @After("execution(* com.example.service.*.*(..))")
    public void cleanupResources(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + " 执行完成,进行资源清理");
        
        // 清理资源:关闭文件、数据库连接等
        ResourceManager.cleanup();
        
        // 重置线程局部变量
        ThreadLocalManager.reset();
    }
}

3.5 环绕通知(@Around)- 最强大的通知类型


@Aspect
@Component
public class TransactionAspect {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Around("execution(* com.example.service.*Service.*(..))")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        
        // 创建事务定义
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setName(methodName);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        // 开始事务
        TransactionStatus status = transactionManager.getTransaction(def);
        System.out.println("开始事务: " + methodName);
        
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            
            // 提交事务
            transactionManager.commit(status);
            System.out.println("事务提交: " + methodName);
            
            return result;
            
        } catch (Exception ex) {
            // 回滚事务
            transactionManager.rollback(status);
            System.out.println("事务回滚: " + methodName + ", 原因: " + ex.getMessage());
            
            // 重新抛出异常
            throw ex;
        }
    }
}

四、切点表达式语言详解

4.1 常用切点表达式

Spring AOP使用AspectJ切点表达式语言来定义切点:


@Aspect
@Component
public class PointcutExamplesAspect {
    
    // 1. 匹配所有public方法
    @Pointcut("execution(public * *(..))")
    public void anyPublicMethod() {}
    
    // 2. 匹配指定包下的所有方法
    @Pointcut("within(com.example.service..*)")
    public void inServicePackage() {}
    
    // 3. 匹配实现了特定接口的类
    @Pointcut("this(com.example.service.UserService)")
    public void implementsUserService() {}
    
    // 4. 匹配带有特定注解的方法
    @Pointcut("@annotation(com.example.annotation.Logged)")
    public void annotatedWithLogged() {}
    
    // 5. 匹配带有特定注解的参数
    @Pointcut("@args(com.example.annotation.Validated)")
    public void hasValidatedParameter() {}
    
    // 6. 匹配Bean名称
    @Pointcut("bean(userService) || bean(orderService)")
    public void specificBeans() {}
    
    // 7. 组合切点
    @Pointcut("inServicePackage() && anyPublicMethod()")
    public void publicServiceMethods() {}
}

4.2 复杂切点表达式示例


@Aspect
@Component
public class ComplexPointcutAspect {
    
    // 匹配Service层中以find开头的方法,且第一个参数为Long类型
    @Pointcut("execution(* com.example.service.*.find*(Long, ..))")
    public void findMethodsWithLongId() {}
    
    // 匹配带有@Transactional注解的方法
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}
    
    // 匹配执行时间超过100ms的方法
    @Around("transactionalMethods()")
    public Object monitorSlowMethods(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            return joinPoint.proceed();
        } finally {
            long elapsedTime = System.currentTimeMillis() - startTime;
            if (elapsedTime > 100) {
                String methodName = joinPoint.getSignature().getName();
                System.out.println("慢方法警告: " + methodName + " 执行了 " + elapsedTime + "ms");
            }
        }
    }
}

五、实战案例:完整的AOP应用

5.1 性能监控切面


@Aspect
@Component
public class PerformanceMonitoringAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringAspect.class);
    
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    
    @Pointcut("execution(* com.example.service..*(..)) || " +
              "execution(* com.example.controller..*(..))")
    public void monitorableMethods() {}
    
    @Around("monitorableMethods()")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录开始时间
        startTime.set(System.currentTimeMillis());
        
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 计算执行时间
            long elapsedTime = System.currentTimeMillis() - startTime.get();
            String methodName = joinPoint.getSignature().toShortString();
            
            // 记录性能数据
            if (elapsedTime > 500) {
                logger.warn("方法 {} 执行缓慢: {}ms", methodName, elapsedTime);
            } else {
                logger.debug("方法 {} 执行时间: {}ms", methodName, elapsedTime);
            }
            
            // 清理ThreadLocal
            startTime.remove();
        }
    }
}

5.2 缓存切面


@Aspect
@Component
public class CachingAspect {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Pointcut("@annotation(com.example.annotation.Cacheable)")
    public void cacheableMethods() {}
    
    @Around("cacheableMethods()")
    public Object handleCache(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        // 获取缓存注解
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        String cacheName = cacheable.value();
        long ttl = cacheable.ttl();
        
        // 生成缓存键
        String cacheKey = generateCacheKey(joinPoint);
        
        // 检查缓存
        Cache cache = cacheManager.getCache(cacheName);
        Cache.ValueWrapper cachedValue = cache.get(cacheKey);
        
        if (cachedValue != null) {
            // 缓存命中
            return cachedValue.get();
        }
        
        // 缓存未命中,执行方法
        Object result = joinPoint.proceed();
        
        // 缓存结果
        if (result != null) {
            cache.put(cacheKey, result);
        }
        
        return result;
    }
    
    private String generateCacheKey(ProceedingJoinPoint joinPoint) {
        // 基于方法签名和参数生成唯一的缓存键
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getName();
        Object[] args = joinPoint.getArgs();
        
        return methodName + ":" + Arrays.deepHashCode(args);
    }
}

5.3 自定义注解与AOP结合


// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
    String value() default "";
    boolean logParameters() default true;
    boolean logResult() default false;
}
// 对应的切面
@Aspect
@Component
public class LogExecutionAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(LogExecutionAspect.class);
    
    @Around("@annotation(logExecution)")
    public Object logMethodExecution(ProceedingJoinPoint joinPoint, LogExecution logExecution) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String customMessage = logExecution.value();
        
        // 记录方法开始
        if (logExecution.logParameters()) {
            logger.info("开始执行 {}{},参数: {}", 
                       customMessage.isEmpty() ? "" : customMessage + " - ",
                       methodName, Arrays.toString(joinPoint.getArgs()));
        } else {
            logger.info("开始执行 {}{}", 
                       customMessage.isEmpty() ? "" : customMessage + " - ",
                       methodName);
        }
        
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            long elapsedTime = System.currentTimeMillis() - startTime;
            
            // 记录方法完成
            if (logExecution.logResult()) {
                logger.info("完成执行 {}{},耗时: {}ms,结果: {}", 
                           customMessage.isEmpty() ? "" : customMessage + " - ",
                           methodName, elapsedTime, result);
            } else {
                logger.info("完成执行 {}{},耗时: {}ms", 
                           customMessage.isEmpty() ? "" : customMessage + " - ",
                           methodName, elapsedTime);
            }
            
            return result;
            
        } catch (Exception ex) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.error("执行 {}{} 失败,耗时: {}ms,异常: {}", 
                        customMessage.isEmpty() ? "" : customMessage + " - ",
                        methodName, elapsedTime, ex.getMessage(), ex);
            throw ex;
        }
    }
}
// 使用自定义注解
@Service
public class UserService {
    
    @LogExecution(value = "用户创建", logParameters = true, logResult = false)
    public User createUser(User user) {
        // 业务逻辑
        return userRepository.save(user);
    }
    
    @LogExecution("用户查询")
    public User getUserById(Long id) {
        return userRepository.findById(id);
    }
}

六、Spring AOP最佳实践与陷阱避免

6.1 最佳实践

  1. 合理选择通知类型
// 优先使用环绕通知处理复杂逻辑
@Around("serviceMethods()")
public Object handleComplexLogic(ProceedingJoinPoint joinPoint) throws Throwable {
    // 前置处理
    preProcess();
    
    try {
        // 执行目标方法
        Object result = joinPoint.proceed();
        
        // 后置处理
        postProcess(result);
        return result;
    } catch (Exception ex) {
        // 异常处理
        handleException(ex);
        throw ex;
    } finally {
        // 最终处理
        cleanup();
    }
}


  1. 优化切点表达式性能
// 避免过于复杂的切点表达式
@Pointcut("execution(public * com.example.service..*(..)) && " +
          "!execution(* com.example.service.internal..*(..))")
public void publicServiceMethodsExcludingInternal() {}
// 缓存切点评估结果
@Pointcut("execution(* *(..)) && args(arg) && @annotation(annotation)")
public void complexPointcut(Object arg, SomeAnnotation annotation) {}


  1. 正确处理异常
@Around("serviceMethods()")
public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        return joinPoint.proceed();
    } catch (BusinessException ex) {
        // 业务异常,记录日志但不包装
        logger.warn("业务异常: {}", ex.getMessage());
        throw ex;
    } catch (TechnicalException ex) {
        // 技术异常,记录错误并包装
        logger.error("技术异常", ex);
        throw new RuntimeException("系统错误,请稍后重试", ex);
    }
}


6.2 常见陷阱与解决方案

  1. 陷阱:内部方法调用导致AOP失效
@Service
public class UserService {
    
    public void createUser(User user) {
        // 内部方法调用,AOP不会生效
        validateUser(user); // 不会触发AOP通知
    }
    
    @LogExecution
    public void validateUser(User user) {
        // 验证逻辑
    }
}
// 解决方案:使用AopContext.currentProxy()
@Service
public class UserService {
    
    @Autowired
    private ApplicationContext context;
    
    public void createUser(User user) {
        // 从容器中获取代理对象
        UserService proxy = context.getBean(UserService.class);
        proxy.validateUser(user); // 会触发AOP通知
    }
}


  1. 陷阱:循环依赖问题
@Aspect
@Component
public class ProblematicAspect {
    
    @Autowired
    private UserService userService; // 可能导致循环依赖
}
// 解决方案:使用setter注入或@Lazy
@Aspect
@Component
public class FixedAspect {
    
    private UserService userService;
    
    @Autowired
    @Lazy // 延迟注入解决循环依赖
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}


七、Spring AOP与AspectJ对比

7.1 功能对比

特性

Spring AOP

AspectJ

实现方式

运行时代理

编译时/加载时织入

性能

较好(运行时开销)

优秀(无运行时开销)

连接点支持

仅方法执行

方法执行、构造器调用、字段访问等

织入时机

运行时

编译时、后编译时、加载时

依赖

轻量,仅需Spring

需要AspectJ编译器/织入器

学习曲线

平缓

陡峭

7.2 选择建议

  • 选择Spring AOP:大多数企业应用,需要简单的AOP功能
  • 选择AspectJ:需要高性能、复杂织入、非方法级别拦截的场景


// Spring AOP与AspectJ结合使用
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@EnableLoadTimeWeaving // 启用加载时织入
public class HybridAopConfig {
}
// 使用AspectJ注解定义更强大的切面
@Aspect
public class PowerfulAspect {
    
    // 构造器执行切点(仅AspectJ支持)
    @Pointcut("execution(com.example.User.new(..))")
    public void userConstructor() {}
    
    // 字段访问切点(仅AspectJ支持)
    @Pointcut("get(* com.example.User.name)")
    public void userNameAccess() {}
    
    @Before("userConstructor()")
    public void beforeUserConstruction() {
        System.out.println("User对象即将被创建");
    }
}

总结

Spring AOP是Spring框架的核心功能之一,它通过代理模式实现了强大的面向切面编程能力。通过合理使用AOP,我们可以:

  1. 分离关注点:将横切逻辑与业务逻辑分离,提高代码可维护性
  2. 减少重复代码:通过切面统一处理通用功能,如日志、事务、安全等
  3. 提高灵活性:通过配置即可添加或移除功能,无需修改业务代码
  4. 增强可测试性:业务逻辑更加纯净,便于单元测试

最佳实践总结

  • 优先使用注解配置方式
  • 合理选择通知类型,环绕通知最强大但也要慎用
  • 优化切点表达式性能,避免过于复杂的匹配
  • 注意内部方法调用和循环依赖问题
  • 根据需求选择合适的AOP实现(Spring AOP vs AspectJ)

通过掌握Spring AOP,开发者可以构建出更加模块化、可维护和可扩展的应用程序,真正实现关注点分离的设计理念。

相关文章
|
2月前
|
缓存 安全 Java
Spring Security通用权限管理模型解析
Spring Security作为Spring生态的核心安全框架,结合RBAC与ACL权限模型,基于IoC与AOP构建灵活、可扩展的企业级权限控制体系,涵盖认证、授权流程及数据库设计、性能优化等实现策略。
246 0
|
2月前
|
缓存 安全 Java
Spring Security权限管理解析
Spring Security是Spring生态中的核心安全框架,采用认证与授权分离架构,提供高度可定制的权限管理方案。其基于过滤器链实现认证流程,通过SecurityContextHolder管理用户状态,并结合RBAC模型与动态权限决策,支持细粒度访问控制。通过扩展点如自定义投票器、注解式校验与前端标签,可灵活适配多租户、API网关等复杂场景。结合缓存优化与无状态设计,适用于高并发与前后端分离架构。
269 0
|
2月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
|
2月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
413 0
|
1月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
2月前
|
Java 数据库 数据安全/隐私保护
Spring Boot四层架构深度解析
本文详解Spring Boot四层架构(Controller-Service-DAO-Database)的核心思想与实战应用,涵盖职责划分、代码结构、依赖注入、事务管理及常见问题解决方案,助力构建高内聚、低耦合的企业级应用。
807 1
|
2月前
|
Kubernetes Java 微服务
Spring Cloud 微服务架构技术解析与实践指南
本文档全面介绍 Spring Cloud 微服务架构的核心组件、设计理念和实现方案。作为构建分布式系统的综合工具箱,Spring Cloud 为微服务架构提供了服务发现、配置管理、负载均衡、熔断器等关键功能的标准化实现。本文将深入探讨其核心组件的工作原理、集成方式以及在实际项目中的最佳实践,帮助开发者构建高可用、可扩展的分布式系统。
422 0
|
Java Spring 安全
[Spring实战系列](16)面向切面编程(AOP)概述
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/50651781 1. 简介 在软件中,有些行为对于大多数应用都是通用的。
1600 0
|
4月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
891 0
|
5月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
632 0