问题引入
JavaEE开发的时候,新增字段,修改字段大都会涉及到创建时间(createTime),更改时间(updateTime),创建人(craeteUser),更改人(updateUser),如果每次都要自己去setter(),会比较麻烦,可以使用Spring的AOP,对于任意Insert,Update操作进行增强
解决步骤
主要依赖
如果使用了springboot,那就只需要导入这一个,这个框架对spring自带的aop框架进行了优化封装,更好用
事实上,还需要两个依赖,spring-context和spring-aop,不过springboot自带的有
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>
注释代码
给那些新增、修改功能中的设置时间、修改人的代码部分注释掉
自定义注解&使用
封装数据库操作类型
/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {// 数据库操作类型:UPDATE INSERTOperationType value();
}
给需要自动填充的方法加上注解
/*** 新增员工* @param employee*/@Insert("INSERT INTO employee" +"(name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user)" +"VALUES" +"(#{name}, #{username}, #{password}, #{phone}, #{sex}, #{idNumber}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Employee employee);/*** 修改员工信息* @param employee*/@AutoFill(value = OperationType.UPDATE)void update(Employee employee);/*** 根据ID查询员工* @param id* @return*/@Select("SELECT * FROM employee WHERE id = #{id}")Employee getById(Long id);/*** 密码修改* @param passwordEditDTO*/@AutoFill(value = OperationType.UPDATE)@Update("UPDATE employee SET `password` = #{newPassword} WHERE `id` = #{empId} AND `password` = #{oldPassword}")void updatePwd(PasswordEditDTO passwordEditDTO);
通知类
@Slf4j
@Component
@Aspect
public class AutoFillAspect {/*** 切面 && 指定切入点*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 切面 && 此方法(autoFill()) 在切入点之前执行* @param joinPoint*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint) {log.info("开始进行公共字段自动填充...");// 拿到当前被拦截的方法上的数据库操作类型MethodSignature signature = (MethodSignature)joinPoint.getSignature();// 拿到实现类的签名信息,不过我们只需要方法的签名信息,强转成MethodSignatureAutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);// 获得签名对象的注解对象OperationType operationType = autoFill.value(); // 拿到数据库操作类型// 拿到当前被拦截方法的方法参数(新增、更改方法的参数一般都是实体类)Object[] args = joinPoint.getArgs();if (args == null || args.length == 0){ // 防止null,防止空参return;}Object entity = args[0]; // 这一行就要求,以后的新增、更改方法参数,实体类必须写在首形参位置// 需填充的数据LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();// 根据当前不同的操作类型,使用反射为对应的属性赋值if (operationType == OperationType.INSERT) {// 为4个公共字段赋值try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);// 通过反射给对象属性赋值setCreateTime.invoke(entity, now);setUpdateTime.invoke(entity, now);setCreateUser.invoke(entity, currentId);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}} else if (operationType == OperationType.UPDATE) {// 为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);// 通过反射给对象属性赋值setUpdateTime.invoke(entity, now);setUpdateUser.invoke(entity, currentId);} catch (Exception e) {e.printStackTrace();}}}
}
依赖相关类
公共字段自动填充相关常量
/*** 公共字段自动填充相关常量*/
public class AutoFillConstant {/*** 实体类中的方法名称*/public static final String SET_CREATE_TIME = "setCreateTime";public static final String SET_UPDATE_TIME = "setUpdateTime";public static final String SET_CREATE_USER = "setCreateUser";public static final String SET_UPDATE_USER = "setUpdateUser";
}
线程空间上下文
public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}
启动类加上注解@EnableAspectJAutoProxy
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@EnableAspectJAutoProxy // 开启注解方式的AOP
@Slf4j
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}
在这段代码中,
joinPoint
是 Spring AOP 中的一个概念,表示连接点,即在程序执行过程中切入的某个点。Signature
则是 Signature 接口的实现类之一,用于表示连接点的签名信息,包括方法名、参数列表等。具体来说,这段代码的含义是从
joinPoint
中获取连接点的签名信息,并将其强制转换为MethodSignature
类型。然后可以通过signature
对象获取连接点(被代理方法)的详细信息,比如方法名、参数类型等。例如,可以通过
signature.getMethod().getName()
获取连接点方法的名称,通过signature.getParameterTypes()
获取连接点方法的参数类型数组等。需要注意的是,在实际应用中,我们使用这段代码时需要确保
joinPoint
真正代表了一个方法连接点,否则在强制转换为MethodSignature
时可能会抛出类型转换异常。