likes
comments
collection
share

Java注解、在Spring boot项目中自定义注解解决实际问题

作者站长头像
站长
· 阅读数 15

Java注解

在Java中注解其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,与普通的注释不同的是:普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。

注解的作用

  • 作为特定标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

注解的分类

注解通常分为三类:

  • 元注解:Java内置的注解,标明该注解的使用范围、生命周期等
  • 标准注解:Java提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告
  • 自定义注解:第三方定义的注解,含义和功能由第三方来定义和实现

元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。元注解主要包含以下五种: @Retention@Target@Documented@Inherited@Repeatable

@Retention

作用:声明注解的保留周期(生命周期)

该注解的不同参数及效果:

  • @Retention(RetentionPolicy.SOURCE):只作用在源码阶段,字节码文件中不存在
  • @Retention(RetentionPolicy.CLASS):保留到字节码文件阶段,运行阶段不存在
  • @Retention(RetentionPolicy.RUNTIME):一直保留到运行阶段

@Target

作用:声明被修饰的注解只能在哪些位置使用

该注解的不同参数及效果:

  • @Target(ElementType.TYPE):被修饰的注解只能在类、接口上使用
  • @Target(ElementType.FIELD):被修饰的注解只能在成员变量上使用
  • @Target(ElementType.METHOD):被修饰的注解只能在成员方法上使用
  • @Target(ElementType.PARAMETER):被修饰的注解只能在方法参数上使用
  • @Target(ElementType.CONSTRUCTOR):被修饰的注解只能在构造器上使用
  • @Target(ElementType.LOCAL_VARIABLE):被修饰的注解只能在局部变量上使用

@Documented

作用:是否在生成的Javadoc文档中体现,被标注该注解后,生成的javadoc中,会包含该注解

@Inherited

作用:是否可以被标注类的子类继承。被@Inherited修饰的注解是具有继承性的,在自定义的注解标注到某个类时,该类的子类会继承这个自定义注解。

@Repeatable

作用:是否可以重复标注

标准注解

Java 中自带且常用的几种标准注解有 @Override@Deprecated@SuppresWarninngs@SafeVarargs

@Override:是一个标记类型注解,用于提示子类要复写父类中被 @Override 修饰的方法,它说明了被标注的方法重载了父类的方法,起到了断言的作用

@Deprecated:也是一个标记类型注解,用于标记过时的元素。比如如果开发人员正在调用一个过时的方法、类或成员变量时,可以用该注解进行标注

@SuppressWarnings :可以阻止警告的提示

@SafeVarargs :是一个参数安全类型注解,它的目的是提醒开发人员,不要用参数做一些不安全的操作

自定义注解

自定义注解的基本格式(修饰符、默认值可缺失):

public @interface 注解名 {
  修饰符 返回值 属性名() 默认值;
  修饰符 返回值 属性名() 默认值;
}

定义一个简单的注解示例:

// 保留至运行时
@Retention(RetentionPolicy.RUNTIME)
// 可以加在方法或者类上
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
    public String method() default "GET";
    String path();
}

注解的本质就是一个接口,继承了java.lang.annotation.Annotation

自定义注解在Spring boot项目中的应用实例

在Spring boot框架中,注解给项目开发带来了巨大的方便,使用注解的优势主要有:

  • 采用纯Java代码,不在需要配置繁杂的xml文件
  • 在配置中也可享受面向对象带来的好处
  • 类型安全对重构可以提供良好的支持
  • 减少复杂配置文件的同时亦能享受到Spring IoC容器提供的功能

在Spring boot框架中,同样可以自定义注解并用于优化项目开发,实例如下:

问题描述

在项目业务表中存在以下四个公共字段:

字段名含义项目涉及的操作类型
create_time创建时间insert
create_user创建人idinsert
update_time修改时间insert/update
update_user修改人idinsert/update

当创建或修改相关信息时,这几个字段也会做相应的set方法赋值,同时存在着代码冗余、不便于后期维护的问题,为解决这个问题,可以加一个公共字段自动填充的功能

实现思路

公共字段自动填充功能的实现思路:

  • 自定义注解@AutoFill,用于标识需要进行公共字段自动填充的方法
  • 自定义切面类AutoFillAspect,统一拦截加入了@AutoFill注解的方法,通过反射为公共字段赋值
  • 在Mapper的方法上加上@AutoFill注解

用到的知识点:枚举注解AOP反射

自定义注解@AutoFill

首先定义一个枚举类,用于作为@AutoFill的参数即数据库操作类型:

public enum OperationType {
    // 更新操作
    UPDATE,

    //插入操作
    INSERT
}

然后自定义注解@AutoFill

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT,OperationType在枚举类中定义
    //写法:@AutoFill(value = OperationType.UPDATE)、@AutoFill(value = OperationType.INSERT)
    OperationType value();
}

自定义切面类AutoFillAspect

@Aspect
@Component
@Slf4j
public class AutoFillAspect {
    //切入点,切入点在com.sky.mapper.*.*(..)且有注解@AutoFill下的方法执行
    @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("开始进行公共字段自动填充");

        //获取到当前被拦截的方法上的数据库操作类型(UPDATE/INSERT)
        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){
            //为四个公共字段赋值
            Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
            Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser", Long.class);
            Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
            Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);

            //通过反射为对象赋值
            setCreateTime.invoke(entity,now);
            setCreateUser.invoke(entity,currentId);
            setUpdateTime.invoke(entity,now);
            setUpdateUser.invoke(entity,currentId);
        }else if(operationType == OperationType.UPDATE){
            //为两个公共字段赋值
            Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class);
            Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class);
            //通过反射为对象赋值
            setUpdateTime.invoke(entity,now);
            setUpdateUser.invoke(entity,currentId);
        }
    }
}

在相关方法上加上@AutoFill

    @AutoFill(value = OperationType.INSERT)
    void insert(Employee employee);

    @AutoFill(value = OperationType.UPDATE)
    void update(Employee employee);