【SpringBoot】公共字段自动填充功能实现(枚举、自定义注解、AOP、反射)

1. 自定义注解

使用@interface语法来定义注解(Annotation)。

注解的参数类似无参数方法,可以用default设定一个默认值,比如String value() default "";

元注解:有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。

@Target
使用@Target可以定义Annotation能够被应用于源码的哪些位置(多个写成数组的形式:{ ElementType.METHOD, ElementType.FIELD }):

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

@Retention
元注解@Retention定义了Annotation的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

如果@Retention不存在,则该Annotation默认为CLASS。但是通常我们自定义的Annotation都是RUNTIME

应用实例如下:

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();
}

枚举数据库操作类型 UPDATE, INSERT。

package com.sky.enumeration;/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}

2. AOP

2.1 AOP 相关概念

AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)。

AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
面向这样的指定的一个或多个方法进行编程,我们就称之为面向切面编程。

1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)。
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)。
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)。切面类(被@Aspect注解标识的类)。
5. 目标对象:Target,通知所应用的对象。

Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。

AOP 常见应用场景:

  • 记录系统的操作日志
  • 权限控制
  • 事务管理:只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 通知类型

Spring中AOP的通知类型:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

在使用通知时的注意事项:

  • @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。

2.3 抽取

@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。避免了每一个注解里面都指定了相同的切入点表达式,增强复用性和可维护性。

需要注意的是:当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式,当外部其他切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候,具体的语法为:全类名.方法名(),具体形式如下:

@Slf4j
@Component
@Aspect
public class MyAspect2 {//引用MyAspect1切面类中的切入点表达式@Before("com.itheima.aspect.MyAspect1.pt()")public void before(){log.info("MyAspect2 -> before ...");}
}

2.3 通知顺序

默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

如果我们想控制通知的执行顺序有两种方式:

  1. 修改切面类的类名(这种方式非常繁琐、而且不便管理)
  2. 使用Spring提供的@Order注解

使用@Order注解,控制通知的执行顺序:

@Slf4j
@Component
@Aspect
@Order(2)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect2 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect2 -> before ...");}//后置通知 @After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect2 -> after ...");}
}
@Slf4j
@Component
@Aspect
@Order(3)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect3 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect3 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect3 ->  after ...");}
}
@Slf4j
@Component
@Aspect
@Order(1) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect4 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect4 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect4 -> after ...");}
}

在这里插入图片描述

2.4 切入点表达式

主要用来决定项目中的哪些方法需要加入通知

  1. execution(……):根据方法的签名来匹配
    execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)

  • 包名.类名: 可省略

  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
    在这里插入图片描述
    可以使用通配符描述切入点

  • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

  • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

切入点表达式的语法规则:

  1. 方法的访问修饰符可以省略
  2. 返回值可以使用*号代替(任意返回值类型)
  3. 包名可以使用*号代替,代表任意包(一层包使用一个*
  4. 使用..配置包名,标识此包以及此包下的所有子包
  5. 类名可以使用*号代替,标识任意类
  6. 方法名可以使用*号代替,表示任意方法
  7. 可以使用 * 配置参数,一个任意类型的参数
  8. 可以使用.. 配置参数,任意个任意类型的参数

切入点表达式示例

  • 省略方法的修饰符号

    execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用*代替返回值类型

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用*代替包名(一层包使用一个*

    execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用..省略包名

    execution(* com..DeptServiceImpl.delete(java.lang.Integer))    
    
  • 使用*代替类名

    execution(* com..*.delete(java.lang.Integer))   
    
  • 使用*代替方法名

    execution(* com..*.*(java.lang.Integer))   
    
  • 使用 * 代替参数

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
    
  • 使用..省略参数

    execution(* com..*.*(..))
    
  • 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

    execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))
    
  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性

    execution(* com.itheima.service.DeptService.*(..))
    
  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 …,使用 * 匹配单个包

    execution(* com.itheima.*.*.DeptServiceImpl.find*(..))
    
  1. @annotation(……) :根据注解匹配
    在这里插入图片描述
    实现步骤:

  2. 编写自定义注解

  3. 在业务类要做为连接点的方法上添加自定义注解

2.5 ​连接点

连接点,连接点可以简单理解为可以被AOP控制的方法。

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

3. 反射

3.1 反射的概念

动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

  • 利用反射创建的对象可以无视修饰符调用类里面的内容

  • 可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。

    读取到什么类,就创建什么类的对象

    读取到什么方法,就调用什么方法

    此时当需求变更的时候不需要修改代码,只要修改配置文件即可。

