✅ 手撸 Spring 简易版 AOP
一、核心目标
在已有 IOC 容器基础上,新增 AOP 能力,包含:
- 自定义注解
@MyAspect、@MyBefore、@MyAfter; - 切面类识别与注册;
- 使用 JDK 动态代理对目标 Bean 进行代理;
- 支持方法执行前/后通知(Before / After);
- 与 IOC 容器无缝集成(依赖注入 + AOP 代理)。
💡 注意:为简化,仅支持 接口代理(JDK Proxy) ,不支持 CGLIB(无接口类)。
二、完整实现代码
步骤 1:定义 AOP 注解
java
import java.lang.annotation.*;
// 标记切面类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
// 前置通知
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBefore {
String value(); // 切点表达式,如 "com.example.service.UserServiceImpl.getUser"
}
// 后置通知
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAfter {
String value();
}
📌 切点表达式简化为 全限定方法名 (如
org.example.service.UserServiceImpl.getUser),不使用 AspectJ 表达式。
步骤 2:扩展 MyApplicationContext,支持 AOP
在原有 IOC 容器中增加 AOP 处理逻辑。
scss
import java.lang.reflect.*;
import java.util.*;
// 新增导入
import java.util.concurrent.ConcurrentHashMap;
public class MyApplicationContext {
private Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<>();
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 改为线程安全
private Class<?> configClass;
// 新增:存储切面信息 { 切点方法全名 -> 切面对象 }
private Map<String, Object> aspectBeans = new HashMap<>();
// 存储 Before 方法
private Map<String, Method> beforeAdviceMethods = new HashMap<>();
// 存储 After 方法
private Map<String, Method> afterAdviceMethods = new HashMap<>();
public MyApplicationContext(Class<?> configClass) {
this.configClass = configClass;
scanAndRegisterBeanDefinitions();
registerAspects(); // 新增:注册切面
instantiateSingletons();
}
// ====== 原有方法保持不变(scanAndRegisterBeanDefinitions, recursiveScan 等)======
// 新增:扫描并注册所有 @MyAspect 切面
private void registerAspects() {
for (Map.Entry<String, MyBeanDefinition> entry : beanDefinitionMap.entrySet()) {
Class<?> clazz = entry.getValue().getBeanClass();
if (clazz.isAnnotationPresent(MyAspect.class)) {
String beanName = entry.getKey();
Object aspectBean = createBean(beanName, entry.getValue()); // 先实例化切面(无依赖注入)
aspectBeans.put(beanName, aspectBean);
// 解析切面中的 @MyBefore / @MyAfter
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyBefore.class)) {
MyBefore before = method.getAnnotation(MyBefore.class);
String pointcut = before.value();
beforeAdviceMethods.put(pointcut, method);
}
if (method.isAnnotationPresent(MyAfter.class)) {
MyAfter after = method.getAnnotation(MyAfter.class);
String pointcut = after.value();
afterAdviceMethods.put(pointcut, method);
}
}
}
}
}
// 重写 createBean:如果目标 Bean 有切面,则返回代理对象
private Object createBean(String beanName, MyBeanDefinition beanDefinition) {
Class<?> beanClass = beanDefinition.getBeanClass();
try {
Object beanInstance = beanClass.getDeclaredConstructor().newInstance();
populateBean(beanInstance); // 依赖注入
// 检查是否需要 AOP 代理
if (needsProxy(beanClass)) {
return createProxy(beanInstance, beanClass);
}
return beanInstance;
} catch (Exception e) {
throw new RuntimeException("创建 Bean 失败:" + beanName, e);
}
}
// 判断是否需要代理:只要存在匹配的切点就代理
private boolean needsProxy(Class<?> targetClass) {
for (String pointcut : beforeAdviceMethods.keySet()) {
if (pointcut.startsWith(targetClass.getName())) {
return true;
}
}
for (String pointcut : afterAdviceMethods.keySet()) {
if (pointcut.startsWith(targetClass.getName())) {
return true;
}
}
return false;
}
// 创建 JDK 动态代理
private Object createProxy(Object target, Class<?> targetClass) {
return Proxy.newProxyInstance(
targetClass.getClassLoader(),
targetClass.getInterfaces(), // 必须有接口!
new MyInvocationHandler(target, targetClass)
);
}
// 自定义 InvocationHandler
private class MyInvocationHandler implements InvocationHandler {
private Object target;
private Class<?> targetClass;
public MyInvocationHandler(Object target, Class<?> targetClass) {
this.target = target;
this.targetClass = targetClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String fullMethodName = targetClass.getName() + "." + method.getName();
// 执行 @MyBefore
if (beforeAdviceMethods.containsKey(fullMethodName)) {
Method beforeMethod = beforeAdviceMethods.get(fullMethodName);
String aspectBeanName = findAspectBeanForMethod(beforeMethod);
Object aspect = aspectBeans.get(aspectBeanName);
beforeMethod.setAccessible(true);
beforeMethod.invoke(aspect);
}
// 执行目标方法
Object result = method.invoke(target, args);
// 执行 @MyAfter
if (afterAdviceMethods.containsKey(fullMethodName)) {
Method afterMethod = afterAdviceMethods.get(fullMethodName);
String aspectBeanName = findAspectBeanForMethod(afterMethod);
Object aspect = aspectBeans.get(aspectBeanName);
afterMethod.setAccessible(true);
afterMethod.invoke(aspect);
}
return result;
}
// 辅助:根据通知方法反推切面 Bean 名称
private String findAspectBeanForMethod(Method adviceMethod) {
Class<?> aspectClass = adviceMethod.getDeclaringClass();
for (Map.Entry<String, Object> entry : aspectBeans.entrySet()) {
if (entry.getValue().getClass() == aspectClass) {
return entry.getKey();
}
}
throw new RuntimeException("未找到切面对应的 Bean:" + aspectClass.getName());
}
}
// ====== 原有方法:populateBean, getBean, 工具方法等保持不变 ======
// (此处省略,与你提供的代码一致)
}
⚠️ 注意:目标类必须实现接口 ,否则
Proxy.newProxyInstance会失败。
步骤 3:编写测试用例
1. 定义接口和实现类
java
public interface UserService {
void getUser();
}
@MyComponent("userService")
public class UserServiceImpl implements UserService {
@MyAutowired
private UserDao userDao;
@Override
public void getUser() {
userDao.queryUser();
System.out.println("业务逻辑:获取用户");
}
}
2. 编写切面类
kotlin
@MyAspect
@MyComponent("logAspect")
public class LogAspect {
@MyBefore("com.example.spring6.aop.demo.UserServiceImpl.getUser")
public void beforeGetUser() {
System.out.println("【AOP 前置通知】准备调用 getUser 方法");
}
@MyAfter("com.example.spring6.aop.demo.UserServiceImpl.getUser")
public void afterGetUser() {
System.out.println("【AOP 后置通知】getUser 方法执行完毕");
}
}
3. 配置类(同 IOC)
kotlin
@MyConfiguration(scanPackage = "org.example.spring6.aop")
public class AppConfig {
}
4. 测试主类
arduino
public class MyAopTest {
public static void main(String[] args) {
MyApplicationContext context = new MyApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.getUser();
}
}
运行结果
【AOP 前置通知】准备调用 getUser 方法
Spring 6.x 简易 IOC:查询用户信息
业务逻辑:获取用户
【AOP 后置通知】getUser 方法执行完毕
✅ 成功实现 AOP 通知!
三、简易 AOP vs Spring 6.x 对比
| 简易 AOP 组件 | Spring 6.x 原生组件 | 说明 |
|---|---|---|
@MyAspect / @MyBefore |
@Aspect / @Before |
切面与通知注解 |
MyInvocationHandler |
JdkDynamicAopProxy |
JDK 动态代理处理器 |
aspectBeans + adviceMethods |
AdvisorRegistry + PointcutAdvisor |
切面注册与匹配 |
| 手动解析切点 | PointcutExpression + AspectJExpressionPointcut |
Spring 使用 AspectJ 表达式 |
📌 关注我 ,每天5分钟,带你从 Java 小白变身编程高手!
👉 点赞 + 关注+私信 "AOP源码"获取手撸源码,让更多小伙伴一起进步!