苍穹外卖学习——day04学习日记

今日完结任务:

完成了编辑员工信息的接口开发以及所有分类管理的接口开发。

今日收获:

1.对象的属性拷贝

 //对象的属性拷贝
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO,employee);

        employeeMapper.update(employee);

是Apache Commons BeanUtils提供的一个工具方法,用于在两个java对象直接复制属性值。这种操作一般讲DTO(数据传输对象)中的数据映射到实体类(Entity)或者其他目标对象

其底层利用了反射机制:首先遍历原对象的所有getter方法,然后查找目标对象的所有setter方法,最后将源对象的值通过setter方法赋值给目标对象。

2.实现了公共字段的自动填充

在我们开发项目时,会牵扯到大量的数据表,而这些数据表会有很多重复的字段,并且当我们对这些重复的字段进行操作时,大量的重复操作会造成代码冗余。例如在苍穹外卖项目中,在对数据进行Update和Insert时,都会对修改人/时间和创建人/时间这四个字段进行更新,这就会使项目中出现了大量相似的代码。

所以我们通过AOP思想(面向切面编程)来优化这种冗余。

AOP思想:它可以将那些被业务模块所共同调用的逻辑封装起来,便于减少系统之间重复的代码。

  • 首先我们先对这四个字段设置一个自定义注解,来表示要进行公共字段字段填充的方法。
@Target(ElementType.METHOD) //表示该注解只能在方法上使用
@Retention(RetentionPolicy.RUNTIME) //表示该注解的生命周期,在启动时使用
public @interface AutoFill {
    //申明数据库操作类型:update ,insert
    OperationType value();
}

OperatType是一个枚举类型的声明,用来枚举数据库的两种操作类型。

/**
 * 数据库操作类型
 */
public enum OperationType {

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

    /**
     * 插入操作
     */
    INSERT

}
  • 自定义切面类,统一拦截加入AutoFill注解的方法,并通过反射为公共字段赋值。
package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
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.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component //也要交个Spring容器管理
@Slf4j
public class AutoFillAspect {
    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知:在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        log.info("开始进行公共字段自动填充");
        //获取到当前被拦截(切入点) 的数据库操作类型
        MethodSignature signature = (MethodSignature)joinPoint.getSignature(); //获得方法签名对象(更详细)
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
        OperationType operationType = autoFill.value(); //获取数据库的操作类型
        //获取到当前被拦截方法的方法参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0) {
            return;
        }

        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据不同的操作类型,为对应的属性通过反射来赋值
        if (operationType == OperationType.INSERT) {
            //尝试从该类中查找名为 setCreateTime 的方法。
            //确保该方法的参数列表与指定的参数类型(这里是 LocalDateTime.class)匹配。
            Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
            Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
            Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
            Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

            //通过反射为对象属性赋值
            setCreateTime.invoke(entity,now);//invoke:动态调用 entity 对象的 setCreateTime 方法,将当前时间(now)作为参数传入。
            setCreateUser.invoke(entity,currentId);
            setUpdateTime.invoke(entity,now);
            setUpdateUser.invoke(entity,currentId);
        }else if(operationType == OperationType.UPDATE) {
            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);
        }
    }
}
  • 在Mapper的方法上加上AutoFill注解
 /**
     * 动态更新员工信息
     * @param e
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Employee e);
2.1 学会了自定义注解里的Target和Retention注解

Target:是一个元注解,用于指定自定义注解可以应用的目标元素类型。

ElementType 是一个枚举类,常见的取值及其含义如下:

  • ElementType.TYPE:可以应用于类、接口或枚举(即类型声明上)。
  • ElementType.FIELD:可以应用于字段(包括枚举常量)。
  • ElementType.METHOD:可以应用于方法。
  • ElementType.PARAMETER:可以应用于方法参数。
  • ElementType.CONSTRUCTOR:可以应用于构造方法。
  • ElementType.LOCAL_VARIABLE:可以应用于局部变量。
  • ElementType.ANNOTATION_TYPE:可以应用于其他注解。
  • ElementType.PACKAGE:可以应用于包声明。

Retention:是一个元注解,用于指定自定义注解的生命周期。(即注解在什么时候用)

RetentionPolicy 是一个枚举类,常见的取值及其含义如下:

  • RetentionPolicy.SOURCE:注解只在源码中保留,编译后会被丢弃(如 @Override)。
  • RetentionPolicy.CLASS:注解在编译后的 .class 文件中保留,但运行时不可用(默认值)。
  • RetentionPolicy.RUNTIME:注解在运行时保留,可以通过反射机制访问。
2.2 反射

面向AOP编程的主要目标就是在方法执行的前后动态的插入额外的逻辑。所以为了实现这一点,AOP需要具备以下能力:

  • 动态获取方法信息:了解哪个方法被调用、它的参数是什么。
  • 动态调用方法:拦截方法调用并执行额外逻辑。
  • 动态操作对象:为目标对象动态添加功能,而无需修改其源代码。

这些需求都涉及到对类和方法的运行时操作,而这正是反射的主要用途。

例如可以通过反射获取方法上的注解:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod(); // 获取当前方法
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); // 获取注解

或者通过反射,动态的调用方法:

Object result = method.invoke(targetObject, args);

今日总结:

之前偷懒把反射这里跳过了,现在听了一下感觉非常难理解。打算明天系统的过一遍反射。

如果我的内容对你有帮助,请点赞,评论,收藏创作不易,大家的支持就是我坚持下去的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值