当前位置: 首页 > news >正文

【SpringBoot苍穹外卖】debugDay03.5

1、AOP面向切面编程

1. @Target(ElementType.METHOD)

  • 作用:指定自定义注解可以应用的目标范围。

  • 参数ElementType 是一个枚举类,定义了注解可以应用的目标类型。

    • ElementType.METHOD 表示该注解只能用于方法上。

    • 其他常见的 ElementType 值:

      • TYPE:类、接口、枚举等。

      • FIELD:字段(包括枚举常量)。

      • PARAMETER:方法参数。

      • CONSTRUCTOR:构造函数。

      • ANNOTATION_TYPE:注解类型。

      • PACKAGE:包。

      • 等等。

示例

@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value() default "";
}
  • 上面的 @MyAnnotation 注解只能用于方法上,如果尝试将其用于类或字段上,编译器会报错。


2. @Retention(RetentionPolicy.RUNTIME)

  • 作用:指定自定义注解的生命周期,即注解在什么阶段有效。

  • 参数RetentionPolicy 是一个枚举类,定义了注解的保留策略。

    • RetentionPolicy.RUNTIME:注解在运行时保留,可以通过反射机制读取。

    • RetentionPolicy.CLASS:注解在编译时保留,但运行时不可见(默认行为)。

    • RetentionPolicy.SOURCE:注解仅在源码阶段保留,编译后会被丢弃。

示例

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}
  • 上面的 @MyAnnotation 注解在运行时仍然有效,可以通过反射获取注解信息。


结合使用

通常,@Target 和 @Retention 会一起使用,以明确注解的作用范围和生命周期。

示例

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) // 该注解在运行时有效
public @interface MyAnnotation {
    String value() default "";
}

使用场景

public class MyClass {
    @MyAnnotation(value = "This is a method")
    public void myMethod() {
        System.out.println("Hello, World!");
    }
}

通过反射获取注解信息

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Method method = MyClass.class.getMethod("myMethod");
        MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
        if (annotation != null) {
            System.out.println("Annotation value: " + annotation.value());
        }
    }
}

输出

Annotation value: This is a method

总结

  • @Target(ElementType.METHOD):指定注解只能用于方法上。

  • @Retention(RetentionPolicy.RUNTIME):指定注解在运行时仍然有效,可以通过反射读取。

  • 这两个注解通常一起使用,用于定义自定义注解的行为和适用范围。

3、反射(Reflection)

 是 Java 提供的一种强大的机制,允许程序在运行时动态地获取类的信息(如类名、方法、字段、构造函数等),并操作这些信息(如调用方法、访问字段、创建对象等)。反射的核心思想是“在运行时检查和修改程序的行为”。

反射的主要用途包括:

  1. 动态加载类。

  2. 动态创建对象。

  3. 动态调用方法。

  4. 动态访问和修改字段。

  5. 获取注解信息。


反射的核心类

Java 反射的核心类和接口位于 java.lang.reflect 包中,主要包括:

  • Class<T>:表示一个类或接口,是反射的入口。

  • Method:表示类的方法。

  • Field:表示类的字段(成员变量)。

  • Constructor<T>:表示类的构造函数。

  • Annotation:表示注解。


反射的基本用法

1. 获取 Class 对象

Class 对象是反射的起点,可以通过以下方式获取:

  • Class.forName("全限定类名"):通过类的全限定名获取。

  • 对象.getClass():通过对象的实例获取。

  • 类名.class:通过类字面量获取。

示例

Class<?> clazz = Class.forName("com.example.MyClass");
2. 创建对象

通过 Class 对象可以动态创建类的实例。

示例

Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.getDeclaredConstructor().newInstance();
3. 调用方法

通过 Method 对象可以动态调用类的方法。

示例

Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.getDeclaredConstructor().newInstance();

// 获取方法
Method method = clazz.getMethod("myMethod", String.class);

// 调用方法
method.invoke(obj, "Hello, Reflection!");
4. 访问字段

通过 Field 对象可以动态访问和修改类的字段。

示例

Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.getDeclaredConstructor().newInstance();

// 获取字段
Field field = clazz.getDeclaredField("myField");

// 设置字段可访问(如果是私有字段)
field.setAccessible(true);

// 修改字段值
field.set(obj, "New Value");

// 获取字段值
Object value = field.get(obj);
System.out.println(value);
5. 获取注解信息

通过反射可以获取类、方法、字段等元素上的注解信息。

示例

Class<?> clazz = Class.forName("com.example.MyClass");

// 获取类上的注解
MyAnnotation classAnnotation = clazz.getAnnotation(MyAnnotation.class);
if (classAnnotation != null) {
    System.out.println("Class Annotation Value: " + classAnnotation.value());
}

// 获取方法上的注解
Method method = clazz.getMethod("myMethod");
MyAnnotation methodAnnotation = method.getAnnotation(MyAnnotation.class);
if (methodAnnotation != null) {
    System.out.println("Method Annotation Value: " + methodAnnotation.value());
}

反射的优缺点

