欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > 【SpringBoot苍穹外卖】debugDay03.5

【SpringBoot苍穹外卖】debugDay03.5

2025/2/23 15:23:39 来源:https://blog.csdn.net/m0_72696598/article/details/145592952  浏览:    关键词:【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 INSERTOperationType 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). 将菜品分类管理的新增和修改方法中的公共字段赋值的代码注释。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词