Spring IOC 和 AOP 完全详解:从入门到精通

Spring IOC 和 AOP 完全详解:从入门到精通

📝 博客简介:本文将深入浅出地讲解Spring框架的两大核心特性——IOC(控制反转)和AOP(面向切面编程)。适合Spring初学者和准备面试的同学阅读。

🎯 知识目标:理解IOC和AOP的概念、实现机制、应用场景,并掌握依赖注入和反射的原理。

📋 目录


一、Spring IOC(控制反转)详解

1.1 什么是IOC?

📖 概念解释

IOC(Inversion of Control,控制反转) 是一种设计原则,用于降低代码之间的耦合度。

💡 通俗理解:想象你是一个老板,以前你需要自己去找员工(new 对象),现在你把这个任务交给HR部门(Spring容器),你只需要告诉HR你需要什么样的员工(声明依赖),HR会帮你找到合适的人(注入对象)。这就是"控制反转"——对象创建的控制权从你的代码转移到了Spring容器。
🔑 核心思想

传统开发模式:

对象A需要对象B → 对象A自己创建对象B(new B()) 

IOC模式:

对象A需要对象B → 对象A声明需要B → Spring容器创建B并注入给A 
📊 对比示例

传统方式(紧耦合):

/** * 传统方式:UserService 直接创建 UserDao * 缺点: * 1. 高耦合:UserService 直接依赖 UserDao 的具体实现 * 2. 难以测试:无法轻易替换 UserDao 的实现 * 3. 不灵活:如果要换一个 UserDao 实现,需要修改 UserService 代码 */publicclassUserService{// 直接在类内部创建依赖对象privateUserDao userDao =newUserDaoImpl();publicvoidaddUser(String username){// 使用 userDao 进行数据库操作 userDao.save(username);}}// UserDao 接口interfaceUserDao{voidsave(String username);}// UserDao 的具体实现classUserDaoImplimplementsUserDao{@Overridepublicvoidsave(String username){System.out.println("保存用户:"+ username);}}

IOC方式(松耦合):

/** * IOC方式:UserService 不再自己创建 UserDao,而是通过依赖注入获得 * 优点: * 1. 低耦合:UserService 只依赖 UserDao 接口,不关心具体实现 * 2. 易于测试:可以轻松注入模拟对象(Mock)进行单元测试 * 3. 灵活性高:可以通过配置切换不同的 UserDao 实现,无需修改代码 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@Service// 告诉Spring这是一个服务类,需要被Spring管理publicclassUserService{// 使用@Autowired注解,让Spring自动注入UserDao的实例// Spring会在容器中查找UserDao类型的Bean,并自动赋值给这个变量@AutowiredprivateUserDao userDao;publicvoidaddUser(String username){// 使用注入的 userDao 对象 userDao.save(username);}}// UserDao 接口定义interfaceUserDao{voidsave(String username);}// UserDao 的实现类importorg.springframework.stereotype.Repository;@Repository// 告诉Spring这是一个数据访问层组件,需要被Spring管理classUserDaoImplimplementsUserDao{@Overridepublicvoidsave(String username){System.out.println("保存用户到数据库:"+ username);}}

1.2 IOC和AOP是通过什么机制来实现的?

🛠️ IOC的实现机制

Spring IOC容器主要通过以下技术实现:

  1. 反射(Reflection):动态创建对象
  2. 工厂模式:管理Bean的生命周期
  3. 注解或XML配置:描述Bean之间的依赖关系
📐 IOC容器工作流程
┌─────────────────────────────────────────────────────┐ │ Spring IOC容器 │ │ │ │ 1. 读取配置(XML/注解) │ │ ↓ │ │ 2. 解析Bean定义信息 │ │ ↓ │ │ 3. 使用反射创建Bean实例 │ │ ↓ │ │ 4. 进行依赖注入(属性赋值) │ │ ↓ │ │ 5. 初始化Bean(调用初始化方法) │ │ ↓ │ │ 6. 将Bean放入容器(可供使用) │ │ ↓ │ │ 7. 容器关闭时销毁Bean │ └─────────────────────────────────────────────────────┘ 
🔧 AOP的实现机制

Spring AOP主要通过以下技术实现:

  1. 动态代理(Dynamic Proxy)
    • JDK动态代理:针对实现了接口的类
    • CGLIB代理:针对没有实现接口的类
  2. 字节码增强:在运行时修改或生成字节码
📊 动态代理原理图
 原始对象(Target) ↓ Spring AOP 检测到需要增强 ↓ 创建代理对象(Proxy) ↓ ┌─────────────────────────────────┐ │ 代理对象 │ │ ┌──────────────────────────┐ │ │ │ 前置通知(Before) │ │ │ └──────────────────────────┘ │ │ ↓ │ │ ┌──────────────────────────┐ │ │ │ 调用原始方法 │ │ │ └──────────────────────────┘ │ │ ↓ │ │ ┌──────────────────────────┐ │ │ │ 后置通知(After) │ │ │ └──────────────────────────┘ │ └─────────────────────────────────┘ 

1.3 怎么理解Spring IOC?

🎯 核心理解

Spring IOC的本质是一个对象工厂,它负责:

  1. 创建对象:根据配置信息创建Bean实例
  2. 管理对象:管理对象的生命周期(从创建到销毁)
  3. 装配对象:处理对象之间的依赖关系
📦 IOC容器的两种类型
容器类型说明应用场景
BeanFactory基础容器,延迟加载Bean资源受限的环境
ApplicationContext高级容器,启动时加载所有Bean企业级应用(推荐)
💡 生活类比:BeanFactory像外卖平台,你点餐(调用getBean)时才开始做菜(创建Bean);ApplicationContext像自助餐厅,所有菜品(Bean)在开门时就已经准备好了。

1.4 依赖注入(DI)详解

📖 什么是依赖注入?

依赖注入(Dependency Injection,DI) 是IOC的一种实现方式,它是指:由Spring容器在运行期间,动态地将依赖关系注入到对象中。

💡 通俗理解:你开了一家餐厅(类),需要厨师(依赖对象)。传统方式是你自己去招聘(new对象),而依赖注入就是HR(Spring容器)直接把合格的厨师送到你的餐厅。
🎯 依赖注入的三种方式
1️⃣ 构造器注入(Constructor Injection)

特点:通过构造方法注入依赖,适合必需的、不可变的依赖。

/** * 构造器注入示例 * 优点: * 1. 依赖关系明确,必须在对象创建时提供 * 2. 注入的对象可以声明为final,保证不可变性 * 3. 便于编写单元测试 * * 适用场景:强制依赖、不可变对象 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassOrderService{// 使用final确保依赖不可变privatefinalUserService userService;privatefinalProductService productService;/** * 构造器注入 * @Autowired 可以省略(Spring 4.3+,只有一个构造器时) * * @param userService 用户服务 * @param productService 商品服务 */@AutowiredpublicOrderService(UserService userService,ProductService productService){// Spring会自动传入这两个参数this.userService = userService;this.productService = productService;System.out.println("OrderService通过构造器注入创建");}/** * 创建订单的业务方法 */publicvoidcreateOrder(String username,String productName){// 使用注入的依赖 userService.addUser(username);System.out.println("订单创建成功:用户="+ username +", 商品="+ productName);}}
2️⃣ Setter方法注入(Setter Injection)

特点:通过setter方法注入依赖,适合可选的、可变的依赖。

/** * Setter注入示例 * 优点: * 1. 灵活性高,可以在对象创建后修改依赖 * 2. 可以注入可选依赖 * * 缺点: * 1. 可能造成对象处于不完整状态 * 2. 依赖关系不够明确 * * 适用场景:可选依赖、需要重新配置的依赖 */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassEmailService{privateMailSender mailSender;// 非final,可以修改/** * Setter方法注入 * Spring会自动调用这个方法,传入MailSender实例 * * @param mailSender 邮件发送器 */@AutowiredpublicvoidsetMailSender(MailSender mailSender){this.mailSender = mailSender;System.out.println("通过Setter方法注入MailSender");}/** * 发送邮件 */publicvoidsendEmail(Stringto,String content){if(mailSender !=null){ mailSender.send(to, content);}else{System.out.println("MailSender未注入,无法发送邮件");}}}
3️⃣ 字段注入(Field Injection)

特点:直接在字段上使用@Autowired注解,最简洁但不推荐。

/** * 字段注入示例 * 优点: * 1. 代码最简洁 * 2. 使用方便 * * 缺点: * 1. 难以进行单元测试(无法轻松注入Mock对象) * 2. 违反了依赖注入的原则(依赖关系不明确) * 3. 无法声明为final * 4. IDE会给出警告 * * 适用场景:快速开发、原型项目(生产环境不推荐) */importorg.springframework.stereotype.Service;importorg.springframework.beans.factory.annotation.Autowired;@ServicepublicclassPaymentService{// 直接在字段上注入,Spring通过反射设置值@AutowiredprivateUserService userService;@AutowiredprivateOrderService orderService;/** * 处理支付 */publicvoidprocessPayment(String username,double amount){System.out.println("处理支付:用户="+ username +", 金额="+ amount);// 使用注入的依赖 userService.addUser(username);}}
🎨 三种注入方式对比
注入方式语法复杂度测试友好度依赖明确性推荐度适用场景
构造器注入中等⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐必需依赖、生产环境
Setter注入简单⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐可选依赖
字段注入最简单⭐⭐⭐⭐⭐⭐快速开发、原型
🏆 最佳实践:优先使用构造器注入,它是Spring官方推荐的方式。

二、Spring AOP(面向切面编程)详解

2.1 什么是AOP?

📖 概念解释

AOP(Aspect Oriented Programming,面向切面编程) 是一种编程范式,它允许程序员模块化横切关注点(Cross-cutting Concerns)。

💡 通俗理解:想象你在开发一个电商系统,每个功能(登录、下单、支付)都需要记录日志、检查权限、统计性能。如果在每个方法里都写这些代码,会非常重复。AOP就像一个"管家",它可以在方法执行前后自动帮你做这些事,而你只需要专注于业务逻辑。
🎯 核心概念
概念英文说明举例
切面Aspect横切关注点的模块化日志切面、事务切面
连接点Join Point程序执行的某个点方法调用、异常抛出
切点Pointcut匹配连接点的表达式所有Service层的方法
通知Advice在切点执行的代码前置通知、后置通知
目标对象Target被增强的对象UserService实例
代理对象ProxyAOP创建的对象UserService的代理
织入Weaving将切面应用到目标的过程Spring在运行时织入
📊 AOP概念图解
 切面(Aspect) ┌─────────────────────┐ │ @Aspect │ │ LoggingAspect │ │ │ │ 切点(Pointcut) │ │ ↓ │ │ execution(* com.example..*(..)) │ │ │ │ 通知(Advice) │ │ ↓ │ │ @Before │ │ @After │ │ @Around │ └─────────────────────┘ ↓ 织入(Weaving) ↓ ┌─────────────────────┐ │ 目标对象(Target) │ │ UserService │ └─────────────────────┘ ↓ ┌─────────────────────┐ │ 代理对象(Proxy) │ │ UserServiceProxy │ └─────────────────────┘ 
🔍 AOP的五种通知类型
/** * AOP通知类型完整示例 * 演示五种通知类型的执行时机和用法 */importorg.aspectj.lang.annotation.*;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.springframework.stereotype.Component;/** * 日志切面:演示各种通知类型 */@Aspect// 标记这是一个切面类@Component// 将切面类注册为Spring BeanpublicclassLoggingAspect{/** * 1. 前置通知(Before Advice) * 在目标方法执行之前执行 * * 使用场景:参数验证、权限检查、日志记录 * * @param joinPoint 连接点,包含方法信息 */@Before("execution(* com.example.service.*.*(..))")publicvoidbeforeAdvice(JoinPoint joinPoint){// 获取方法名String methodName = joinPoint.getSignature().getName();// 获取参数Object[] args = joinPoint.getArgs();System.out.println("=== 前置通知 ===");System.out.println("即将执行方法:"+ methodName);System.out.println("方法参数:"+java.util.Arrays.toString(args));}/** * 2. 后置通知(After Returning Advice) * 在目标方法正常返回后执行(不包括异常情况) * * 使用场景:记录返回值、数据处理、缓存更新 * * @param joinPoint 连接点 * @param result 方法返回值 */@AfterReturning( pointcut ="execution(* com.example.service.*.*(..))", returning ="result"// 指定返回值参数名)publicvoidafterReturningAdvice(JoinPoint joinPoint,Object result){String methodName = joinPoint.getSignature().getName();System.out.println("=== 后置通知 ===");System.out.println("方法执行成功:"+ methodName);System.out.println("返回值:"+ result);}/** * 3. 异常通知(After Throwing Advice) * 在目标方法抛出异常后执行 * * 使用场景:异常日志记录、异常转换、告警通知 * * @param joinPoint 连接点 * @param exception 抛出的异常 */@AfterThrowing( pointcut ="execution(* com.example.service.*.*(..))", throwing ="exception"// 指定异常参数名)publicvoidafterThrowingAdvice(JoinPoint joinPoint,Exception exception){String methodName = joinPoint.getSignature().getName();System.out.println("=== 异常通知 ===");System.out.println("方法执行异常:"+ methodName);System.out.println("异常信息:"+ exception.getMessage());}/** * 4. 最终通知(After Advice) * 在目标方法执行之后执行(无论是否抛出异常) * 类似于finally块 * * 使用场景:资源释放、清理工作 * * @param joinPoint 连接点 */@After("execution(* com.example.service.*.*(..))")publicvoidafterAdvice(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();System.out.println("=== 最终通知 ===");System.out.println("方法执行完毕:"+ methodName);}/** * 5. 环绕通知(Around Advice) * 包围目标方法,可以控制方法的执行 * 最强大的通知类型 * * 使用场景:性能统计、事务管理、缓存处理 * * @param proceedingJoinPoint 可执行的连接点 * @return 方法返回值 * @throws Throwable 方法可能抛出的异常 */@Around("execution(* com.example.service.*.*(..))")publicObjectaroundAdvice(ProceedingJoinPoint proceedingJoinPoint)throwsThrowable{String methodName = proceedingJoinPoint.getSignature().getName();System.out.println("=== 环绕通知开始 ===");System.out.println("准备执行方法:"+ methodName);// 记录开始时间long startTime =System.currentTimeMillis();Object result =null;try{// 执行目标方法(这是关键!) result = proceedingJoinPoint.proceed();// 目标方法执行成功System.out.println("方法执行成功,返回值:"+ result);}catch(Throwable throwable){// 目标方法执行失败System.out.println("方法执行失败:"+ throwable.getMessage());throw throwable;// 重新抛出异常}finally{// 计算执行时间long endTime =System.currentTimeMillis();long executionTime = endTime - startTime;System.out.println("方法执行耗时:"+ executionTime +"ms");System.out.println("=== 环绕通知结束 ===");}// 返回结果(可以修改返回值)return result;}}
📈 通知执行顺序
正常执行流程: @Around 开始 ↓ @Before ↓ 目标方法执行 ↓ @AfterReturning ↓ @After ↓ @Around 结束 异常执行流程: @Around 开始 ↓ @Before ↓ 目标方法执行(抛出异常) ↓ @AfterThrowing ↓ @After ↓ @Around 异常处理 

2.2 Spring AOP主要想解决什么问题?

🎯 解决的核心问题

Spring AOP主要解决代码重复横切关注点分离的问题。

📊 横切关注点示例
 业务逻辑层 ┌─────────────────────────┐ │ │ │ UserService │ ← 日志、事务、权限 │ OrderService │ ← 日志、事务、权限 │ ProductService │ ← 日志、事务、权限 │ PaymentService │ ← 日志、事务、权限 │ │ └─────────────────────────┘ 横切关注点: - 日志记录 - 事务管理 - 权限检查 - 性能监控 
❌ 没有AOP的代码(问题代码)
/** * 没有AOP的代码:每个方法都要写重复的横切逻辑 * 问题: * 1. 代码重复 * 2. 业务逻辑和横切逻辑混在一起 * 3. 难以维护 * 4. 修改横切逻辑需要改动所有方法 */publicclassUserServiceWithoutAOP{publicvoidaddUser(String username){// ===== 横切逻辑开始 =====// 1. 日志记录System.out.println("[日志] 开始执行addUser方法,参数:"+ username);long startTime =System.currentTimeMillis();try{// 2. 权限检查if(!checkPermission()){System.out.println("[权限] 无权限执行此操作");return;}// 3. 事务开启beginTransaction();// ===== 业务逻辑开始 =====System.out.println("执行添加用户的业务逻辑:"+ username);// 实际的业务代码...// ===== 业务逻辑结束 =====// 4. 事务提交commitTransaction();}catch(Exception e){// 5. 事务回滚rollbackTransaction();System.out.println("[异常] 方法执行失败:"+ e.getMessage());}finally{// 6. 性能统计long endTime =System.currentTimeMillis();System.out.println("[性能] 方法执行耗时:"+(endTime - startTime)+"ms");}// ===== 横切逻辑结束 =====}publicvoiddeleteUser(String username){// 同样的横切逻辑又要写一遍...System.out.println("[日志] 开始执行deleteUser方法,参数:"+ username);// ... 重复代码 ...}// 辅助方法privatebooleancheckPermission(){returntrue;}privatevoidbeginTransaction(){}privatevoidcommitTransaction(){}privatevoidrollbackTransaction(){}}
✅ 使用AOP的代码(优雅代码)
/** * 使用AOP的代码:业务逻辑和横切逻辑完全分离 * 优点: * 1. 代码简洁,只关注业务逻辑 * 2. 横切逻辑集中管理 * 3. 易于维护和扩展 * 4. 修改横切逻辑只需修改切面类 */importorg.springframework.stereotype.Service;@ServicepublicclassUserServiceWithAOP{/** * 添加用户 * 注意:这个方法只包含业务逻辑 * 日志、权限、事务等横切逻辑由AOP切面自动处理 */publicvoidaddUser(String username){// 纯粹的业务逻辑,简洁明了System.out.println("执行添加用户的业务逻辑:"+ username);}/** * 删除用户 */publicvoiddeleteUser(String username){// 纯粹的业务逻辑System.out.println("执行删除用户的业务逻辑:"+ username);}}

三、反射机制详解

3.1 什么是反射?

📖 概念解释

反射(Reflection) 是Java提供的一种能力,允许程序在运行时检查和操作类、方法、字段等信息,即使在编译时不知道它们的名称。

💡 通俗理解:想象你是一个侦探,反射就像是一个X光机,可以让你在程序运行时"透视"一个对象,看到它的内部结构(字段)、能力(方法)、身份(类型),甚至可以修改它们。
🔑 反射的核心功能
  1. 获取类信息:获取类的名称、父类、接口、修饰符等
  2. 创建对象:动态创建类的实例
  3. 访问字段:获取和修改对象的字段值(包括私有字段)
  4. 调用方法:动态调用对象的方法(包括私有方法)
  5. 操作数组:动态创建和操作数组
📊 反射API核心类
类名说明主要方法
Class表示类的信息forName(), newInstance(), getName()
Constructor表示构造方法newInstance(), getParameterTypes()
Method表示方法invoke(), getName(), getReturnType()
Field表示字段get(), set(), setAccessible()
🔍 反射完整示例
/** * 反射机制完整示例 * 演示反射的各种用法 */importjava.lang.reflect.*;/** * 用户类(作为反射操作的目标) */classUser{// 私有字段privateString username;privateint age;// 无参构造器publicUser(){System.out.println("无参构造器被调用");}// 有参构造器publicUser(String username,int age){this.username = username;this.age = age;System.out.println("有参构造器被调用:"+ username +", "+ age);}// 公共方法publicvoidsayHello(){System.out.println("Hello, I'm "+ username);}// 私有方法privatevoidsecretMethod(){System.out.println("这是一个私有方法");}// Getter和SetterpublicStringgetUsername(){return username;}publicvoidsetUsername(String username){this.username = username;}publicintgetAge(){return age;}publicvoidsetAge(int age){this.age = age;}@OverridepublicStringtoString(){return"User{username='"+ username +"', age="+ age +"}";}}/** * 反射示例类 */publicclassReflectionDemo{publicstaticvoidmain(String[] args)throwsException{System.out.println("=== 反射机制演示 ===\n");// 1. 获取Class对象的三种方式demonstrateGetClass();// 2. 创建对象demonstrateCreateInstance();// 3. 访问和修改字段demonstrateFieldAccess();// 4. 调用方法demonstrateMethodInvocation();// 5. 获取类信息demonstrateClassInfo();}/** * 演示获取Class对象的三种方式 */privatestaticvoiddemonstrateGetClass()throwsException{System.out.println("=== 1. 获取Class对象 ===");// 方式1:通过类名.classClass<?> clazz1 =User.class;System.out.println("方式1(类名.class):"+ clazz1.getName());// 方式2:通过对象.getClass()User user =newUser();Class<?> clazz2 = user.getClass();System.out.println("方式2(对象.getClass()):"+ clazz2.getName());// 方式3:通过Class.forName()(最常用)Class<?> clazz3 =Class.forName("User");System.out.println("方式3(Class.forName()):"+ clazz3.getName());System.out.println();}/** * 演示创建对象 */privatestaticvoiddemonstrateCreateInstance()throwsException{System.out.println("=== 2. 创建对象 ===");Class<?> clazz =User.class;// 方式1:使用无参构造器创建对象System.out.println("方式1:使用无参构造器");User user1 =(User) clazz.getDeclaredConstructor().newInstance();// 方式2:使用有参构造器创建对象System.out.println("\n方式2:使用有参构造器");Constructor<?> constructor = clazz.getConstructor(String.class,int.class);User user2 =(User) constructor.newInstance("张三",25);System.out.println("创建的对象:"+ user2);System.out.println();}/** * 演示访问和修改字段 */privatestaticvoiddemonstrateFieldAccess()throwsException{System.out.println("=== 3. 访问和修改字段 ===");User user =newUser("李四",30);Class<?> clazz = user.getClass();// 获取私有字段Field usernameField = clazz.getDeclaredField("username");Field ageField = clazz.getDeclaredField("age");// 设置可访问(绕过private限制) usernameField.setAccessible(true); ageField.setAccessible(true);// 读取字段值System.out.println("原始username:"+ usernameField.get(user));System.out.println("原始age:"+ ageField.get(user));// 修改字段值 usernameField.set(user,"王五"); ageField.set(user,35);System.out.println("修改后的对象:"+ user);System.out.println();}/** * 演示调用方法 */privatestaticvoiddemonstrateMethodInvocation()throwsException{System.out.println("=== 4. 调用方法 ===");User user =newUser("赵六",28);Class<?> clazz = user.getClass();// 调用公共方法System.out.println("调用公共方法:");Method sayHelloMethod = clazz.getMethod("sayHello"); sayHelloMethod.invoke(user);// 调用私有方法System.out.println("\n调用私有方法:");Method secretMethod = clazz.getDeclaredMethod("secretMethod"); secretMethod.setAccessible(true);// 绕过private限制 secretMethod.invoke(user);System.out.println();}/** * 演示获取类信息 */privatestaticvoiddemonstrateClassInfo()throwsException{System.out.println("=== 5. 获取类信息 ===");Class<?> clazz =User.class;// 获取类名System.out.println("类名:"+ clazz.getName());System.out.println("简单类名:"+ clazz.getSimpleName());// 获取所有公共字段System.out.println("\n公共字段:");Field[] publicFields = clazz.getFields();for(Field field : publicFields){System.out.println(" "+ field.getName());}// 获取所有字段(包括私有)System.out.println("\n所有字段:");Field[] allFields = clazz.getDeclaredFields();for(Field field : allFields){System.out.println(" "+Modifier.toString(field.getModifiers())+" "+ field.getType().getSimpleName()+" "+ field.getName());}// 获取所有方法System.out.println("\n所有方法:");Method[] methods = clazz.getDeclaredMethods();for(Method method : methods){System.out.println(" "+ method.getName()+"()");}System.out.println();}}

3.2 反射的使用场景

🎯 主要使用场景
场景说明示例
框架开发Spring IOC容器使用反射创建BeanSpring, Hibernate
动态代理AOP使用反射实现动态代理Spring AOP, MyBatis
注解处理读取和处理注解JUnit, Spring
序列化/反序列化JSON与对象互转Jackson, Gson, Fastjson
ORM框架数据库记录映射为对象Hibernate, MyBatis
插件系统动态加载和执行插件Eclipse插件, IDEA插件
工具类通用的工具方法BeanUtils, PropertyUtils
⚠️ 反射的优缺点

优点:

  • ✅ 增加程序的灵活性,可以在运行时动态操作
  • ✅ 降低耦合度,无需在编译时确定具体类型
  • ✅ 支持动态创建对象和调用方法

缺点:

  • ❌ 性能开销大,比直接调用慢10-100倍
  • ❌ 破坏封装性,可以访问私有成员
  • ❌ 代码复杂度高,难以理解和维护

五、常见面试问题汇总

💼 IOC相关面试题

Q1: 什么是IOC?IOC有什么好处?

答案:

IOC(控制反转)是一种设计原则,将对象创建和依赖关系的管理从应用程序代码转移到容器中。

好处:

  1. 降低耦合度:对象之间不直接依赖,而是通过接口依赖
  2. 提高可测试性:可以轻松注入Mock对象进行单元测试
  3. 提高可维护性:修改依赖关系只需修改配置,无需修改代码
  4. 统一管理:所有对象的生命周期由容器统一管理

Q2: IOC和DI的区别是什么?

答案:

  • IOC(控制反转):是一种设计思想,指将对象创建的控制权交给容器
  • DI(依赖注入):是IOC的一种实现方式,指容器在运行时动态地将依赖注入到对象中

关系: IOC是目标,DI是实现IOC的手段。


Q3: Spring IOC容器的初始化过程是怎样的?

答案:

  1. 加载配置:读取XML配置文件或扫描注解
  2. 解析配置:将配置信息解析为BeanDefinition对象
  3. 注册BeanDefinition:将BeanDefinition注册到容器中
  4. 实例化Bean:使用反射创建Bean实例
  5. 属性赋值:进行依赖注入,为Bean的属性赋值
  6. 初始化Bean:调用初始化方法(如@PostConstruct、InitializingBean)
  7. 使用Bean:Bean可以被应用程序使用
  8. 销毁Bean:容器关闭时调用销毁方法(如@PreDestroy、DisposableBean)

Q4: @Autowired和@Resource的区别?

答案:

对比项@Autowired@Resource
来源Spring框架Java标准(JSR-250)
装配方式默认按类型(byType)默认按名称(byName)
required属性支持不支持
适用范围字段、构造器、方法字段、方法
推荐Spring项目推荐跨框架时使用

示例:

// @Autowired:按类型注入@AutowiredprivateUserService userService;// @Resource:按名称注入@Resource(name ="userServiceImpl")privateUserService userService;

Q5: Spring Bean的作用域有哪些?

答案:

作用域说明使用场景
singleton单例(默认)无状态的Bean(Service、Dao)
prototype每次获取创建新实例有状态的Bean
request每次HTTP请求创建Web应用(请求级数据)
session每个Session创建Web应用(用户会话数据)
application整个ServletContext创建Web应用(全局数据)
websocket每个WebSocket创建WebSocket应用

示例:

@Service@Scope("prototype")// 设置为原型作用域publicclassUserService{// ...}

💼 AOP相关面试题

Q6: 什么是AOP?AOP的应用场景有哪些?

答案:

AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、权限)从业务逻辑中分离出来。

应用场景:

  1. 日志记录:记录方法调用信息
  2. 事务管理:自动开启、提交、回滚事务
  3. 权限验证:检查用户是否有权限执行操作
  4. 性能监控:统计方法执行时间
  5. 异常处理:统一处理异常
  6. 缓存处理:自动缓存方法返回值

Q7: AOP的实现原理是什么?JDK动态代理和CGLIB代理的区别?

答案:

实现原理: Spring AOP使用动态代理技术,在运行时为目标对象创建代理对象,在代理对象中织入横切逻辑。

JDK动态代理 vs CGLIB代理:

对比项JDK动态代理CGLIB代理
原理基于接口,实现InvocationHandler基于继承,生成子类
要求目标类必须实现接口目标类不能是final
性能略慢略快
Spring选择实现了接口时默认使用未实现接口时使用

示例:

// JDK动态代理:目标类实现接口publicinterfaceUserService{voidaddUser(String username);}@ServicepublicclassUserServiceImplimplementsUserService{@OverridepublicvoidaddUser(String username){System.out.println("添加用户:"+ username);}}// CGLIB代理:目标类没有实现接口@ServicepublicclassProductService{publicvoidaddProduct(String productName){System.out.println("添加商品:"+ productName);}}

Q8: AOP的五种通知类型分别在什么时候执行?

答案:

通知类型注解执行时机使用场景
前置通知@Before方法执行前参数验证、权限检查
后置通知@AfterReturning方法正常返回后记录返回值、缓存更新
异常通知@AfterThrowing方法抛出异常后异常日志、告警通知
最终通知@After方法执行后(无论是否异常)资源释放、清理工作
环绕通知@Around包围整个方法性能统计、事务管理

执行顺序(正常情况):

@Around 开始 → @Before → 目标方法 → @AfterReturning → @After → @Around 结束 

Q9: 切点表达式怎么写?

答案:

语法格式:

execution(修饰符? 返回类型 包名.类名.方法名(参数) 异常?) 

常用通配符:

  • *:匹配任意字符
  • ..:匹配任意层级的包或任意个参数
  • +:匹配指定类及其子类

示例:

// 1. 匹配所有public方法execution(public**(..))// 2. 匹配service包下所有类的所有方法execution(* com.example.service.*.*(..))// 3. 匹配service包及子包下所有类的所有方法execution(* com.example.service..*.*(..))// 4. 匹配UserService类的所有方法execution(*com.example.service.UserService.*(..))// 5. 匹配add开头的方法execution(* com.example.service.*Service.add*(..))// 6. 匹配只有一个String参数的方法execution(* com.example.service.*Service.*(String))// 7. 匹配第一个参数为String的方法execution(* com.example.service.*Service.*(String,..))

💼 反射相关面试题

Q10: 什么是反射?反射有什么应用?

答案:

反射是Java提供的一种能力,允许程序在运行时检查和操作类、方法、字段等信息。

应用:

  1. Spring框架:IOC容器使用反射创建Bean
  2. ORM框架:Hibernate、MyBatis使用反射映射数据
  3. 动态代理:AOP使用反射实现
  4. 序列化:JSON库使用反射转换对象
  5. 单元测试:Junit使用反射执行测试方法
  6. 注解处理:框架使用反射读取注解

Q11: 反射的性能问题如何优化?

答案:

性能问题原因:

  1. 反射需要查找类信息,比直接调用慢
  2. 需要进行权限检查
  3. 涉及到对象包装/拆包

优化方案:

  1. 缓存反射结果:缓存Class、Method、Field对象
  2. 关闭安全检查method.setAccessible(true)
  3. 使用MethodHandle:Java 7+提供的更高效的方式
  4. 减少反射使用:能直接调用就不用反射
  5. 使用反射工具:Apache Commons BeanUtils

示例:

// 优化前:每次都获取Method对象for(int i =0; i <10000; i++){Method method = clazz.getMethod("sayHello"); method.invoke(user);}// 优化后:缓存Method对象Method method = clazz.getMethod("sayHello"); method.setAccessible(true);// 关闭安全检查for(int i =0; i <10000; i++){ method.invoke(user);}

Q12: 如何获取Class对象?

答案:

有三种方式获取Class对象:

// 方式1:通过类名.class(编译时确定)Class<?> clazz1 =User.class;// 方式2:通过对象.getClass()(运行时获取)User user =newUser();Class<?> clazz2 = user.getClass();// 方式3:通过Class.forName()(最灵活)Class<?> clazz3 =Class.forName("com.example.User");

选择建议:

  • 编译时已知类型:使用 类名.class
  • 已有对象实例:使用 对象.getClass()
  • 类名在配置文件中:使用 Class.forName()

🎓 总结

核心要点回顾

  1. IOC(控制反转)
    • 将对象创建的控制权交给Spring容器
    • 通过依赖注入实现
    • 优先使用构造器注入
  2. AOP(面向切面编程)
    • 用于模块化横切关注点
    • 通过动态代理实现
    • 五种通知类型,环绕通知最强大
  3. 反射(Reflection)
    • 运行时操作类和对象
    • Spring框架的基础技术
    • 注意性能问题,合理使用缓存

学习建议

  1. 📚 理论学习:先理解概念,再看源码实现
  2. 💻 实践练习:动手写代码,加深理解
  3. 🔍 源码阅读:阅读Spring源码,学习设计思想
  4. 📝 总结归纳:整理笔记,形成知识体系

🎉 恭喜你! 你已经完成了Spring IOC和AOP的学习。希望这篇博客能帮助你更好地理解和使用Spring框架。继续加油!

Read more

【保姆级教程】llama.cpp大模型部署全攻略:CPU/GPU全兼容,小白也能轻松上手!

【保姆级教程】llama.cpp大模型部署全攻略:CPU/GPU全兼容,小白也能轻松上手!

一、简介 * • llama.cpp 是一个在 C/C++ 中实现大型语言模型(LLM)推理的工具 * • 支持跨平台部署,也支持使用 Docker 快速启动 * • 可以运行多种量化模型,对电脑要求不高,CPU/GPU设备均可流畅运行 * • 开源地址参考:https://github.com/ggml-org/llama.cpp • 核心工作流程参考: 二、安装与下载模型(Docker方式) 1. 搜索可用模型 • 这里以 qwen3-vl 模型为例,提供了多种量化版本,每种版本的大小不一样,根据自己的电脑性能做选择,如选择(模型+量化标签):Qwen/Qwen3-VL-8B-Instruct-GGUF:Q8_0 • 可以在huggingface官网中搜索可用的量化模型:https://huggingface.co/models?search=

By Ne0inhk

2026 年 AI 辅助编程工具全景对比:Copilot、Cursor、Claude Code 与 Codex 深度解析

引言 2026 年,AI 辅助编程已经从"尝鲜"变成了"标配"。从 GitHub Copilot 的横空出世,到 Cursor 的异军突起,再到 Claude Code 的强势入局,AI 编程助手正在重塑开发者的工作方式。但面对市面上琳琅满目的工具,你是否也有这样的困惑:哪个工具最适合我?它们之间到底有什么区别? 本文将深入对比四款主流 AI 编程工具,帮你找到最适合自己的那一款。 AI 辅助编程的演进之路 从代码补全到智能协作 早期的 AI 编程工具,如 OpenAI Codex,主要聚焦于代码补全——你写一行,它接下一行。但到了 2026 年,AI 编程助手已经进化成真正的&

By Ne0inhk

Obsidian Copilot API密钥配置终极指南:OpenRouter、Gemini、OpenAI一步到位

Obsidian Copilot 是一个强大的AI助手插件,它能将智能对话功能直接集成到你的Obsidian笔记中。要充分发挥其潜力,正确配置API密钥是关键第一步。本指南将手把手教你如何配置OpenRouter、Google Gemini和OpenAI等主流AI提供商的API密钥,让你轻松享受智能笔记体验。✨ 【免费下载链接】obsidian-copilotA ChatGPT Copilot in Obsidian 项目地址: https://gitcode.com/gh_mirrors/ob/obsidian-copilot 🔑 为什么需要配置API密钥? Obsidian Copilot 本身不提供AI模型服务,它需要连接外部的AI服务提供商。配置API密钥后,你可以: * 在笔记中直接与AI对话 * 智能分析和总结笔记内容 * 自动生成文章大纲和草稿 * 基于你的知识库进行问答 🚀 快速开始:配置API密钥 步骤1:打开API密钥设置 在Obsidian Copilot的设置界面中,点击"API Keys"区域的"Set Keys&

By Ne0inhk