AOP_青春版_VS_Pro版
背景:
在javaweb和ssm中学习了面向切面编程的两种方式(两种切点表达式不同),在苍穹外卖中,对于设置更新时间,创建时间,更新人,创建者为避免重复编码,将Update&Insert中的方法调用从业务逻辑中抽离出来,实现代码复用;
需求分析:
1.设置更新时间——在AOP通知中修改mapper方法中的参数
2.设置修改人——在AOP中获取线程令牌ID
“自写”码(青春版):
package com.sky.aspect; import java.time.LocalDateTime; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import com.sky.context.BaseContext; import com.sky.entity.Employee; import lombok.extern.slf4j.Slf4j; /** * insert Aop */ @Component @Aspect @Slf4j public class TimeAdvice { @Around("execution(* com.sky.mapper.*Mapper.insert*(..))") public Object SetTime(ProceedingJoinPoint joinPoint) throws Throwable{ log.info("*******start AOP*******",BaseContext.getCurrentId()); Object [] arg=joinPoint.getArgs(); if(arg.length>0&&arg[0]!=null&&arg[0] instanceof Employee){ Employee employee=(Employee) arg[0]; employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser(BaseContext.getCurrentId()); employee.setUpdateUser(BaseContext.getCurrentId()); } Object result=joinPoint.proceed(arg); return result; } }可优化点:
1.修改方法参数方式
2.解决变量属性硬编码
3.代码复用性通配性不高
优化版(Pro版):
package com.sky.enumeration; public enum OperationType { UPDATE, // 更新操作 INSERT // 插入操作 } package com.sky.annotation; import com.sky.enumeration.OperationType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) // 只能标注在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留,便于AOP拦截 public @interface AutoFill { OperationType value(); // 指定操作类型 } package com.sky.aspect; import com.sky.annotation.AutoFill; import com.sky.enumeration.OperationType; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Aspect @Component @Slf4j public class AutoFillAspect { /** * 定义切点:拦截所有带有@AutoFill注解的方法 */ @Pointcut("@annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut() {} /** * 前置通知:在方法执行前自动填充字段 */ @Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint) { log.info("开始进行公共字段自动填充..."); // 1. 获取方法签名和注解 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); OperationType operationType = autoFill.value(); // 2. 获取方法参数(实体对象) Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) { return; } Object entity = args[0]; // 3. 获取当前时间和当前登录用户ID LocalDateTime now = LocalDateTime.now(); Long currentId = BaseContext.getCurrentId(); // 假设有获取当前用户ID的工具类 // 4. 根据操作类型反射填充字段 try { if (operationType == OperationType.INSERT) { // 插入操作:填充createTime, updateTime, createUser, updateUser setFieldValue(entity, "createTime", now); setFieldValue(entity, "updateTime", now); setFieldValue(entity, "createUser", currentId); setFieldValue(entity, "updateUser", currentId); } else if (operationType == OperationType.UPDATE) { // 更新操作:只填充updateTime, updateUser setFieldValue(entity, "updateTime", now); setFieldValue(entity, "updateUser", currentId); } } catch (Exception e) { log.error("公共字段自动填充失败", e); } } /** * 反射设置字段值 */ private void setFieldValue(Object object, String fieldName, Object value) throws Exception { Class<?> clazz = object.getClass(); java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value); } }1.枚举(Enum)定义操作类型:
2.自定义注解 (Annotation) — 给代码"打标签
核心概念:
@interface= 我要定义一个新注解@Target= 这个注解能贴在哪儿(方法?类?字段?)@Retention= 这个注解保留到什么时候(源码?编译后?运行时?)value()= 使用注解时必须填的参数
如果用预制字符串呢?
定义标签值这块使用自定义Constant类,规定Final属性字符串“Insert”和“Update”,在自定义标签中填写呢?
通过对比发现一些“安全隐患”:在标签中填写值时,仍要使用Operation.Insert这样的操作,涉及Insert书写错误的风险,会出现编译报错;其次,定义自定义标签赋值类型为String,会导致随意赋值,eg:@AutoFill("Taylorswift"),仍然可以通过编译,但会导致aop失效;
3.获取方法签名和注解 — AOP的"透视眼"
@Before("autoFillPointCut()") public void autoFill(JoinPoint joinPoint) { // JoinPoint = 连接点,代表被拦截的方法 // ========== 第一步:获取方法签名 ========== // MethodSignature 是方法的"身份证",包含方法的所有信息 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // signature.getName() → 方法名 "insert" // signature.getParameterTypes() → 参数类型 // signature.getReturnType() → 返回值 // ========== 第二步:从方法上读取注解 ========== // 拿到方法上的 @AutoFill 标签 AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // 读取标签上的值(INSERT 还是 UPDATE) OperationType operationType = autoFill.value(); }4.Java 反射 — 运行时"黑进"对象
// 正常赋值(编译时确定):employee.setCreateTime(now); // 反射赋值(运行时动态):不管对象是什么类,强行给字段赋值 private void setFieldValue(Object object, String fieldName, Object value) throws Exception { // 1. 获取对象的类模板 Class<?> clazz = object.getClass(); // 比如 Employee.class // 2. 从类模板中找到指定字段(即使它是private的) Field field = clazz.getDeclaredField(fieldName); // 找 "createTime" 字段 // 3. 暴力破解访问权限(private也能访问) field.setAccessible(true); // 4. 给这个对象的该字段赋值 field.set(object, value); // 相当于 object.fieldName = value }优点:
1.使用自定义注解标注方法描述切入点,代码可读性高
2.通过joinpoint获取方法签名和注解与第一种方法相比代码利用率更高
3.通过条件语句判断是insert/update
4.java反射:降低耦合性,通配性更高,通过Field打通属性,修改属性值,参数修改无需新建或引用;
