- 什么是IOC?
- IOC解决了什么问题?
- IOC和DI的区别?
- 什么是AOP?
- AOP解决了什么问题?
- AOP为什么叫做面向切面编程?
- AOP实现方式有哪些?
IOC和AOP不是Spring提出来的,它们是在Spring之前其实已经存在了,只不过当时偏向于理论。Spring在技术层次将这两个思想进行很好的实现。
IOC(Inversion of control)
什么是IOC?
IOC(Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java开发领域对象的创建以及管理的问题。
例如:现有类A依赖于类B
- 传统开发方式:往往是在类A中new关键字来new一个B的对象出来。
- 使用IOC思想的开发方式:不通过new关键字来创建对象,而是通过IOC容器(Spring框架)来帮助我们实例化对象。我们需要哪个对象,直接从IOC容器里面去取即可。
从以上两种开发方式的对比来看:我们"丧失了一个权力"(创建、管理对象的权力),从而也得到一个好处(不用再考虑对象的创建、管理等一系列的事情)
为什么叫控制反转?
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(IOC容器)

IOC解决了什么问题?
IOC的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
- 对象之间的耦合或者说依赖程度低;
- 资源变的容易管理;比如你用Spring容器提供的话很容易实现一个单例
例如:现有针对User的操作,利用Service和Dao两次结构仅从开发
在没有使用IOC思想的情况下,Service层想要使用Dao层的具体实现的话,需要通过new关键字在UserServiceImpl中手动new出IUserDao的具体实现类UserDaoImpl(不能直接new接口类)。
很完美,这种方式也是可以实现的,但是我们想象一下如下场景:
开发过程中突然接到一个新的需求,针对<font style="color:rgb(60, 60, 67);">IUserDao</font> 接口开发出另一个具体实现类。因为 Server 层依赖了<font style="color:rgb(60, 60, 67);">IUserDao</font>的具体实现,所以我们需要修改<font style="color:rgb(60, 60, 67);">UserServiceImpl</font>中 new 的对象。如果只有一个类引用了<font style="color:rgb(60, 60, 67);">IUserDao</font>的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了<font style="color:rgb(60, 60, 67);">IUserDao</font>的具体实现的话,一旦需要更换<font style="color:rgb(60, 60, 67);">IUserDao</font> 的实现方式,那修改起来将会非常的头疼。

使用 IoC 的思想,我们将对象的控制权(创建、管理)交由 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 "要" 就可以了

IOC和DI有区别吗?
IoC(Inverse of Control:控制反转)是一种设计思想或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权交给第三方比如 IoC 容器。 对于我们常用的 Spring 框架来说, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。不过,IoC 在其他语言中也有应用,并非 Spring 特有。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
老马(Martin Fowler)在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:https://martinfowler.com/articles/injection.html 。

老马的大概意思是 IoC 太普遍并且不表意,很多人会因此而迷惑,所以,使用 DI 来精确指名这个模式比较好。
AOP
AOPAspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性。
AOP为什么叫面向切面编程?
AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面(Aspect)。

这里顺带总结一下 AOP 关键术语(不理解也没关系,可以继续往下看):
- 横切关注点(cross-cutting concerns) :多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等)。
- 切面(Aspect):对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能。
- 连接点(JoinPoint):连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出等)。
- 通知(Advice):通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。
- 切点(Pointcut) :一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如
<font style="color:rgb(60, 60, 67);">execution(* com.xyz.service..*(..))</font>匹配<font style="color:rgb(60, 60, 67);">com.xyz.service</font>包及其子包下的类或接口。 - 织入(Weaving):织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(Compile-Time Weaving 如:AspectJ)和运行期织入(Runtime Weaving 如:AspectJ、Spring AOP)。
AOP常见的通知类型有哪些?

- Before(前置通知):目标对象的方法调用之前触发
- After (后置通知):目标对象的方法调用之后触发
- AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
- AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
- Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
AOP解决了什么问题?
OOP 不能很好地处理一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等),这些行为通常被称为 横切关注点(cross-cutting concerns) 。如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。
AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从 核心业务逻辑(core concerns,核心关注点) 中分离出来,实现关注点的分离。

