外观
IOC 和 AOP 的概念说一下
IOC(控制反转)
- 基本概念
控制反转(Inversion of Control,简称 IOC)是一种设计原则,它将对象的创建、依赖关系的管理和生命周期的控制从代码中转移到外部容器中。在传统的编程方式中,对象的创建和依赖关系的管理是由对象自身或调用者负责的,而在 IOC 模式下,这些控制权被反转给了一个专门的容器。这个容器会负责创建对象、管理对象之间的依赖关系,并在需要时将对象注入到使用它的地方。
- 核心思想
IOC 的核心思想是 “好莱坞原则”,即 “别找我们,我们会找你”。在传统编程中,对象主动去创建或查找它所依赖的对象;而在 IOC 模式下,对象只需要声明它所依赖的对象,容器会在合适的时候将这些依赖对象注入给它,对象不需要主动去获取这些依赖。
- 实现方式
依赖注入(Dependency Injection,简称 DI):这是 IOC 最常见的实现方式。依赖注入是指容器通过构造函数、Setter 方法或接口注入等方式将依赖对象注入到目标对象中。例如,在 Spring 框架中,可以使用注解(如 @Autowired)或 XML 配置来实现依赖注入。
// 定义一个服务接口
interface UserService {
void sayHello();
}
// 实现服务接口
class UserServiceImpl implements UserService {
@Override
public void sayHello() {
System.out.println("Hello!");
}
}
// 定义一个控制器类,依赖 UserService
class UserController {
private UserService userService;
// 通过构造函数进行依赖注入
public UserController(UserService userService) {
this.userService = userService;
}
public void performAction() {
userService.sayHello();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建服务对象
UserService userService = new UserServiceImpl();
// 创建控制器对象,并将服务对象注入
UserController userController = new UserController(userService);
// 调用控制器的方法
userController.performAction();
}
}
依赖查找(Dependency Lookup):这种方式是指对象通过容器提供的接口主动查找它所依赖的对象。与依赖注入不同,依赖查找需要对象主动去获取依赖,而不是由容器自动注入。
- 优点
降低耦合度:通过将对象的创建和依赖关系的管理交给容器,对象之间的耦合度大大降低,提高了代码的可维护性和可测试性。 提高可扩展性:当需要更换依赖对象时,只需要在容器中进行配置,而不需要修改使用该依赖的对象的代码。 便于单元测试:可以方便地为对象注入模拟对象,进行单元测试。
AOP(面向切面编程)基本概念 面向切面编程(Aspect-Oriented Programming,简称 AOP)是一种编程范式,它通过将横切关注点(如日志记录、事务管理、权限验证等)从业务逻辑中分离出来,以一种模块化的方式进行处理。这些横切关注点通常会跨越多个业务模块,传统的面向对象编程很难将它们很好地封装和管理,而 AOP 提供了一种有效的解决方案。
核心概念
- 切面(Aspect):切面是一个模块化的单元,它封装了横切关注点的实现。切面可以包含多个通知和切点。
- 通知(Advice):通知定义了在何时(如方法调用前、方法调用后、方法抛出异常时等)执行横切逻辑。常见的通知类型有前置通知(Before Advice)、后置通知(After Advice)、返回通知(After Returning Advice)、异常通知(After Throwing Advice)和环绕通知(Around Advice)。
- 切点(Pointcut):切点定义了哪些连接点(如方法调用、字段访问等)会触发通知的执行。切点可以使用表达式来匹配连接点。
- 连接点(Join Point):连接点是程序执行过程中的一个点,如方法调用、字段访问等。在 AOP 中,连接点通常指的是方法调用。
- 织入(Weaving):织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时或运行时进行。
实现方式
- 静态织入:在编译时或类加载时将切面代码织入到目标类中,生成新的类文件。例如,AspectJ 是一个支持静态织入的 AOP 框架。
- 动态代理:在运行时通过代理对象来实现 AOP。常见的动态代理方式有 JDK 动态代理和 CGLIB 动态代理。Spring AOP 就是基于动态代理实现的。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个接口
interface Calculator {
int add(int a, int b);
}
// 实现接口
class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
}
// 定义一个调用处理器,实现 AOP 功能
class CalculatorInvocationHandler implements InvocationHandler {
private Object target;
public CalculatorInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置通知
System.out.println("Before method call");
Object result = method.invoke(target, args);
// 后置通知
System.out.println("After method call");
return result;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建目标对象
Calculator calculator = new CalculatorImpl();
// 创建调用处理器
CalculatorInvocationHandler handler = new CalculatorInvocationHandler(calculator);
// 创建代理对象
Calculator proxy = (Calculator) Proxy.newProxyInstance(
Calculator.class.getClassLoader(),
new Class<?>[]{Calculator.class},
handler
);
// 调用代理对象的方法
int result = proxy.add(1, 2);
System.out.println("Result: " + result);
}
}
优点
- 提高代码的可维护性:将横切关注点从业务逻辑中分离出来,使得业务逻辑更加清晰,易于维护。
- 增强代码的复用性:切面可以在多个业务模块中复用,避免了代码的重复编写。
- 方便进行功能扩展:当需要添加新的横切关注点时,只需要创建新的切面并进行配置,而不需要修改原有的业务逻辑代码。
IOC 和 AOP 的关系
IOC 和 AOP 是两种不同的编程思想,但它们在很多框架(如 Spring 框架)中是相互配合使用的。IOC 主要解决对象的创建和依赖关系的管理问题,而 AOP 主要解决横切关注点的分离和处理问题。通过 IOC 容器可以方便地管理 AOP 中的切面和目标对象,将切面织入到目标对象中,实现 AOP 的功能。