问题引入
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 INSERT
OperationType 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();// 拿到实现类的签名信息,不过我们只需要方法的签名信息,强转成MethodSignature
AutoFill 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
时可能会抛出类型转换异常。