# 第13天:完善AOP切面实现 ## 📚 今日目标 1. 完善DBRouterJoinPoint类 2. 处理类级别注解 3. 处理异常情况 4. 优化路由逻辑 --- ## 🛠️ 实践任务:完善DBRouterJoinPoint ### 完整实现代码 在 `src/main/java/cn/bugstack/middleware/db/router/` 目录下完善 `DBRouterJoinPoint.java`: ```java package cn.bugstack.middleware.db.router; import cn.bugstack.middleware.db.router.annotation.DBRouter; import cn.bugstack.middleware.db.router.annotation.DBRouterStrategy; import cn.bugstack.middleware.db.router.config.DBRouterConfig; import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy; import cn.bugstack.middleware.db.router.util.PropertyUtil; import cn.bugstack.middleware.db.router.util.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * 数据库路由切面 * * @author 小傅哥 */ @Aspect public class DBRouterJoinPoint { private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class); private DBRouterConfig dbRouterConfig; private IDBRouterStrategy dbRouterStrategy; public DBRouterJoinPoint(DBRouterConfig dbRouterConfig, IDBRouterStrategy dbRouterStrategy) { this.dbRouterConfig = dbRouterConfig; this.dbRouterStrategy = dbRouterStrategy; } @Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)") public void aopPoint() { } @Around("aopPoint() && @annotation(dbRouter)") public Object doRouter(ProceedingJoinPoint jp, DBRouter dbRouter) throws Throwable { String dbKey = dbRouter.key(); if (StringUtils.isBlank(dbKey)) { throw new RuntimeException("annotation DBRouter key is null!"); } // 获取路由键的值 String dbKeyAttr = getAttrValue(dbKey, jp.getArgs()); // 执行路由 dbRouterStrategy.doRouter(dbKeyAttr); // 执行原方法 try { return jp.proceed(); } finally { // 清理路由信息 dbRouterStrategy.clear(); } } /** * 获取路由键的值 */ public String getAttrValue(String attr, Object[] args) { if (1 == args.length) { Object arg = args[0]; if (arg instanceof String) { return arg.toString(); } return String.valueOf(PropertyUtil.getProperty(arg, attr)); } for (Object arg : args) { if (arg == null) { continue; } try { Object value = PropertyUtil.getProperty(arg, attr); if (null != value) { return String.valueOf(value); } } catch (Exception e) { // 忽略,继续查找 } } throw new RuntimeException("未找到路由键: " + attr); } /** * 获取方法对象 */ private Method getMethod(ProceedingJoinPoint jp) throws NoSuchMethodException { Signature sig = jp.getSignature(); MethodSignature methodSignature = (MethodSignature) sig; return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); } /** * 根据字段名获取字段值 */ private Object getValueByName(Object obj, String name) { try { Field field = getFieldByName(obj, name); if (null == field) { return null; } field.setAccessible(true); return field.get(obj); } catch (Exception e) { return null; } } /** * 根据字段名获取字段 */ private Field getFieldByName(Object obj, String name) { Field field = null; Class clazz = obj.getClass(); for (; clazz != Object.class; clazz = clazz.getSuperclass()) { try { field = clazz.getDeclaredField(name); } catch (Exception e) { // 忽略,继续查找 } } return field; } } ``` ### 代码解释 1. **路由键获取**: - 单个参数:直接获取 - 多个参数:遍历查找包含该属性的对象 2. **异常处理**: - 路由键为空:抛出异常 - 找不到路由键:抛出异常 - 使用finally确保清理 3. **辅助方法**: - `getMethod`:获取方法对象 - `getValueByName`:根据字段名获取值 - `getFieldByName`:根据字段名获取字段 --- ## 🎓 知识点拓展 ### 拓展1:处理类级别注解 **场景**:类上也有@DBRouterStrategy注解 ```java @DBRouterStrategy(splitTable = true) public class UserMapper { @DBRouter(key = "userId") public User selectById(Long userId) { // ... } } ``` **处理逻辑**: ```java // 先检查类上的注解 DBRouterStrategy classAnnotation = jp.getTarget().getClass().getAnnotation(DBRouterStrategy.class); if (classAnnotation != null && classAnnotation.splitTable()) { // 需要分表 } ``` ### 拓展2:路由键的优先级 **优先级**: 1. 方法上的@DBRouter注解 2. 类上的@DBRouterStrategy注解 3. 配置中的routerKey **实现**: ```java String dbKey = dbRouter.key(); if (StringUtils.isBlank(dbKey)) { // 检查类上的注解 DBRouterStrategy strategy = getClassAnnotation(jp); if (strategy != null) { dbKey = dbRouterConfig.getRouterKey(); } } ``` ### 拓展3:性能优化 **缓存Method对象**: ```java private static final Map METHOD_CACHE = new ConcurrentHashMap<>(); private DBRouter getDBRouterAnnotation(ProceedingJoinPoint jp) { Method method = getMethod(jp); return METHOD_CACHE.computeIfAbsent(method, m -> m.getAnnotation(DBRouter.class)); } ``` --- ## ✅ 今日检查清单 - [ ] 完善了DBRouterJoinPoint类 - [ ] 处理了类级别注解 - [ ] 处理了异常情况 - [ ] 优化了路由逻辑 - [ ] 完成了拓展阅读 --- ## 🎯 明日预告 明天我们将完善MyBatis插件: - 优化SQL替换逻辑 - 处理表别名 - 处理JOIN语句 --- ## 💡 思考题 1. 如何支持类级别注解? 2. 如何优化AOP切面的性能? 3. 如何处理路由键的优先级? --- ## 📚 参考资源 - [Spring AOP最佳实践](https://www.baeldung.com/spring-aop) - [AOP性能优化](https://www.baeldung.com/spring-aop-performance) - [切面设计模式](https://www.baeldung.com/aspect-oriented-programming)