Java 依赖注入、控制反转与面向切面:面试深度解析

发布于:2025-06-06 ⋅ 阅读:(19) ⋅ 点赞:(0)

🤟致敬读者

  • 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉

📘博主相关



📃文章前言

  • 🔷文章均为学习工作中整理的笔记。
  • 🔶如有错误请指正,共同学习进步。

Java 依赖注入、控制反转与面向切面:面试深度解析

在这里插入图片描述

一、控制反转(IoC)与依赖注入(DI)

1. 核心概念解析
传统编程
对象主动创建依赖
IoC/DI
容器管理依赖
对象被动接收依赖
  • 控制反转(IoC)

    • 设计原则:将对象创建和绑定的控制权从应用程序代码转移到外部容器
    • 实现方式:依赖查找(DL)和依赖注入(DI)
    • 核心思想:“好莱坞原则” - 不要调用我们,我们会调用你
  • 依赖注入(DI)

    • IoC的具体实现方式
    • 三种注入方式:
      // 1. 构造器注入(推荐)
      public class UserService {
          private final UserRepository repo;
          
          @Autowired
          public UserService(UserRepository repo) {
              this.repo = repo;
          }
      }
      
      // 2. Setter注入
      public class OrderService {
          private PaymentGateway gateway;
          
          @Autowired
          public void setGateway(PaymentGateway gateway) {
              this.gateway = gateway;
          }
      }
      
      // 3. 字段注入(不推荐)
      public class ProductService {
          @Autowired
          private InventoryService inventory;
      }
      
2. 高频面试题

Q1:IoC和DI的区别与联系?

  • 区别
    • IoC是设计原则(思想层面)
    • DI是实现模式(技术层面)
  • 联系
    • DI是IoC最常用的实现方式
    • Spring框架通过DI实现IoC容器

Q2:构造器注入为什么被推荐?

  1. 不可变性:依赖可声明为final
  2. 空安全:对象创建时依赖必须就绪
  3. 测试友好:无需容器即可测试
  4. 循环依赖检测:启动时即可发现循环依赖
  5. 线程安全:避免并发修改问题

