导航
📖 本讲目标
- 实现 @Aspect 切面类
- 实现 @Before 和 @After 通知
- 实现切点表达式匹配
- 创建代理对象
- 集成 AOP 到 ApplicationContext
🏷️ AOP 注解实现
@Aspect 注解
创建 src/main/java/com/minispring/aop/Aspect.java:
package com.minispring.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Aspect 注解用于标记一个类为切面类
* 切面类中包含通知方法(@Before、@After)
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/**
* 切面类的名称
*/
String value() default "";
}
@Before 注解
创建 src/main/java/com/minispring/aop/Before.java:
package com.minispring.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Before 注解用于标记前置通知方法
* 在目标方法执行之前执行
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Before {
/**
* 切点表达式,用于匹配目标方法
* 格式:类名.方法名 或 类名.*(匹配该类的所有方法)
* 例如:com.example.UserService.saveUser
* com.example.UserService.*
*/
String value();
}
@After 注解
创建 src/main/java/com/minispring/aop/After.java:
package com.minispring.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @After 注解用于标记后置通知方法
* 在目标方法执行之后执行(无论是否抛出异常)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface After {
/**
* 切点表达式,用于匹配目标方法
* 格式:类名.方法名 或 类名.*(匹配该类的所有方法)
* 例如:com.example.UserService.saveUser
* com.example.UserService.*
*/
String value();
}
🔧 AOP 代理实现
AopProxy 类设计
创建 src/main/java/com/minispring/aop/AopProxy.java:
package com.minispring.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* AOP 代理工厂,负责创建代理对象
*/
public class AopProxy {
/**
* 切点映射:类名 -> 通知列表
*/
private final Map<String, List<Advice>> pointcutMap = new ConcurrentHashMap<>();
/**
* 注册切面
* @param aspectBean 切面 Bean 实例
* @param aspectClass 切面类
*/
public void registerAspect(Object aspectBean, Class<?> aspectClass) {
Method[] methods = aspectClass.getDeclaredMethods();
for (Method method : methods) {
// 处理 @Before 注解
Before before = method.getAnnotation(Before.class);
if (before != null) {
String pointcut = before.value();
registerAdvice(pointcut, aspectBean, method, AdviceType.BEFORE);
}
// 处理 @After 注解
After after = method.getAnnotation(After.class);
if (after != null) {
String pointcut = after.value();
registerAdvice(pointcut, aspectBean, method, AdviceType.AFTER);
}
}
}
/**
* 注册通知
*/
private void registerAdvice(String pointcut, Object aspectBean, Method adviceMethod, AdviceType type) {
// 解析切点表达式
// 支持格式:com.example.UserService.saveUser 或 com.example.UserService.*
String[] parts = pointcut.split("\.");
if (parts.length < 2) {
throw new IllegalArgumentException("无效的切点表达式: " + pointcut);
}
// 构建类名和方法名模式
String className = String.join(".", java.util.Arrays.copyOf(parts, parts.length - 1));
String methodPattern = parts[parts.length - 1];
String key = className;
pointcutMap.computeIfAbsent(key, k -> new ArrayList<>())
.add(new Advice(className, methodPattern, aspectBean, adviceMethod, type));
}
/**
* 创建代理对象
* @param target 目标对象
* @return 代理对象
*/
public Object createProxy(Object target) {
Class<?> targetClass = target.getClass();
String className = targetClass.getName();
// 检查是否有匹配的切点
List<Advice> advices = pointcutMap.get(className);
if (advices == null || advices.isEmpty()) {
return target; // 没有切点,直接返回原对象
}
// 使用 JDK 动态代理
ClassLoader classLoader = targetClass.getClassLoader();
Class<?>[] interfaces = targetClass.getInterfaces();
if (interfaces.length == 0) {
// 如果没有接口,无法使用 JDK 动态代理,返回原对象
System.out.println(" ⚠ 类 " + className + " 没有实现接口,无法创建代理");
return target;
}
return Proxy.newProxyInstance(classLoader, interfaces,
new AopInvocationHandler(target, advices));
}
/**
* 通知类型
*/
enum AdviceType {
BEFORE, AFTER
}
/**
* 通知信息
*/
static class Advice {
final String className;
final String methodPattern;
final Object aspectBean;
final Method adviceMethod;
final AdviceType type;
Advice(String className, String methodPattern, Object aspectBean,
Method adviceMethod, AdviceType type) {
this.className = className;
this.methodPattern = methodPattern;
this.aspectBean = aspectBean;
this.adviceMethod = adviceMethod;
this.type = type;
}
/**
* 检查方法是否匹配切点
*/
boolean matches(String methodName) {
if ("*".equals(methodPattern)) {
return true;
}
return methodPattern.equals(methodName);
}
}
/**
* AOP 调用处理器
*/
static class AopInvocationHandler implements InvocationHandler {
private final Object target;
private final List<Advice> advices;
AopInvocationHandler(Object target, List<Advice> advices) {
this.target = target;
this.advices = advices;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 执行前置通知
for (Advice advice : advices) {
if (advice.type == AdviceType.BEFORE && advice.matches(methodName)) {
advice.adviceMethod.setAccessible(true);
advice.adviceMethod.invoke(advice.aspectBean);
}
}
// 执行目标方法
Object result = null;
try {
result = method.invoke(target, args);
} finally {
// 执行后置通知(无论是否抛出异常)
for (Advice advice : advices) {
if (advice.type == AdviceType.AFTER && advice.matches(methodName)) {
advice.adviceMethod.setAccessible(true);
advice.adviceMethod.invoke(advice.aspectBean);
}
}
}
return result;
}
}
}
🔗 集成到 ApplicationContext
添加 AopProxy
在 ApplicationContext 中添加 AOP 支持:
/** * AOP 代理工厂 */ private final AopProxy aopProxy = new AopProxy();
注册切面
在创建 Bean 时注册切面:
private Object createBean(String beanName, BeanDefinition bd) throws Exception {
// ... 前面的代码 ...
// 6. 注册切面(如果是切面类)
if (bd.getBeanClass().isAnnotationPresent(Aspect.class)) {
aopProxy.registerAspect(bean, bd.getBeanClass());
System.out.println(" ✓ 注册切面: " + beanName);
// 切面 Bean 本身不需要代理
return bean;
}
// 7. 应用 AOP 代理(非切面 Bean)
Object proxy = aopProxy.createProxy(bean);
if (proxy != bean) {
System.out.println(" ✓ 创建 AOP 代理: " + beanName);
}
return proxy;
}
优先创建切面 Bean
确保切面 Bean 在其他 Bean 之前创建:
private void preInstantiateSingletons() {
System.out.println("n开始预实例化单例 Bean...");
// 第一阶段:优先创建切面 Bean
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.isSingleton() && !beanDefinition.isLazy() &&
beanDefinition.getBeanClass().isAnnotationPresent(Aspect.class)) {
try {
getBean(beanName);
} catch (Exception e) {
throw new RuntimeException("预实例化切面 Bean 失败: " + beanName, e);
}
}
}
// 第二阶段:创建其他 Bean(此时切面已注册,可以应用代理)
for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = entry.getValue();
if (beanDefinition.isSingleton() && !beanDefinition.isLazy() &&
!beanDefinition.getBeanClass().isAnnotationPresent(Aspect.class)) {
try {
getBean(beanName);
} catch (Exception e) {
throw new RuntimeException("预实例化 Bean 失败: " + beanName, e);
}
}
}
System.out.println("单例 Bean 预实例化完成n");
}
🧪 测试示例
测试类
package com.minispring.demo;
import com.minispring.aop.After;
import com.minispring.aop.Aspect;
import com.minispring.aop.Before;
import com.minispring.stereotype.Component;
import com.minispring.context.ApplicationContext;
// 接口
interface IUserService {
void saveUser(String name);
}
// 实现类
@Component
class UserService implements IUserService {
public void saveUser(String name) {
System.out.println("保存用户: " + name);
}
}
// 切面类
@Component
@Aspect
class LoggingAspect {
@Before("com.minispring.demo.UserService.*")
public void beforeMethod() {
System.out.println(" [AOP-Before] 准备执行 UserService 方法...");
}
@After("com.minispring.demo.UserService.*")
public void afterMethod() {
System.out.println(" [AOP-After] UserService 方法执行完成");
}
}
public class AopTest {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ApplicationContext("com.minispring.demo");
// 获取 Bean(返回代理对象)
IUserService service = context.getBean(IUserService.class);
// 调用方法(会触发 AOP)
service.saveUser("张三");
}
}
运行结果
注册 Bean: userService [scope=singleton, lazy=false] 注册 Bean: loggingAspect [scope=singleton, lazy=false] 开始预实例化单例 Bean... 创建 Bean: loggingAspect (LoggingAspect) ✓ 注册切面: loggingAspect 创建 Bean: userService (UserService) ✓ 创建 AOP 代理: userService 调用方法: [AOP-Before] 准备执行 UserService 方法... 保存用户: 张三 [AOP-After] UserService 方法执行完成
📝 本讲总结
本讲我们完成了:
- ✅ 实现了 @Aspect、@Before、@After 注解
- ✅ 实现了 AopProxy 代理工厂
- ✅ 实现了切点表达式匹配
- ✅ 实现了 JDK 动态代理
- ✅ 集成了 AOP 到 ApplicationContext
- ✅ 实现了切面 Bean 的优先创建机制
🎯 关键点回顾
- 切面注册:扫描切面类的方法,注册通知
- 切点匹配:通过类名和方法名匹配切点
- 代理创建:使用 JDK 动态代理创建代理对象
- 通知执行:在方法调用前后执行通知
- 创建顺序:切面 Bean 需要优先创建
⚠️ 注意事项
- 接口要求:JDK 动态代理需要目标类实现接口
- 切点表达式:当前实现支持简单的类名.方法名格式
- 切面顺序:切面 Bean 必须在其他 Bean 之前创建
- 性能考虑:代理对象的方法调用有性能开销
🚀 下一讲预告
在下一讲中,我们将进行 项目总结与扩展,包括:
- 功能总结
- 性能优化建议
- 扩展方向
- 与 Spring 框架对比
准备好了吗?让我们继续 第 11 讲:项目总结与扩展!
思考题:
- 如何实现更复杂的切点表达式?
- 如何支持环绕通知(@Around)?
- 如何优化 AOP 的性能?