字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。这个对象里面至少包含了:构造方法,成员变量,成员方法。

反射获取的是字节码文件对象,这个对象在内存中是唯一的。

3.2 获取字节码文件对象

  • Class这个类里面的静态方法forName(“全类名”)(最常用)
  • 通过class属性获取
  • 通过对象获取字节码文件对象

代码示例:

//1.Class这个类里面的静态方法forName
//Class.forName("类的全类名"): 全类名 = 包名 + 类名
Class clazz1 = Class.forName("com.itheima.reflectdemo.Student");
//源代码阶段获取 --- 先把Student加载到内存中,再获取字节码文件的对象
//clazz 就表示Student这个类的字节码文件对象。
//就是当Student.class这个文件加载到内存之后,产生的字节码文件对象//2.通过class属性获取
//类名.class
Class clazz2 = Student.class;//因为class文件在硬盘中是唯一的,所以,当这个文件加载到内存之后产生的对象也是唯一的
System.out.println(clazz1 == clazz2);//true//3.通过Student对象获取字节码文件对象
Student s = new Student();
Class clazz3 = s.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz2 == clazz3);//true

3.3 获取构造方法

方法名说明
Constructor<?>[] getConstructors()获得所有的构造(只能public修饰)
Constructor<?>[] getDeclaredConstructors()获得所有的构造(包含private修饰)
Constructor getConstructor(Class<?>… parameterTypes)获取指定构造(只能public修饰)
Constructor getDeclaredConstructor(Class<?>… parameterTypes)获取指定构造(包含private修饰)

代码示例:

public class ReflectDemo2 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {//1.获得整体(class字节码文件对象)Class clazz = Class.forName("com.itheima.reflectdemo.Student");//2.获取构造方法对象//获取所有构造方法(public)Constructor[] constructors1 = clazz.getConstructors();for (Constructor constructor : constructors1) {System.out.println(constructor);}System.out.println("=======================");//获取所有构造(带私有的)Constructor[] constructors2 = clazz.getDeclaredConstructors();for (Constructor constructor : constructors2) {System.out.println(constructor);}System.out.println("=======================");//获取指定的空参构造Constructor con1 = clazz.getConstructor();System.out.println(con1);Constructor con2 = clazz.getConstructor(String.class,int.class);System.out.println(con2);System.out.println("=======================");//获取指定的构造(所有构造都可以获取到,包括public包括private)Constructor con3 = clazz.getDeclaredConstructor();System.out.println(con3);//了解 System.out.println(con3 == con1);//每一次获取构造方法对象的时候,都会新new一个。Constructor con4 = clazz.getDeclaredConstructor(String.class);System.out.println(con4);}
}

3.4 获取构造方法并创建对象

涉及到的方法:newInstance

代码示例:

//首先要有一个javabean类
public class Student {private String name;private int age;public Student() {}public Student(String name) {this.name = name;}private Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}
}//测试类中的代码:
//需求1:
//获取空参,并创建对象//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取空参的构造方法
Constructor con = clazz.getConstructor();
//3.利用空参构造方法创建对象
Student stu = (Student) con.newInstance();
System.out.println(stu);System.out.println("=============================================");//测试类中的代码:
//需求2:
//获取带参构造,并创建对象
//1.获取整体的字节码文件对象
Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");
//2.获取有参构造方法
Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
//3.临时修改构造方法的访问权限(暴力反射)
con.setAccessible(true);
//4.直接创建对象
Student stu = (Student) con.newInstance("zhangsan", 23);
System.out.println(stu);

3.5 获取成员变量

方法名说明
Field[] getFields()返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name)返回单个成员变量对象,存在就能拿到

代码示例:

