通过AOP切面实现公共字段的自动填充

本文介绍了如何通过创建枚举、自定义注解以及AOP技术,实现在Mapper层的方法中自动填充公共字段值,如创建时间、更新时间、创建用户和更新用户,支持插入和更新操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以create_time、update_time、create_user、update_user字段为例

实现步骤:

第一步:创建一个枚举

public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT
}

第二步:自定义一个注解

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

第三步:在需要填充的Mapper层的方法上加上相应的注解,eg:

第四步:通过aop使得添加的注解生效

@Aspect
@Component
public class AutofillAspect {

    @Before("execution(* com.sky.mapper.*.*(..)) && @annotation(autoFill)")
    public void autoFill(JoinPoint joinPoint, AutoFill autoFill){
        log.info("开始自动填充公共字段值");

        //获取Mapper方法的参数:entity对象
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        Object entity = args[0];

        //准备要赋的值
        LocalDateTime now = LocalDateTime.now();
        Long currentUser = BaseContext.getCurrentId();

        //判断操作的类型
        Class<?> clazz = entity.getClass();
        if (autoFill.value() == OperationType.INSERT) {
            try {
                //是新增操作:获取setCreateTime、setUpdateTime、setCreateUser、setUpdateUser四个方法
                Method setCreateTimeMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTimeMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreatorMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdatorMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //使用反射调用entity的对应方法,给属性赋值
                setCreateTimeMethod.invoke(entity, now);
                setUpdateTimeMethod.invoke(entity, now);
                setCreatorMethod.invoke(entity, currentUser);
                setUpdatorMethod.invoke(entity, currentUser);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            try {
                //是修改操作:获取setUpdateTime、setUpdateUser两个方法
                Method setUpdateTimeMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdatorMethod = clazz.getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                //使用反射调用entity的对应方法,给属性赋值
                setUpdateTimeMethod.invoke(entity, now);
                setUpdatorMethod.invoke(entity, currentUser);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

最后就能够正常使用了

<think>嗯,用户想了解使用AOP实现自动填充公共字段的最佳实践和示例代码。首先,我需要回忆一下AOP的基本概念和它在实际中的应用场景。AOP,也就是面向切面编程,通常用于处理横切关注点,比如日志记录、事务管理、安全控制等。公共字段填充,比如创建时间、更新时间、操作人等,这些字段在多个表和方法中都需要处理,非常适合用AOP来统一处理,避免重复代码。 用户提供的引用内容中提到了使用@Before注解在Mapper层的方法执行前进行填充,还提到了自定义注解@AutoFill来标记需要处理的方法。这可能涉及到定义切点,拦截带有特定注解的方法,然后在切面中处理这些方法,自动填充公共字段。接下来需要整理出具体的步骤,比如如何定义注解、如何编写切面类、如何获取方法参数并填充字段等。 首先,定义操作类型的枚举,比如INSERT和UPDATE,这样在注解中可以指定操作类型,决定填充哪些字段。然后定义自定义注解@AutoFill,接收操作类型作为参数,这样可以灵活地应用到不同的方法上。接着,在Mapper接口的方法上添加这个注解,指定对应的操作类型,这样切面就能识别需要处理的方法。 然后是切面类的编写,需要用到AspectJ的注解,比如@Aspect和@Component。切点表达式应该匹配Mapper层的方法,并且带有@AutoFill注解。在通知方法中,通过反射获取方法参数,这里可能需要处理参数是实体类的情况,无论是单个参数还是多个参数中的实体类。然后根据操作类型设置对应的公共字段,比如创建时间、更新时间、操作人ID等。 需要注意的是,处理参数时,可能需要遍历参数列表,找到实体类对象。如果参数是多个,可能需要判断每个参数是否是实体类的实例。然后使用反射来设置字段的值,这里需要处理字段是否存在,以及访问权限的问题,比如通过setAccessible(true)来修改私有字段。 另外,用户可能关心如何动态获取当前操作人的ID,这可能涉及到使用ThreadLocal或者SecurityContext来保存当前用户的信息,在切面中获取并填充到实体类中。这部分可能需要额外的工具类或者服务类来支持。 示例代码需要展示完整的注解定义、切面实现,以及Mapper层的使用示例。同时,要提到事务管理和性能优化的注意事项,比如确保反射操作不会影响性能,以及避免在切面中处理过多逻辑导致事务问题。 可能还需要考虑异常处理,比如实体类中没有对应的字段时如何处理,是否抛出异常还是忽略。此外,单元测试和集成测试也是最佳实践的一部分,确保切面按预期工作,不会影响原有业务逻辑。 总结一下,最佳实践包括:定义清晰的注解和操作类型,编写可复用的切面类,处理不同参数类型的实体类,动态获取用户信息,注意事务和性能,以及进行充分的测试。示例代码需要覆盖这些方面,并且结构清晰,易于理解和扩展。</think>以下是通过AOP实现公共字段自动填充的最佳实践与示例代码,结合您提供的引用内容进行技术实现说明: --- ### 一、最佳实践核心步骤 1. **定义操作类型枚举** 标识数据库操作类型(INSERT/UPDATE),用于动态判断字段填充逻辑 ```java public enum OperationType { INSERT, UPDATE } ``` 2. **自定义注解标记方法** 通过注解声明需要自动填充的方法[^3] ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { OperationType value(); } ``` 3. **统一字段基类设计** 建议实体类继承包含公共字段的基类 ```java public class BaseEntity { private LocalDateTime createTime; private Long createUser; private LocalDateTime updateTime; private Long updateUser; } ``` 4. **切面编程实现填充逻辑** 通过AOP拦截Mapper层方法[^2] ```java @Aspect @Component @Slf4j public class AutoFillAspect { @Before("execution(* com.example.mapper.*.*(..)) && @annotation(autoFill)") public void autoFillFields(JoinPoint joinPoint, AutoFill autoFill) { Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) return; Object entity = args[0]; // 获取实体参数 if (entity instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) entity; LocalDateTime now = LocalDateTime.now(); Long currentUserId = getCurrentUserId(); // 实现用户信息获取 if (autoFill.value() == OperationType.INSERT) { baseEntity.setCreateTime(now); baseEntity.setCreateUser(currentUserId); } if (autoFill.value() == OperationType.UPDATE || autoFill.value() == OperationType.INSERT) { baseEntity.setUpdateTime(now); baseEntity.setUpdateUser(currentUserId); } } } } ``` --- ### 二、完整实现示例 #### Mapper层应用 ```java public interface UserMapper { @AutoFill(OperationType.INSERT) void insert(User user); @AutoFill(OperationType.UPDATE) void update(User user); } ``` #### 增强型切面(支持多参数) ```java // 在原有切面基础上增加参数扫描 private BaseEntity findEntityParam(Object[] args) { for (Object arg : args) { if (arg instanceof BaseEntity) { return (BaseEntity) arg; } else if (arg instanceof Map) { // 处理MyBatis的@Param注解参数 for (Object value : ((Map<?,?>)arg).values()) { if (value instanceof BaseEntity) { return (BaseEntity) value; } } } } return null; } ``` --- ### 三、关键优化建议 1. **动态用户信息获取** 推荐使用`ThreadLocal`存储当前用户上下文 ```java public class UserContext { private static final ThreadLocal<Long> currentUser = new ThreadLocal<>(); public static void setCurrentUser(Long userId) { currentUser.set(userId); } public static Long getCurrentUser() { return currentUser.get(); } } ``` 2. **事务边界处理** 建议在Service层开启事务前设置用户信息,避免切面获取不到上下文 3. **性能优化** 对反射操作进行缓存优化: ```java private static final ConcurrentHashMap<Class<?>, List<Field>> fieldCache = new ConcurrentHashMap<>(); ``` --- ### 四、验证测试方案 1. **单元测试用例** ```java @Test void testInsertAutoFill() { User user = new User(); userMapper.insert(user); assertNotNull(user.getCreateTime()); assertEquals(1001L, user.getCreateUser()); } ``` 2. **集成测试检查项** - 多参数方法填充验证 - 嵌套事务中的上下文保持 - 并发场景下的线程安全 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值