Q3:Spring如何解决循环依赖?

  • 三级缓存机制
    1. 一级缓存:完整Bean(singletonObjects
    2. 二级缓存:早期暴露Bean(earlySingletonObjects
    3. 三级缓存:Bean工厂(singletonFactories
  • 解决流程
    BeanA 容器 BeanB 创建A,放入三级缓存 需要B 创建B 需要A(从三级缓存获取早期A) 完成创建,放入一级缓存 完成创建,放入一级缓存 BeanA 容器 BeanB

Q4:@Autowired和@Resource的区别?

特性 @Autowired @Resource
来源 Spring框架 JSR-250标准
注入方式 按类型(可配合@Qualifier) 按名称(可指定name属性)
支持参数 required name, type
适用场景 Spring专属项目 需要跨框架兼容性

二、面向切面编程(AOP)

1. 核心概念解析
Aspect
+Pointcut()
+Advice()
«interface»
Advice
+Before()
+After()
+Around()
  • 核心组件

    • 切面(Aspect):横切关注点的模块化
    • 连接点(Joinpoint):程序执行点(方法调用、异常抛出等)
    • 切点(Pointcut):匹配连接点的表达式
    • 通知(Advice):在切点执行的动作
    • 织入(Weaving):将切面应用到目标对象的过程
  • 通知类型

    @Aspect
    public class LoggingAspect {
        // 前置通知
        @Before("execution(* com.service.*.*(..))")
        public void logBefore(JoinPoint jp) {
            System.out.println("Method: " + jp.getSignature().getName());
        }
        
        // 环绕通知(最强大)
        @Around("@annotation(com.audit.Loggable)")
        public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.currentTimeMillis();
            Object result = pjp.proceed();
            long duration = System.currentTimeMillis() - start;
            System.out.println("Duration: " + duration + "ms");
            return result;
        }
    }
    
2. 高频面试题

Q1:Spring AOP和AspectJ的区别?

特性 Spring AOP AspectJ
织入方式 运行时代理 编译时/加载时织入
性能 有运行时开销 无运行时开销
连接点支持 仅方法级别 方法、构造器、字段等
依赖 轻量级,内置于Spring 需要额外编译器/类加载器
适用场景 普通应用 高性能、复杂切面需求

Q2:JDK动态代理和CGLIB如何选择?

  • JDK动态代理
    • 要求目标类实现接口
    • 基于反射,生成接口代理类
    • 示例:Proxy.newProxyInstance()
  • CGLIB代理
    • 通过继承目标类生成子类
    • 不需要接口
    • 无法代理final类/方法
  • Spring选择策略
    Yes
    No
    目标类有接口?
    JDK动态代理
    CGLIB代理
    强制CGLIB
    配置proxyTargetClass=true

Q3:如何实现自定义注解的切面?

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuditLog {
    String action();
}

@Aspect
@Component
public class AuditAspect {
    @AfterReturning(
        value = "@annotation(auditLog)",
        returning = "result"
    )
    public void audit(AuditLog auditLog, Object result) {
        String action = auditLog.action();
        // 保存审计日志到DB
        auditRepository.save(new AuditRecord(action, result));
    }
}

// 使用示例
@Service
public class OrderService {
    @AuditLog(action = "PLACE_ORDER")
    public Order createOrder(OrderRequest request) {
        // 业务逻辑
    }
}

三、综合实战与设计模式

1. 典型应用场景
  • 事务管理(@Transactional实现原理):

    @Transactional(propagation = Propagation.REQUIRED,
                   isolation = Isolation.DEFAULT,
                   timeout = 30)
    public void transfer(Account from, Account to, BigDecimal amount) {
        // 使用AOP代理实现:
        // 1. 获取连接
        // 2. 设置隔离级别
        // 3. try{业务逻辑} catch{回滚} finally{关闭连接}
    }
    
  • 安全审计

    @Before("within(@org.springframework.stereotype.Controller *)")
    public void logControllerAccess(JoinPoint jp) {
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        logger.info("User: " + user + " accessed: " + jp.getSignature());
    }
    
2. 高频面试题

Q1:如何选择代理方式?

  • 考虑因素
    1. 目标类是否实现接口 → 是:JDK代理;否:CGLIB
    2. 是否需要代理非public方法 → 是:CGLIB
    3. 性能要求 → 高:AspectJ编译时织入
    4. 应用场景 → Spring Boot默认CGLIB

Q2:AOP中的设计模式?

  1. 代理模式

    • JDK动态代理:java.lang.reflect.Proxy
    • CGLIB:net.sf.cglib.proxy.Enhancer
  2. 责任链模式

    public interface MethodInterceptor {
        Object invoke(MethodInvocation invocation) throws Throwable;
    }
    
    // 拦截器链执行
    public class ReflectiveMethodInvocation implements MethodInvocation {
        private final List<MethodInterceptor> interceptors;
        private int currentInterceptorIndex = -1;
        
        public Object proceed() {
            if (this.currentInterceptorIndex == this.interceptors.size() - 1) {
                return invokeJoinpoint();
            }
            MethodInterceptor interceptor = 
                interceptors.get(++this.currentInterceptorIndex);
            return interceptor.invoke(this);
        }
    }
    

Q3:如何调试AOP不生效问题?

  1. 检查切点表达式:

    // 打印所有被代理的Bean
    @Bean
    public CommandLineRunner aopDebug(ApplicationContext ctx) {
        return args -> {
            String[] beans = ctx.getBeanDefinitionNames();
            Arrays.stream(beans)
                  .filter(name -> ctx.getBean(name).getClass().getName().contains("$$"))
                  .forEach(System.out::println);
        };
    }
    
  2. 确认代理方式:

    // 检查Bean是否被代理
    if (AopUtils.isAopProxy(bean)) {
        System.out.println(bean + " is proxied");
    }
    

四、最佳实践与避坑指南

  1. DI最佳实践

    • 优先使用构造器注入
    • 避免字段注入(不利于测试和不变性)
    • 使用@Qualifier解决歧义依赖
    • 对可选依赖使用@Autowired(required=false)
  2. AOP避坑指南

    • 避免在切面中处理业务逻辑
    • 谨慎使用@Around(必须调用proceed())
    • 注意切点表达式的性能影响
    • 避免自调用(this.method())导致的AOP失效
    // 错误示例(自调用导致AOP失效)
    public void process() {
        this.validate(); // 不会被AOP拦截
    }
    
    @Transactional
    public void validate() { /* ... */ }
    
    // 解决方案:通过代理调用
    @Autowired
    private ApplicationContext context;
    
    public void process() {
        context.getBean(this.getClass()).validate();
    }
    
  3. 性能优化

    // 优化切点表达式
    @Pointcut("within(com.service..*) && execution(public * *(..))")
    public void servicePublicMethods() {}
    
    // 使用条件编译(AspectJ)
    @Aspect
    @ConditionalOnExpression("${aop.enabled:true}")
    public class PerformanceAspect { /* ... */ }
    

五、面试深度问题

Q1:Spring如何整合IoC和AOP?

  • 核心流程
    1. Bean创建阶段:AbstractAutowireCapableBeanFactory.createBean()
    2. 判断是否需要代理:AbstractAutoProxyCreator.postProcessAfterInitialization()
    3. 创建代理:ProxyFactory.getProxy()
    4. 织入切面:将Advisor应用到目标类

Q2:如何实现跨切面数据传递?

// 使用ThreadLocal
public class RequestContext {
    private static final ThreadLocal<Map<String, Object>> context = new ThreadLocal<>();
    
    public static void put(String key, Object value) {
        getContextMap().put(key, value);
    }
}

// 前置切面
@Before("@within(org.springframework.web.bind.annotation.RestController)")
public void initContext() {
    RequestContext.put("startTime", System.currentTimeMillis());
}

// 后置切面
@AfterReturning("@within(org.springframework.web.bind.annotation.RestController)")
public void logContext() {
    long start = (long) RequestContext.get("startTime");
    System.out.println("Request took: " + (System.currentTimeMillis() - start) + "ms");
}

Q3:如何扩展Spring AOP?

  1. 实现自定义Pointcut

    public class CustomPointcut extends StaticMethodMatcherPointcut {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return method.isAnnotationPresent(CustomAnnotation.class);
        }
    }
    
  2. 创建自定义Advisor

    @Bean
    public Advisor customAdvisor() {
        CustomPointcut pointcut = new CustomPointcut();
        Advice advice = new CustomInterceptor();
        return new DefaultPointcutAdvisor(pointcut, advice);
    }
    

掌握这些核心概念和实战技巧,不仅能应对面试中的深度问题,更能构建高内聚、低耦合的企业级应用。


📜文末寄语

  • 🟠关注我,获取更多内容。
  • 🟡技术动态、实战教程、问题解决方案等内容持续更新中。
  • 🟢《全栈知识库》技术交流和分享社区,集结全栈各领域开发者,期待你的加入。
  • 🔵​加入开发者的《专属社群》,分享交流,技术之路不再孤独,一起变强。
  • 🟣点击下方名片获取更多内容🍭🍭🍭👇