public class ReflectDemo4 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {//获取成员变量对象//1.获取class对象Class clazz = Class.forName("com.itheima.reflectdemo.Student");//2.获取成员变量的对象(Field对象)只能获取public修饰的Field[] fields1 = clazz.getFields();for (Field field : fields1) {System.out.println(field);}System.out.println("===============================");//获取成员变量的对象(public + private)Field[] fields2 = clazz.getDeclaredFields();for (Field field : fields2) {System.out.println(field);}System.out.println("===============================");//获得单个成员变量对象//如果获取的属性是不存在的,那么会报异常//Field field3 = clazz.getField("aaa");//System.out.println(field3);//NoSuchFieldExceptionField field4 = clazz.getField("gender");System.out.println(field4);System.out.println("===============================");//获取单个成员变量(私有)Field field5 = clazz.getDeclaredField("name");System.out.println(field5);}
}public class Student {private String name;private int age;public String gender;public String address;public Student() {}public Student(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public Student(String name, int age, String gender, String address) {this.name = name;this.age = age;this.gender = gender;this.address = address;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return gender*/public String getGender() {return gender;}/*** 设置* @param gender*/public void setGender(String gender) {this.gender = gender;}/*** 获取* @return address*/public String getAddress() {return address;}/*** 设置* @param address*/public void setAddress(String address) {this.address = address;}public String toString() {return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";}
}

3.6 获取成员变量并获取值和修改值

方法说明
void set(Object obj, Object value)赋值
Object get(Object obj)获取值

代码示例:

public class ReflectDemo5 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {Student s = new Student("zhangsan",23,"广州");Student ss = new Student("lisi",24,"北京");//需求://利用反射获取成员变量并获取值和修改值//1.获取class对象Class clazz = Class.forName("com.itheima.reflectdemo.Student");//2.获取name成员变量//field就表示name这个属性的对象Field field = clazz.getDeclaredField("name");//临时修饰他的访问权限field.setAccessible(true);//3.设置(修改)name的值//参数一:表示要修改哪个对象的name?//参数二:表示要修改为多少?field.set(s,"wangwu");//3.获取name的值//表示我要获取这个对象的name的值String result = (String)field.get(s);//4.打印结果System.out.println(result);System.out.println(s);System.out.println(ss);}
}public class Student {private String name;private int age;public String gender;public String address;public Student() {}public Student(String name, int age, String address) {this.name = name;this.age = age;this.address = address;}public Student(String name, int age, String gender, String address) {this.name = name;this.age = age;this.gender = gender;this.address = address;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return gender*/public String getGender() {return gender;}/*** 设置* @param gender*/public void setGender(String gender) {this.gender = gender;}/*** 获取* @return address*/public String getAddress() {return address;}/*** 设置* @param address*/public void setAddress(String address) {this.address = address;}public String toString() {return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";}
}

3.7 获取成员方法

方法名说明
Method[] getMethods()返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes)返回单个成员方法对象,存在就能拿到

代码示例:

public class ReflectDemo6 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {//1.获取class对象Class<?> clazz = Class.forName("com.itheima.reflectdemo.Student");//2.获取方法//getMethods可以获取父类中public修饰的方法Method[] methods1 = clazz.getMethods();for (Method method : methods1) {System.out.println(method);}System.out.println("===========================");//获取所有的方法(包含私有)//但是只能获取自己类中的方法Method[] methods2 = clazz.getDeclaredMethods();for (Method method : methods2) {System.out.println(method);}System.out.println("===========================");//获取指定的方法(空参)Method method3 = clazz.getMethod("sleep");System.out.println(method3);Method method4 = clazz.getMethod("eat",String.class);System.out.println(method4);//获取指定的私有方法Method method5 = clazz.getDeclaredMethod("playGame");System.out.println(method5);}
}

3.8 获取成员方法并运行

方法

Object invoke(Object obj, Object… args) :运行方法

参数一:用obj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

代码示例:

package com.itheima.a02reflectdemo1;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class ReflectDemo6 {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {//1.获取字节码文件对象Class clazz = Class.forName("com.itheima.a02reflectdemo1.Student");//2.获取一个对象//需要用这个对象去调用方法Student s = new Student();//3.获取一个指定的方法//参数一:方法名//参数二:参数列表,如果没有可以不写Method eatMethod = clazz.getMethod("eat",String.class);//运行//参数一:表示方法的调用对象//参数二:方法在运行时需要的实际参数//注意点:如果方法有返回值,那么需要接收invoke的结果//如果方法没有返回值,则不需要接收String result = (String) eatMethod.invoke(s, "重庆小面");System.out.println(result);}
}public class Student {private String name;private int age;public String gender;public String address;public Student() {}public Student(String name) {this.name = name;}private Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}public String toString() {return "Student{name = " + name + ", age = " + age + "}";}private void study(){System.out.println("学生在学习");}private void sleep(){System.out.println("学生在睡觉");}public String eat(String something){System.out.println("学生在吃" + something);return "学生已经吃完了,非常happy";}
}

面试题

​ 你觉得反射好不好?好,有两个方向

​ 第一个方向:无视修饰符访问类中的内容。但是这种操作在开发中一般不用,都是框架底层来用的。

​ 第二个方向:反射可以跟配置文件结合起来使用,动态的创建对象,动态的调用方法。

4. 公共字段自动填充功能实现

自定义注解见 目录1。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
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) {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();}}}
}
    /*** 根据id修改分类* @param category*/@AutoFill(value = OperationType.UPDATE)void update(Category category);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/328018.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言-整型在内存的存储

