🤟致敬读者
- 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉
📘博主相关
文章目录
📃文章前言
- 🔷文章均为学习工作中整理的笔记。
- 🔶如有错误请指正,共同学习进步。
Java 依赖注入、控制反转与面向切面:面试深度解析
一、控制反转(IoC)与依赖注入(DI)
1. 核心概念解析
控制反转(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:构造器注入为什么被推荐?
- 不可变性:依赖可声明为final
- 空安全:对象创建时依赖必须就绪
- 测试友好:无需容器即可测试
- 循环依赖检测:启动时即可发现循环依赖
- 线程安全:避免并发修改问题
Q3:Spring如何解决循环依赖?
- 三级缓存机制:
- 一级缓存:完整Bean(
singletonObjects
) - 二级缓存:早期暴露Bean(
earlySingletonObjects
) - 三级缓存:Bean工厂(
singletonFactories
)
- 一级缓存:完整Bean(
- 解决流程:
Q4:@Autowired和@Resource的区别?
特性 | @Autowired |
@Resource |
---|---|---|
来源 | Spring框架 | JSR-250标准 |
注入方式 | 按类型(可配合@Qualifier) | 按名称(可指定name属性) |
支持参数 | required | name, type |
适用场景 | Spring专属项目 | 需要跨框架兼容性 |
二、面向切面编程(AOP)
1. 核心概念解析
核心组件:
- 切面(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选择策略:
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:如何选择代理方式?
- 考虑因素:
- 目标类是否实现接口 → 是:JDK代理;否:CGLIB
- 是否需要代理非public方法 → 是:CGLIB
- 性能要求 → 高:AspectJ编译时织入
- 应用场景 → Spring Boot默认CGLIB
Q2:AOP中的设计模式?
代理模式:
- JDK动态代理:
java.lang.reflect.Proxy
- CGLIB:
net.sf.cglib.proxy.Enhancer
- JDK动态代理:
责任链模式:
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不生效问题?
检查切点表达式:
// 打印所有被代理的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); }; }
确认代理方式:
// 检查Bean是否被代理 if (AopUtils.isAopProxy(bean)) { System.out.println(bean + " is proxied"); }
四、最佳实践与避坑指南
DI最佳实践:
- 优先使用构造器注入
- 避免字段注入(不利于测试和不变性)
- 使用
@Qualifier
解决歧义依赖 - 对可选依赖使用
@Autowired(required=false)
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(); }
性能优化:
// 优化切点表达式 @Pointcut("within(com.service..*) && execution(public * *(..))") public void servicePublicMethods() {} // 使用条件编译(AspectJ) @Aspect @ConditionalOnExpression("${aop.enabled:true}") public class PerformanceAspect { /* ... */ }
五、面试深度问题
Q1:Spring如何整合IoC和AOP?
- 核心流程:
- Bean创建阶段:
AbstractAutowireCapableBeanFactory.createBean()
- 判断是否需要代理:
AbstractAutoProxyCreator.postProcessAfterInitialization()
- 创建代理:
ProxyFactory.getProxy()
- 织入切面:将Advisor应用到目标类
- Bean创建阶段:
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?
实现自定义
Pointcut
:public class CustomPointcut extends StaticMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { return method.isAnnotationPresent(CustomAnnotation.class); } }
创建自定义
Advisor
:@Bean public Advisor customAdvisor() { CustomPointcut pointcut = new CustomPointcut(); Advice advice = new CustomInterceptor(); return new DefaultPointcutAdvisor(pointcut, advice); }
掌握这些核心概念和实战技巧,不仅能应对面试中的深度问题,更能构建高内聚、低耦合的企业级应用。
📜文末寄语
- 🟠关注我,获取更多内容。
- 🟡技术动态、实战教程、问题解决方案等内容持续更新中。
- 🟢《全栈知识库》技术交流和分享社区,集结全栈各领域开发者,期待你的加入。
- 🔵加入开发者的《专属社群》,分享交流,技术之路不再孤独,一起变强。
- 🟣点击下方名片获取更多内容🍭🍭🍭👇