以日志记录为例进行介绍,假如我们需要对某些方法进行统一格式的日志记录,没有使用 AOP 技术之前,我们需要挨个写日志记录的逻辑代码,全是重复的的逻辑。
java
public CommonResponse<Object> method1() {
// 业务逻辑
xxService.method1();
// 省略具体的业务处理逻辑
// 日志记录
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 省略记录日志的具体逻辑 如:获取各种信息,写入数据库等操作...
return CommonResponse.success();
}
public CommonResponse<Object> method2() {
// 业务逻辑
xxService.method2();
// 省略具体的业务处理逻辑
// 日志记录
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 省略记录日志的具体逻辑 如:获取各种信息,写入数据库等操作...
return CommonResponse.success();
}
// ...
使用 AOP 技术之后,我们可以将日志记录的逻辑封装成一个切面,然后通过切入点和通知来指定在哪些方法需要执行日志记录的操作。
java
// 日志注解
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 描述
*/
String description() default "";
/**
* 方法类型 INSERT DELETE UPDATE OTHER
*/
MethodType methodType() default MethodType.OTHER;
}
// 日志切面
@Component
@Aspect
public class LogAspect {
// 切入点,所有被 Log 注解标注的方法
@Pointcut("@annotation(cn.javaguide.annotation.Log)")
public void webLog() {
}
/**
* 环绕通知
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 省略具体的处理逻辑
}
// 省略其他代码
}
这样的话,我们一行注解即可实现日志记录:
java
@Log(description = "method1",methodType = MethodType.INSERT)
public CommonResponse<Object> method1() {
// 业务逻辑
xxService.method1();
// 省略具体的业务处理逻辑
return CommonResponse.success();
}
AOP的应用场景有哪些?
- 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
- 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
- 事务管理:
@Transactional注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。 - 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用
@PreAuthorize注解一行代码即可自定义权限校验。 - 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
- 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。
多个切面的顺序如何控制?
1、通常使用<font style="color:rgb(60, 60, 67);">@Order</font> 注解直接定义切面顺序
java
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
2、实现**<font style="color:rgb(60, 60, 67);">Ordered</font>**接口重写**<font style="color:rgb(60, 60, 67);">getOrder</font>**方法**。**
java
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
AOP实现方式有哪些?
AOP 的常见实现方式有动态代理、字节码操作等方式。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 CGLIB 生成一个被代理对象的子类来作为代理,如下图所示:

1. JDK Proxy 模式(有接口)
这是 Spring AOP 的默认首选。只要你的类实现了接口,Spring 就用这个。
A. 你写的代码:
你需要有一个接口(剧本)和一个实现类(明星)。
Java
plain
// 1. 定义接口(剧本)
public interface UserService {
void login();
}
// 2. 实现接口(明星/被代理对象)
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("用户登录中...");
}
}
B. Spring 运行时偷偷生成的代码(JDK Proxy):
Spring 发现你有 UserService 接口,就会在内存里生成一个兄弟类。
Java
plain
// 这是一个内存中动态生成的类,大概长这样:
public class $Proxy_Generated implements UserService { // 注意:它也实现了接口
private UserServiceImpl target; // 持有真正的明星对象
@Override
public void login() {
System.out.println("【AOP 增强】开启事务/记录日志..."); // 前置增强
target.login(); // 让真正的明星去干活
System.out.println("【AOP 增强】提交事务..."); // 后置增强
}
}
核心点: 代理类和你的类是平级关系 (兄弟),因为它们都实现了同一个接口 UserService。
2. CGLIB 模式(无接口)
如果你偷懒(或者业务需要),没有写接口,直接写了一个类。
A. 你写的代码:
只有一个光秃秃的类。
Java
plain
// 只有类,没有 implements 任何接口
public class OrderService {
public void createOrder() {
System.out.println("创建订单中...");
}
}
B. Spring 运行时偷偷生成的代码(CGLIB):
Spring 发现没有接口,JDK Proxy 用不了,于是改用 CGLIB 生成一个子类。
Java
plain
// Spring 生成了一个子类继承你的类
public class OrderService$$EnhancerByCGLIB extends OrderService { // 注意:extends
@Override
public void createOrder() {
System.out.println("【AOP 增强】检查权限...");
super.createOrder(); // 调用父类(也就是你写的那个类)的方法
System.out.println("【AOP 增强】记录耗时...");
}
}
核心点: 代理类是你的类的子类 (父子关系)。这也是为什么被代理的类不能是 final 的(因为 final 类不能被继承)。
当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
面试回答
什么是IOC?
IOC(Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java开发领域对象的创建以及管理的问题。
例如:现有类A依赖于类B
- 传统开发方式:往往是在类A中new关键字来new一个B的对象出来。
- 使用IOC思想的开发方式:不通过new关键字来创建对象,而是通过IOC容器(Spring框架)来帮助我们实例化对象。我们需要哪个对象,直接从IOC容器里面去取即可。
从以上两种开发方式的对比来看:我们"丧失了一个权力"(创建、管理对象的权力),从而也得到一个好处(不用再考虑对象的创建、管理等一系列的事情)
为什么叫控制反转?
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(IOC容器)
IOC解决了什么问题?
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
- 对象之间的耦合度或者说依赖程度降低;
- 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发
在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在UserServiceImpl 中手动 new 出 IUserDao 的具体实现类 UserDaoImpl(不能直接 new 接口类)。
很完美,这种方式也是可以实现的,但是我们想象一下如下场景:
开发过程中突然接到一个新的需求,针对IUserDao 接口开发出另一个具体实现类。因为 Server 层依赖了IUserDao的具体实现,所以我们需要修改UserServiceImpl中 new 的对象。如果只有一个类引用了IUserDao的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了IUserDao的具体实现的话,一旦需要更换IUserDao 的实现方式,那修改起来将会非常的头疼。
什么是AOP?
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
和IOC一样,AOP也指的是一种思想。AOP思想是OOP的补充。OOP是面向类和对象的,但是AOP则是面向不同切面的。一个切面可以横跨多个类和对象去操作,极大丰富了开发者的使用方式,提高了开发效率。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy ,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

上面有详细演示代码
当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
AOP一些专业术语:
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的代理对象
- 连接点(JoinPoint):目标对象的所属类中,定义的所有方法均为连接点
- 切入点(Pointcut):被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
- 通知(Advice):增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
- 切面(Aspect):切入点(Pointcut)+通知(Advice)
- Weaving(织入):将通知应用到目标对象,进而生成代理对象的过程动作
Spring AOP和AspectJ AOP有什么区别?

如何选择?
- 功能考量 :AspectJ 支持更复杂的 AOP 场景,Spring AOP 更简单易用。如果你需要增强
final方法、静态方法、字段访问、构造器调用等,或者需要在非 Spring 管理的对象上应用增强逻辑,AspectJ 是唯一的选择。 - 性能考量:切面数量较少时两者性能差异不大,但切面较多时 AspectJ 性能更优。
一句话总结:简单场景优先使用 Spring AOP;复杂场景或高性能需求时,选择 AspectJ。