Java自定义注解实现持久化时自动设置创建人字段

先简单介绍一下自定义注解相关的一些前置知识,完整代码可通过目录直接跳转查看

1. 注解的定义

1.1 元注解

元注解 的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation(元注解)类型,它们被用来提供对其它 annotation类型作说明

标准的元注解:
  • @Target
  • @Retention
  • @Documented
  • @Inherited

下面对这四个元注解进行详细介绍:

@Target:标明作用范围,取值在java.lang.annotation.ElementType 中进行定义

public enum ElementType {
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,

    /** 属性的声明 */
    FIELD,

    /** 方法的声明 */
    METHOD,

    /** 方法形式参数声明 */
    PARAMETER,

    /** 构造方法的声明 */
    CONSTRUCTOR,

    /** 局部变量声明 */
    LOCAL_VARIABLE,

    /** 注解类型声明 */
    ANNOTATION_TYPE,

    /** 包的声明 */
    PACKAGE
}

@Retention:修饰自定义注解的生命周期,取值在java.lang.annotation.RetentionPolicy中定义:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     * (注解将被编译器忽略掉)
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     * (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

@Documented:指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中

@Inherited:是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解,@Inherited注解只对那些@Target被定义为 ElementType.TYPE 的自定义注解起作用

介绍完元注解之后,我们再来看一下自定义注解的实现代码。

1.2 基于Spring AOP开发

1.2.1 AspectJ

AspectJ 是一个基于 Java 语言的 AOP 框架。在 Spring 2.0 以后,新增了对 AspectJ 框架的支持。在 Spring 框架中建议使用 AspectJ 框架开发 AOP,在使用 AspectJ 配置切面时,切面不需要实现一些特定的接口。

1.2.1.1 AspectJ术语
  • Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点,连接点具有以下方法

    joinPoint.getTarget();     获取目标对象
    joinPoint.getSignature().getName();   获取目标方法名
    joinPoint.getArgs();    获取目标方法参数列表
    joinPoint.getThis();    获取代理对象
    
  • **Pointcut(切入点): ** 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,切入点指示符规则稍后详细介绍

    方式一:

    @Before("within(com.baomidou.mybatisplus.extension.service.IService+)")
    public void before(JoinPoint joinPoint) {
    	System.out.println("进入切面");
    }
    

    方式二:

    /**
     * 使用Pointcut定义切点
     */
    @Pointcut("execution(* com.dwp.spring.springAop.dao.UserDao.addUser(..))")
    private void myPointcut(){}
    
    /**
     * 应用切入点函数
     */
    @After(value="myPointcut()")
    public void after(){
        System.out.println("最终通知....");
    }
    
  • Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情,分为以下五种类型

    • before 目标方法执行前执行,前置通知
    • after 目标方法执行后执行,后置通知
    • after returning 目标方法返回时执行 ,后置返回通知
    • after throwing 目标方法抛出异常时执行 异常通知
    • around 在目标函数执行中执行,可控制目标函数是否执行,环绕通知
  • Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

  • Target(目标对象): 代理的目标对象(要增强的类)

  • Weaving(织入): 是把增强应用到目标的过程,把advice 应用到 target的过程

  • Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类

1.2.1.2 切入点指示符:

为了方法通知应用到相应过滤的目标方法上,SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符.

a. 通配符

在定义匹配表达式时,通配符几乎随处可见,如*、… 、+ ,它们的含义如下:

  • … :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包

    //任意返回值,任意名称,任意参数的公共方法
    execution(public * *(..))
    //匹配com.dwp.dao包及其子包中所有类中的所有方法
    within(com.dwp.dao..*)
    
  • + :匹配给定类的任意子类

    //匹配实现了DaoUser接口的所有子类的方法
    within(com.dwp.dao.DaoUser+)
    
  • * :匹配任意数量的字符

    //匹配com.dwp.service包及其子包中所有类的所有方法
    within(com.dwp.service..*)
    //匹配以set开头,参数为int类型,任意返回值的方法
    execution(* set*(int))
    

b. 类型签名表达式

为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:

within(<type name>)

示例:

//匹配com.dwp.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.dwp.dao..*)")

//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.dwp.dao.UserDaoImpl)")

//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.dwp.dao.UserDaoImpl+)")

//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.dwp.dao.UserDao+)")

c. 方法签名表达式

如果想根据方法签名进行过滤,关键字execution可以帮到我们,语法表达式如下

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))

对于给定的作用域、返回值类型、完全限定类名以及参数匹配的方法将会应用切点函数指定的通知,这里给出模型案例:

