Spring IOC 与 Spring AOP
一、 概述
IOC:Inversion of Control,控制反转,是一种对象创建与获取的设计思想。依赖注入(DI,Dependency Injection)是实现这种思想的常见方式。传统开发中对象通过 new 创建;采用 IoC 时,对象实例化与依赖关系由 IoC 容器负责,从而显著降低模块间耦合。
AOP:Aspect-Oriented Programming,面向切面编程,用于把那些与业务无关但被多个模块共同调用的逻辑抽离出来,以减少重复代码并降低耦合。Spring AOP 基于动态代理:当被代理对象实现了接口时优先使用 JDK Proxy;若无接口,则使用 CGLIB 为目标类生成子类代理。
在 Spring 中,IoC 和 AOP 常结合使用:IoC 管理对象与依赖,AOP 将横切关注点(例如事务、日志)切入到业务逻辑中,使代码更模块化、更易维护。例如通过 IoC 管理 Service/DAO 的依赖,并在 Service 层用 AOP 实现事务和日志。
二、Spring AOP
1. 核心概念
Aspect(切面):封装横切逻辑的模块,是 JoinPoint、Advice 和 Pointcut 的集合概念。
JoinPoint(连接点):程序执行中的一个点(在 Spring AOP 中通常是方法调用)。
Advice(通知):切面中要执行的逻辑,例如 before、after、around 等。通知常被实现为拦截器链,围绕 JoinPoint 执行。
Pointcut(切入点):用于选择哪些 JoinPoint 应该被 Advice 拦截。
Introduction(引入):允许在运行时把额外接口声明到目标对象上(例如动态为目标对象添加某个接口的实现)。
Weaving(织入):把切面逻辑插入到目标方法的过程。
AOP proxy(代理):在 Spring AOP 中由 JDK 动态代理或 CGLIB 生成的代理对象,负责在方法调用时触发 Advice。
Target object(目标对象):被代理的原始业务对象。
2.核心注解
@Aspect:用于定义切面类。
@Pointcut:声明切入点表达式。
@Before:在方法执行之前执行通知。
@After:在方法执行之后执行通知。
@Around:在方法执行前后都执行通知。
@AfterReturning:在方法执行后返回结果后执行通知。
@AfterThrowing:在方法抛出异常后执行通知。
3. 代码示例
目标接口与实现:
package com.example.service;
public interface UserService {void create(String name);
}package com.example.service;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl implements UserService {@Overridepublic void create(String name) {System.out.println("创建用户: " + name);}
}
切面示例:
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class SimpleAspect {@Pointcut("execution(* com.example.service.UserService.*(..))")public void userServiceMethods() {}@Before("userServiceMethods()")public void before(JoinPoint jp) {System.out.println("[Before] " + jp.getSignature());}@Around("userServiceMethods()")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("[Around] start");Object ret = pjp.proceed(); // 执行目标方法System.out.println("[Around] end");return ret;}
}
运行与输出:
package com.example;
import org.springframework.context.annotation.*;@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy
public class AppConfig { }package com.example;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.UserService;public class Main {public static void main(String[] args) {var ctx = new AnnotationConfigApplicationContext(AppConfig.class);UserService svc = ctx.getBean(UserService.class);System.out.println("bean class = " + svc.getClass().getName());svc.create("alice");ctx.close();}
}/* 输出
bean class = com.sun.proxy.$Proxy...
[Before] void com.example.service.UserService.create(String)
[Around] start
创建用户: alice
[Around] end
*/
4. 原理
Spring AOP 使用运行时代理将 Advice 织入目标对象:
a. 容器启动时扫描 @Aspect 并解析 Pointcut。
b. 为匹配的目标 Bean 创建代理(JDK Proxy 或 CGLIB)。
c. 客户端调用代理 -> 触发 Advice 链 -> 最终调用目标方法。
Spring AOP 不修改目标类字节码(除非使用 AspectJ 的编译期/类加载期织入),而是通过代理间接实现增强。
5.动态代理和静态代理的区别
代理是一种常见设计模式,用来为目标对象提供一个代替对象以控制对目标的访问,从而解耦客户端与目标对象。代理按实现方式可以分为静态代理和动态代理,两者的区别与特点如下:
a. 动态代理
在运行时动态生成代理类并实例化,不需要为每个目标手写代理类,适用于统一处理大量类的增强逻辑(如日志、事务、缓存等)。
Java 动态代理主要分为两种类型:
基于接口的代理(JDK 动态代理):这种类型的代理对象要求目标对象实现至少一个接口。通过 java.lang.reflect.Proxy 在运行时生成一个实现指定接口的代理类;所有方法调用会被转发到实现了 java.lang.reflect.InvocationHandler 的 handler 的 invoke 方法中处理。
基于类的代理(CGLIB 动态代理):CGLIB(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行时动态生成一个目标类的子类。CGLIB 代理通过重写方法来插入增强逻辑,最终返回生成的子类实例作为代理对象。
import java.lang.reflect.*;// InvocationHandler
class LogHandler implements InvocationHandler {private final Object target;public LogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[日志] 方法调用前");Object result = method.invoke(target, args);System.out.println("[日志] 方法调用后");return result;}
}// 使用动态代理
public class DynamicProxyDemo {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),new Class[]{UserService.class},new LogHandler(target));proxy.createUser("Bob");}
}
b. 静态代理
由程序员创建或者由特定的工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类。
// 静态代理类
class UserServiceProxy implements UserService {private final UserService target;public UserServiceProxy(UserService target) {this.target = target;}@Overridepublic void createUser(String name) {System.out.println("[日志] 方法调用前");target.createUser(name); // 调用目标对象方法System.out.println("[日志] 方法调用后");}
}// 使用静态代理
public class StaticProxyDemo {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = new UserServiceProxy(target);proxy.createUser("Alice");}
}
6. Spring AOP 和 AspectJ AOP的区别
Spring AOP:基于代理、运行时织入,轻量且易用,满足大部分企业场景。
AspectJ:功能更强,支持编译期/类加载期/运行期织入,可拦截方法之外的连接点(如字段访问、构造器等),在性能和能力上优于单纯的代理机制,但配置复杂度更高。
当切面非常多或需要更低开销时,可考虑使用 AspectJ。
三、Spring IOC
1. 核心概念
在传统的 Java SE 程序设计中,对象通常由程序通过 new 显式创建,类自身主动控制其依赖的构造与生命周期——也就是程序负责创建、组装和销毁依赖对象。
而在 Spring 的编程范式中,这一控制权被反转到 IoC 容器:容器负责对象的创建、依赖注入、初始化回调和销毁。具体来说:
创建对象:不再由业务类直接 new 实例,Spring 容器负责根据配置或注解实例化 Bean。
注入与初始化:对象之间的依赖由容器自动装配(构造器注入、Setter 注入或字段注入),容器还负责调用初始化回调(例如 @PostConstruct 或 InitializingBean)。
销毁与释放:容器在适当时机负责销毁 Bean 并触发销毁回调(例如 @PreDestroy 或 DisposableBean),释放资源,便于 JVM 回收内存。
控制权反转:由于容器掌握了创建与管理生命周期的职责,应用代码从“主动控制者”变为“被动使用者”,对象的管理不再由业务类直接操控,控制权交由 Spring 管理。
这一设计带来的好处包括降低模块耦合、便于替换实现、提高可测试性(可以注入 mock)、以及统一管理资源初始化与释放,从而提升系统的可维护性与健壮性。
2. 依赖注入
依赖注入是 IoC 的常见实现方式,主要注入方式包含:构造器注入、Setter 注入和字段注入(如 @Autowired)
DI 使得 Bean 之间通过容器进行耦合组装,便于替换实现、单元测试和代码解耦。
3. 设计一个Spring IOC,需要考虑哪些方面
Bean 的创建与销毁生命周期管理(初始化、销毁回调)。
依赖注入实现:属性注入、构造器注入、方法注入的解析与注入机制。
Bean 的作用域支持:singleton、prototype、request、session 等。
AOP 支持:如何在创建或获取 Bean 时生成代理并织入切面。
异常处理:Bean 创建或依赖注入失败时的回滚与错误信息。
配置加载:支持 XML、注解或 Java 配置方式。
工厂抽象支持:例如实现类似 FactoryBean 的能力以支持复杂对象创建逻辑。