优点
  1. 灵活性:可以在运行时动态加载类、调用方法、访问字段,适合编写通用框架或工具。

  2. 扩展性:通过反射可以实现插件化架构,动态加载第三方类。

  3. 注解处理:反射是处理注解的基础,许多框架(如 Spring、MyBatis)都依赖反射来解析注解。

缺点
  1. 性能开销:反射操作比直接调用方法或访问字段要慢,因为涉及动态解析。

  2. 安全性问题:反射可以绕过访问控制(如访问私有字段或方法),可能导致安全问题。

  3. 代码可读性差:反射代码通常比较复杂,难以理解和维护。


反射的实际应用场景

  1. 框架开发:许多框架(如 Spring、Hibernate、MyBatis)都使用反射来实现依赖注入、动态代理、注解解析等功能。

  2. 单元测试:测试框架(如 JUnit)使用反射来调用测试方法。

  3. 动态加载类:在插件化架构中,通过反射动态加载第三方类。

  4. 序列化和反序列化:通过反射访问对象的字段,实现对象的序列化和反序列化。


示例代码

以下是一个完整的反射示例,展示了如何通过反射创建对象、调用方法和访问字段:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        // 获取 Class 对象
        Class<?> clazz = Class.forName("com.example.MyClass");

        // 创建对象
        Object obj = clazz.getDeclaredConstructor().newInstance();

        // 调用方法
        Method method = clazz.getMethod("myMethod", String.class);
        method.invoke(obj, "Hello, Reflection!");

        // 访问字段
        Field field = clazz.getDeclaredField("myField");
        field.setAccessible(true); // 设置私有字段可访问
        field.set(obj, "New Value");
        System.out.println("Field Value: " + field.get(obj));
    }
}

class MyClass {
    private String myField = "Default Value";

    public void myMethod(String message) {
        System.out.println("Method Invoked: " + message);
    }
}

输出

Method Invoked: Hello, Reflection!
Field Value: New Value

总结

反射是 Java 中一种强大的机制,允许程序在运行时动态操作类和对象。虽然反射提供了极大的灵活性,但也带来了性能开销和安全性问题,因此应谨慎使用。在实际开发中,反射常用于框架开发、注解处理、动态加载类等场景。

4、idea快捷键ctrl + alt + b

快速转到接口实现方法。

5、实战

1 步骤一

自定义注解 AutoFill

进入到sky-server模块,创建com.sky.annotation包。

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)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

其中OperationType已在sky-common模块中定义

package com.sky.enumeration;

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

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

    /**
     * 插入操作
     */
    INSERT
}
2 步骤二

自定义切面 AutoFillAspect

在sky-server模块,创建com.sky.aspect包。

package com.sky.aspect;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        /重要
        //可先进行调试,是否能进入该方法 提前在mapper方法添加AutoFill注解
        log.info("开始进行公共字段自动填充...");

    }
}

完善自定义切面 AutoFillAspect 的 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.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.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        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){
            //为4个公共字段赋值
            try {
                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);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                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();
            }
        }
    }
}
3 步骤三

在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作

package com.sky.mapper;

@Mapper
public interface CategoryMapper {
    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);
    /**
     * 根据id修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

}

同时,将业务层为公共字段赋值的代码注释掉。

1). 将员工管理的新增和编辑方法中的公共字段赋值的代码注释。

2). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

相关文章:

  • Java 设计模式之备忘录模式
  • 2.11学习
  • Unity 命令行设置运行在指定的显卡上
  • Hyper-V管理器连接服务器提示你没有完成此任务所需的权限
  • 日常故障排查 - Linux常用命令
  • C++ 中的继承与派生
  • Windchill 成套的解决方案
  • Linux Mem -- ARM8.5-A Memory Tagging Extension
  • 人工智能大模型技术剖析:分类、对比与性能洞察
  • 【情感识别】SECap: Speech Emotion Captioning with Large Language Model 论文阅读
  • 逻辑回归不能解决非线性问题,而svm可以解决
  • 【prompt实战】旅行攻略顾问
  • 二、k8s项目的生命周期
  • 闭源大语言模型的怎么增强:提示工程 检索增强生成 智能体
  • hot100_101. 对称二叉树
  • 国家队出手!DeepSeek上线国家超算互联网平台!
  • 使用docker compose启动postgres并设置时区
  • 力扣100. 相同的树(利用分解思想解决)
  • 相机与激光雷达联合标定综述
  • 网络安全 | 什么是网络安全?
  • 第一集丨《亲爱的仇敌》和《姜颂》,都有耐人寻味的“她”
  • 武汉旅游体育集团有限公司原党委书记、董事长董志向被查
  • 解放军仪仗分队参加白俄罗斯纪念苏联伟大卫国战争胜利80周年阅兵活动
  • 14岁女生瞒报年龄文身后洗不掉,法院判店铺承担六成责任
  • 蔡达峰:推动食品安全法全面有效实施,为维护人民群众身体健康提供有力法治保障
  • 国博馆刊|北朝至唐初夏州酋豪李氏家族的发展与身份记忆