//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.dwp.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.dwp.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.dwp.dao.UserDaoImpl.*(..))")

//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.dwp.dao.UserDaoImpl.*(int , ..))")

d. 其他指示符

  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

    //匹配名称中带有后缀Service的Bean。
    @Pointcut("bean(*Service)")
    private void myPointcut1(){}
    
  • this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配

    //匹配了任意实现了UserDao接口的代理对象的方法进行过滤
    @Pointcut("this(com.dwp.spring.springAop.dao.UserDao)")
    private void myPointcut2(){}
    
  • target :用于匹配当前目标对象类型的执行方法;

    //匹配了任意实现了UserDao接口的目标对象的方法进行过滤
    @Pointcut("target(com.dwp.spring.springAop.dao.UserDao)")
    private void myPointcut3(){}
    
  • @within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;

    //匹配使用了MarkerAnnotation注解的类(注意是类)
    @Pointcut("@within(com.dwp.spring.annotation.MarkerAnnotation)")
    private void myPointcut4(){}
    
  • @annotation(com.dwp.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤

    //匹配使用了MarkerAnnotation注解的方法(注意是方法)
    @Pointcut("@annotation(com.dwp.spring.annotation.MarkerAnnotation)")
    private void myPointcut5(){}
    

最后说明一点,切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!),如下例:

//匹配了任意实现了UserDao接口的目标对象的方法并且该接口不在com.dwp.dao包及其子包下
@Pointcut("target(com.dwp.spring.springAop.dao.UserDao) !within(com.dwp.dao..*)")
private void myPointcut6(){}

//匹配了任意实现了UserDao接口的目标对象的方法并且该方法名称为addUser
@Pointcut("target(com.dwp.spring.springAop.dao.UserDao)&&execution(* com.dwp.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}

1.3 完整代码

自定义注解工程:

pom.xml引入依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

Insert.class:

import java.lang.annotation.*;

/**
 * 修饰在方法上,用于数据持久化时自动给创建人属性字段赋值
 * 

* 从Keycloak中获取操作用户信息,如果实体类存在指定名称的属性,则会自动为其赋值 * * @author deng.weiping * @since 0.0.1 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Insert { /** * 需赋值的字段名称,默认值:createBy */ String name() default "createBy"; }

InsertAspect.class:

import com.dwp.sdw.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class InsertAspect {

    /**
    * 前置通知
    */
    @Before("@annotation(an)")
    public void before(JoinPoint joinPoint, Insert an) {
        invokeMethod(joinPoint, an);
    }

    /**
     * 获取请求参数
     *
     * @param point
     * @return
     */
    private static void invokeMethod(JoinPoint point, Insert an) {
        Object[] objs = point.getArgs();
        if (objs.length > 0) {
            for (Object obj : objs) {
                ReflectUtil.setSpecifiedFieldValue(obj, an.name());
            }
        }
    }

}

ReflectUtil.class:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class ReflectUtil {
    /**
     * 查找指定属性并赋值
     *
     * @param bean      类对象
     * @param fieldName 字段名
     */
    public static void setSpecifiedFieldValue(Object bean, String fieldName) {
        try {
            Class clazz = bean.getClass();
            while (clazz != Object.class) {
                Field[] fields = clazz.getDeclaredFields();
                List<Field> results = Arrays.stream(fields).filter(field -> fieldName.equals(field.getName())).collect(Collectors.toList());
                if (!results.isEmpty()) {
                    results.forEach(field -> setFieldValue(bean, field));
                    //最小匹配
                    break;
                }
                //当前类未匹配上指定字段,尝试向上从父类中查找指定字段
                clazz = clazz.getSuperclass();
            }
        } catch (Exception e) {
            log.warn("The specified field was not matched");
        }
    }

    /**
     * 通过反射给类的属性赋值
     *
     * @param bean  类对象
     * @param field 类属性
     */
    public static void setFieldValue(Object bean, Field field) {
        field.setAccessible(true);
        try {
            /**
             * 具体值根据实际情况自行获取
             * 这里使用Keycloak
             */
            field.set(bean, KeycloakUtils.getUsername());
        } catch (IllegalAccessException e) {
            log.warn("Failed to set a value for the property:{}", e);
        }
    }
}

2 注解的使用

项目工程

  1. pom.xml引入自定义注解依赖
<dependency>
    <groupId>com.dwpgroupId>
    <artifactId>sdw-annotationartifactId>
    <version>0.0.1version>
dependency>
  1. 在方法上加上注解
@Insert
public void save(Student stu) {
    studentMapper.insert(stu);
}

你可能感兴趣的:(java,spring,mybatis)