文章目录 前言一、整型数值在内存中的存储1.1 整型数值的表示形式1.2 二进制的表示形式1.3 整数在内存中存储 二、大端字节序存储和小端字节序存储2.1 大端字节序存储2.2 小端字节序存储2.3 练习 总结 前言 本篇文章叙述c语言中整型数据在内存中的存储方式。 一、整型数值在内…

自动驾驶预测-决策-规划-控制学习(4):预测分析文献阅读

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、摘要分析1.Transformer模型是什么&#xff1f;什么是自注意力机制&#xff1f; 2.数据集是什么&#xff1f;3.预测车辆行驶轨迹和车辆换道意图4. LSTM 网络…

P12 音视频复合流——TS流讲解

前言 从本章开始我们将要学习嵌入式音视频的学习了 &#xff0c;使用的瑞芯微的开发板 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《Linux C应用编程&#xff08;概念类&#xff09;_C…

MySQL之视图索引执行计划

目录 一.视图 二.执行计划 2.1.什么是执行计划 2.2.执行计划的作用 三.使用外连接、内连接和子查询进行举例 四.思维导图 好啦今天就到这里了哦&#xff01;&#xff01;&#xff01;希望能帮到你哦&#xff01;&#xff01;&#xff01; 一.视图 含义 &#xff1a;在数…

docker部署kibana

1&#xff0c;简介 官网 kibana 2&#xff0c;安装docker 参考 linux安装docker 3&#xff0c;准备 Kibana 配置文件 # 进入主节点配置文件目录 cd /export/server/docker/kibana/config # 编辑单机版配置文件 vi kibana.ymlkibana.yml内容 # 主机地址&#xff0c;可以是…

BUUCTF--actf_2019_babyheap1

这题看名字就知道是堆题&#xff0c;先看保护&#xff1a; 保护除了PIE全开&#xff0c;黑盒测试&#xff1a; 题目提供增删查&#xff0c;没有改。看看IDA中代码逻辑&#xff1a; 逻辑跟我前面做的一题极为相似&#xff0c;就不过多分析。 这是free&#xff1a; 因为题目不能…

八大算法排序@快速排序、递归版本一(C语言版本)

目录 快速排序版本一概念算法思想一二三 快排步骤代码实现时间复杂度空间复杂度特性总结 快速排序版本一 概念 快速排序&#xff08;Quicksort&#xff09;是一种高效的排序算法&#xff0c;它是由英国计算机科学家 Tony Hoare 在1960年提出的。快速排序是基于分治&#xff08…

修改对象的行为和值(代理)

文章目录 前言一、复制一个对象二、代理对象重点来了 总结 前言 最近遇到一个需求,需要在某个位置,统一处理对象的一些属性值&#xff1a; 方案有两种: 直接复制一份,将属性覆盖后,返回一个新对象搞一个代理类,代理这个对象,修改对象的原有行为和值,从而达到修改属性值的目的…

【GoLang入门教程】Go语言几种标准库介绍(四)

编程语言的未来&#xff1f; 文章目录 编程语言的未来&#xff1f;前言几种库fmt库 (格式化操作)关键函数&#xff1a;示例 Go库标准库第三方库示例 html库(HTML 转义及模板系统)主要功能&#xff1a;示例 总结专栏集锦写在最后 前言 上一篇&#xff0c;我们介绍了debug、enco…

关于“Python”的核心知识点整理大全65

目录 20.2.19 设置 SECRET_KEY 20.2.20 将项目从 Heroku 删除 注意 20.3 小结 附录 A 安装Python A.1.1 确定已安装的版本 A.1.2 在 Linux 系统中安装 Python 3 A.2 在 OS X 系统中安装 Python A.2.1 确定已安装的版本 A.2.2 使用 Homebrew 来安装 Python 3 注意 …

基于商品列表的拖拽排序后端实现

目录 一&#xff1a;实现思路 二&#xff1a;实现步骤 二&#xff1a;实现代码 三&#xff1a;注意点 一&#xff1a;实现思路 后台实现拖拽排序通常需要与前端进行配合&#xff0c;对商品的列表拖拽排序&#xff0c;前端需要告诉后端拖拽的元素和拖动的位置。 这里我们假…

Matlab三维绘图

绘制三维图plot3 t0:pi/50:10*pi; xsin(t); ycos(t); zt; plot3(x,y,z); 产生栅格数据点meshgrid 这个接口在绘制三维图像里面相当重要&#xff0c;很多时候要将向量变成矩阵才能绘制三维图。 x0:0.5:5; y0:1:10; [X,Y]meshgrid(x,y); plot(X,Y,o); x和y是向量&#xff0c;…