[转]Java注解技术详细解析和示例

2022-12-15 470点热度 0人点赞 0条评论

在网上看很多文章都在说自定义注解的语法,这谁不会,我要的是怎么在项目中用,什么时候自定义,如果你是这样的需求这篇文章一定可以满足你!老样子,文章很长很舒服,建议收藏反复阅读!喜欢记得点赞哦~

本文章会从以下几点全面讲解Java注解:

  • 注解概念和分类
  • 自定义注解
  • 通过SpringBoot + AOP实现 2个 自定义注解的应用案例
  • 案例的深入优化,以及Spring框架中的相同注解源码
  • Java8重复注解和类型注解

从JDK5刚推出注解,到Java8的重复注解全面介绍Java注解的所有知识点,看完本篇文章你将会:

  • 知道自定义注解怎么在项目中运用了,妈妈再也不担心在外边受欺负了
  • 注解的面试题就不需要看了,看了,练了,想了就都明白了

注解概念

注解【Annotation】是Java5引入的新机制,也被称为元数据,为我们在代码中添加信息提供了一种形式化的方法,可以理解为给代码打一个标记,允许我们可以在稍后的某个时刻非常方便地使用这些数据。

Java注解也是对来自像C#之类的其他语言对Java造成的语言特性压力所做出的一种回应。

注解特点

Java中的注解可以使用在类、构造器、方法、成员变量、参数等位置上。和 Javadoc 不同,Java 注解可以通过反射获取注解内容。在编译器生成类文件时,注解可以存在于字节码中【注意是可以,不是一定,是可以控制的】。Java 虚拟机可以保留注解内容,在运行时可以通过反射机制获取到注解内容 。 当然它也支持自定义注解【重点】

注解重要的是现有的Java注解和第三方注解的作用和使用方法,以及自定义注解

小贴士:无论是自定义注解还是使用框架或者JDK提供的注解,都需要配合反射才能玩出花样,因为大多数注解的作用是在运行时搞点事情,而反射机制就是在运行时可以获得类信息来搞事情,一啪即合 ——> 完美!只有注解没有反射就是耍流氓!

注解的好处:

  • 可以读懂别人的代码,特别是框架的代码
  • 让编程更加简洁,代码更加清晰,比如使用Spring、MyBatis等框架都要使用大量注解
  • 让别人直呼内行,装在无形之中

注解分类

Java定义了一套注解,元注解在 java.lang.annotation 包,标准注解在 java.lang中:

  • @Override:使用在方法上,判断是否是子类重写父类的方法
  • @Deprecated:使用在方法或类上,标记为过时或弃用方法【类】,被该注解标识的方法【类】一般都不建议再使用,老的API中有许多过时方法
  • @SuppressWarnings:使用在方法上,禁止警告注解,代码不规范或者某变量未使用,代码冗余,过长等编辑器都会警告,有些洁癖公司不允许出现警告,这时可以利用该注解清除

作用在其他注解的注解【称为:元注解】是:

  • @Retention:标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
  • @Documented:标记这些注解是否包含在用户文档中
  • @Target:标记这个注解应该是哪种 Java 成员
  • @Inherited:标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs:Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
  • @FunctionalInterface:Java 8 开始支持,标识一个匿名函数或函数式接口
  • @Repeatable:Java 8 开始支持,标识某注解可以在同一个声明上使用多次,自定义注解需要使用到,也就是重复注解

Java内置注解

简单使用一下常见的三个标准注解:@Override、@Deprecated、@SuppressWarnings

声明父类:

public class Person {
    private String name;
    private Integer age;
    // 说话方法
    public void say() {
    }
}

子类:

  • 继承父类,通过 @Override 标记
  • 部分技能已经生疏,可以通过 @Deprecated 标记为过时,不建议使用,可能【翻车】
  • 声明 str变量但是没有使用,可以通过 @SuppressWarnings 注解,该注解有值,需要填入警告类型,unused就是未使用的意思,可以填写多个值
public class Student extends Person{

    // 重写注解
    @Override
    public void say() {
        System.out.println("我是学生");
    }
    // 过时注解,大学毕业之后篮球技能过时了不会了
    @Deprecated
    public String skill() {
        return "篮球";
    }

    // 排除警告注解,比如声明变量没有使用,编辑器就会报黄【警告提示】,添加该注解写上 unused,就是禁止【未使用】警告
    @SuppressWarnings({"unused","rawtypes"})
    public void warning() {
        String str;
    }
}

SuppressWarnings所有值:

作用

all

抑制所有警告

boxing

抑制装箱、拆箱操作时候的警告

cast

抑制映射相关的警告

dep-ann

抑制启用注释的警告

deprecation

抑制过期方法警告

fallthrough

抑制确在switch中缺失breaks的警告

finally

抑制finally模块没有返回的警告

hiding

抑制相对于隐藏变量的局部变量的警告

incomplete-switch

忽略不完整的 switch 语句

nls

忽略非nls格式的字符

null

忽略对null的操作

rawtypes

使用generics时忽略没有指定相应的类型

restriction

抑制禁止使用劝阻或禁止引用的警告

serial

忽略在serializable类中没有声明serialVersionUID变量

static-access

抑制不正确的静态访问方式警告

synthetic-access

抑制子类没有按最优方法访问内部类的警告

unchecked

抑制没有进行类型检查操作的警告

unqualified-field-access

抑制没有权限访问的域的警告

unused

抑制没被使用过的代码的警告

小贴士:合理使用 @SuppressWarnings 注解可以方便调试和运维

自定义注解

自定义注解并可以使用自定义注解是迈向更高一层的重要表现,我们要明白为什么要自定义注解,什么时候自定义什么样的注解比较合适才是重中之重。

自定义注解的使用场景

自定义注解步骤:

  • 通过 @interface 关键字创建一个注解类,注解类中可以包含方法也可以不包含方法,这个方法可以认为是注解的参数
  • 使用元注解对自定义注解的功能和范围进行限制

元注解:

元注解,就是定义注解的注解,也就是说这些元注解是的作用就是专门用来约束其它注解的注解。

元注解主要有五个:@Target,@Retention,@Documented,@Inherited,@Repeatable,其中 @Repeatable 是Java8新增的可重复注解。

@Target:用于指定注解的使用范围,通过 ElemenetType 枚举值决定

  • ElementType.TYPE:类、接口、注解、枚举
  • ElementType.FIELD:字段、枚举常量
  • ElementType.METHOD:方法
  • ElementType.PARAMETER:形式参数
  • ElementType.CONSTRUCTOR:构造方法
  • ElementType.LOCAL_VARIABLE:局部变量
  • ElementType.ANNOTATION_TYPE:注解
  • ElementType.PACKAGE:
  • ElementType.TYPE_PARAMETER:类型参数
  • ElementType.TYPE_USE:类型使用

@Retention:用于指定注解的保留策略,通过 RetentionPolicy 枚举值决定

  • RetentionPolicy.SOURCE:注解只保留在源码中,在编译时会被编译器丢弃
  • RetentionPolicy.CLASS:(默认的保留策略) 注解会被保留在Class文件中,但不会被加载到虚拟机中,运行时无法获得
  • RetentionPolicy.RUNTIME:注解会被保留在Class文件中,且会被加载到虚拟机中,可以在运行时通过反射获得

@Documented: 用于将注解包含在javadoc中,默认情况下,javadoc是不包括注解的,但如果使用了@Documented注解,则相关注解类型信息会被包含在生成的文档中,该注解没有参数

@Inherited: 允许子类继承父类中的注解

@Repeatable:Java8中引入的元注解,用于声明标记的注解为可重复类型注解,可以在同一个地方多次使用

发现所有的元注解上的@Target注解值为
ElementType.ANNOTATION_TYPE,意为该注解的作用范围在注解上

案例:

需求:自定义一个名为 MyClassAnnonation 类级别的无参注解,定义一个使用在属性上名为 Name 的带属性注解

代码实现:

MyClassAnnonation:

package com.stt.annontation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// ElementType.TYPE:这个注解可以使用在 类、接口、注解、枚举上
@Target(ElementType.TYPE)
// RetentionPolicy.RUNTIME:注解被保留在Class文件中,也会被加载到JVM中
@Retention(RetentionPolicy.RUNTIME)
public @interface MyClassAnnonation {

}

Name:

package com.stt.annontation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

// 可以使用在字段上
@Target(ElementType.FIELD)
public @interface Name {
    // value带小括号,是一个方法,有些文章写value是参数,我感觉是不准确的
    String value();
    // 因为注解中可以写成员变量【属性】
    String job = "程序猿";
}

Student类测试自定义注解:

package com.stt.annontation;

// 使用类注解
@MyClassAnnonation
public class Student {
    // 使用Name注解
    @Name(value = "添甄")
    private String name;

    public String getName() {
        return name;
    }
}

错误应用1:比如@Name注解我们限制只能使用在字段上,应用在方法上就会报错

错误应用2:比如@Name注解需要传参数,未传参数也会报错

深入一下:

这里介绍注解的三个小知识点:

  • 巧用value方法
  • 默认值
  • 基本数据类型不能使用包装类

value属性:当注解中有value方法时,使用注解赋值时,value方法可以不写,直接写具体的值即可

会不禁思考,是不是只有一个方法时就可以省略属性名直接填值,所以我们将value属性名改成name试一下:

发现提示找不到value方法,说明默认会根据value进行匹配,这里说巧用value方法

默认值:我们可以通过default给注解参数默认值,如给value默认值为添甄

建议大家在自定义注解时,合理分配默认值,spring、MyBatis等框架的注解每个属性都设置了默认值

基本数据类型不能使用包装类:比如声明一个年龄,Integer无效,只能使用int

以上就是自定义注解时的三个小知识点,大家知道,使用时避免踩坑。说到这不禁觉得我是真滴细细节的细

求解:问看到这里的小伙伴一个问题:注解里边的 value() 你们都称呼为什么?他正确的叫法应该是属性还是方法还是直接叫成员还是其他的什么呢?

注解应用

案例1:

需求:通过注解实现类似于Junit框架的@Test注解类似的功能,在程序运行时有@MyTest的注解的方法都执行

分析:

  • 创建名为MyTest的注解,限制使用在方法上
  • 因为程序运行时要检测哪些方法上有@MyTest注解,所以该注解的保留机制应该是运行时期仍然保存
  • 运行时通过反射扫描哪些方法上包含@MyTest注解,获取到之后逐一执行

代码实现:

注解:

package com.stt.annontation;

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 MyTest {
    
}

方法标记:

package com.stt.annontation;

import java.lang.reflect.Method;

public class MyTestMain {

    @MyTest
    public void test1() {
        System.out.println("==========test1==========");
    }

    @MyTest
    public void test2() {
        System.out.println("==========test2==========");
    }

    @MyTest
    public void test3() {
        System.out.println("==========test3==========");
    }

    public void test4() {
        System.out.println("==========test4==========");
    }

    public static void main(String[] args) {
        // 1、创建测试类对象,通过反射运行方法时需要使用
        MyTestMain testMain = new MyTestMain();
        // 2、获取类对象
        Class<MyTestMain> clazz = MyTestMain.class;
        // 3、获取所有方法
        Method[] methods = clazz.getMethods();
        // 4、遍历所有的方法
        for (Method method : methods) {
            // 5、判断哪些方法上有 @MyTest 注解
            if(method.isAnnotationPresent(MyTest.class)) {
                // 6、触发执行,指明通过(1)创建的对象调用
                try {
                    method.invoke(testMain);
                } catch (Exception e) {
                    System.out.println("运行出错!");
                }
            }
        }
    }
}

运行结果:

test4方法没有添加注解,所以就没有运行,发现运行顺序1、3、2,其实这个运行顺序是随机的,如果想要控制执行顺序,可以在注解中添加一个order值来实现,如下:

修改注解:添加order方法,值为int类型,默认值为0

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
    int order() default 0;
}

修改测试:在使用注解时添加顺序,在使用时获取到该注解,通过Stream根据order值排序之后再注意调用执行,就可以控制顺序

public class MyTestMain {

    @MyTest(order = 1)
    public void test1() {
        System.out.println("==========test1==========");
    }

    @MyTest(order = 2)
    public void test2() {
        System.out.println("==========test2==========");
    }

    @MyTest(order = 3)
    public void test3() {
        System.out.println("==========test3==========");
    }

    public void test4() {
        System.out.println("==========test4==========");
    }

    public static void main(String[] args) {
        // 1、创建测试类对象,通过反射运行方法时需要使用
        MyTestMain testMain = new MyTestMain();
        // 2、获取类对象
        Class<MyTestMain> clazz = MyTestMain.class;
        // 3、获取所有方法
        Method[] methods = clazz.getMethods();
        List<Method> invokeMethods = new ArrayList<>();
        // 4、遍历所有的方法
        for (Method method : methods) {
            // 5、判断哪些方法上有 @MyTest 注解
            if(method.isAnnotationPresent(MyTest.class)) {
                // 将方法添加到集合中
                invokeMethods.add(method);
            }
        }
        // 6、根据order值排序
        List<Method> methodList = invokeMethods.stream()
                .sorted(((o1, o2) -> o1.getAnnotation(MyTest.class).order() - o2.getAnnotation(MyTest.class).order()))
                .collect(Collectors.toList());
        // 7、根据排序后的方法执行,结果永远是1,2,3
        methodList.forEach(method -> {
            try {
                method.invoke(testMain);
            } catch (Exception e) {
                System.out.println("运行错误!");
            }
        });
    }
}

运行结果:

这样是不就非常哇塞!

Spring中的类似注解:

@Order:就可以控制配置类的加载顺序

@DependsOn:指定当前bean所依赖的beans。任何被指定依赖的bean都由Spring保证在当前bean之前创建

案例2:小高潮,这个案例一定要掌握,你的项目中绝对有这玩意

需求:在SpringBoot程序中,通过自定义注解 + aop实现日志记录功能,在请求接口时,自动将操作日志记录到数据库

分析:

  • 自定义Log注解,使用在类和方法上,并在运行时可以获取到
  • 通过aop配置获取哪些方法上定义了Log注解,并获取到相关信息
  • 将信息整合到日志实体类中,存储到MySQL
  • 为方便操作数据库和实体类,引入mybatis-plus和lombok
  • 贴出核心代码,springboot配置文件和service、mapper就不贴出了

依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.5</version>
    </dependency>
    <!-- SpringBoot 拦截器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>2.7.5</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.31</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>2.0.19</version>
    </dependency>
</dependencies>

数据库:

CREATE TABLE `sys_log` (
  `oper_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` int(11) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `operator_type` int(11) DEFAULT '0' COMMENT '操作类别( 0、用户端   1、平台管理端)',
  `oper_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `oper_location` varchar(255) DEFAULT '' COMMENT '操作地点',
  `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数',
  `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数',
  `status` int(11) DEFAULT '0' COMMENT '操作状态(1正常 0异常)',
  `error_msg` varchar(2000) DEFAULT '' COMMENT '错误消息',
  `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  PRIMARY KEY (`oper_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='操作日志记录';

实体类:

package com.stt.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;

@Data
public class SysLog {

    @TableId(value = "oper_id", type = IdType.AUTO)
    private Long operId;

    private String title;

    private Integer businessType;

    private String method;

    private String requestMethod;

    private String operName;

    private String operUrl;

    private String operIp;

    private String operLocation;

    private String operParam;

    private String jsonResult;

    private Integer status;

    private String errorMsg;

    private LocalDateTime operTime;
}

自定义注解:

package com.stt.annontation;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {

    /**
     * 日志标题
     */
    public String title() default "";

    /**
     * 操作类型
     */
    public BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
}

BusinessTypeEnum枚举:记录什么类型的操作

package com.stt.annontation;

public enum BusinessTypeEnum {
    /**
     * 其它
     */
    OTHER,

    /**
     * 新增
     */
    INSERT,

    /**
     * 修改
     */
    UPDATE,

    /**
     * 删除
     */
    DELETE,

    /**
     * 授权
     */
    GRANT,
}

AOP配置:

package com.stt.annontation;

import com.alibaba.fastjson2.JSON;
import com.stt.entity.SysLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Map;

@Component
@Aspect
public class LogConfig {

    private static final Logger log = LoggerFactory.getLogger(LogConfig.class);
    // 引入日志Service,用于存储数据进数据库
    @Autowired
    private ISysLogService sysLogService;

    // 配置织入点-xxx代表自定义注解的存放位置,如:com.stt.annontation.Log
    @Pointcut("@annotation(com.stt.annontation.Log)")
    public void logPointCut() {}

    /**
     * 处理完请求后执行此处代码
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult)
    {
        handleLog(joinPoint, controllerLog, null, jsonResult);
    }

    /**
     * 如果处理请求时出现异常,在抛出异常后执行此处代码
     *
     * @param joinPoint 切点
     * @param e 异常
     */
    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e)
    {
        handleLog(joinPoint, controllerLog, e, null);
    }

    /**
     * 日志处理
     */
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult)
    {
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            // 获取当前的用户
            String userName = "添甄";
            // *========数据库日志=========*//
            SysLog sysLog = new SysLog();
            sysLog.setStatus(1);
            // 请求的地址
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            assert requestAttributes != null;
            HttpServletRequest request = requestAttributes.getRequest();
            String ip = getIpAddr(request);
            sysLog.setOperIp(ip);
            sysLog.setOperUrl(request.getRequestURI());
            sysLog.setOperName(userName);

            if (e != null) {
                sysLog.setStatus(0);
                sysLog.setErrorMsg(e.getMessage().substring(0,2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            sysLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            sysLog.setRequestMethod(request.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, controllerLog, sysLog, jsonResult, request);
            // 保存数据库
            sysLog.setOperTime(LocalDateTime.now());
            // 将处理好的日至对象存储进数据库
            sysLogService.save(sysLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }
    }
    // 获取操作ip地址
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
    // 获取注解信息
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysLog sysLog, Object jsonResult, HttpServletRequest request) throws Exception {
        // 设置action动作
        sysLog.setBusinessType(log.businessType().ordinal());
        sysLog.setTitle(log.title());
    }

    private void setRequestValue(JoinPoint joinPoint, SysLog sysLog, HttpServletRequest request) throws Exception {
        String requestMethod = sysLog.getRequestMethod();
        if (RequestMethod.PUT.name().equals(requestMethod) || RequestMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            sysLog.setOperParam(params.substring(0,2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            sysLog.setOperParam(paramsMap.toString().substring(0,2000));
        }
    }
    // 解析方法参数信息
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (o != null && !isFilterObject(o)) {
                    try {
                        Object jsonObj = JSON.toJSON(o);
                        params.append(jsonObj.toString()).append(" ");
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return params.toString().trim();
    }

    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}

Controller:在需要记录日志的请求方法上添加Log注解

package com.stt.controller;

import com.stt.annontation.BusinessTypeEnum;
import com.stt.annontation.Log;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("test")
public class TestController {

    @Log(title = "查询列表",businessType = BusinessTypeEnum.OTHER)
    @GetMapping("list")
    public String list() {
        return "返回数据列表";
    }

    @Log(title = "添加数据",businessType = BusinessTypeEnum.INSERT)
    @PostMapping
    public String save() {
        return "数据添加成功";
    }

    @Log(title = "修改数据",businessType = BusinessTypeEnum.UPDATE)
    @PutMapping
    public String update() {
        return "修改数据成功";
    }

    @Log(title = "删除数据",businessType = BusinessTypeEnum.DELETE)
    @DeleteMapping
    public String delete() {
        return "删除数据成功";
    }
}

启动项目之后请求接口效果如下:

请求对应接口,就会添加一条日志进库,这就是通过自定义注解+aop实现的,把这个注解给公司小伙伴使用都直呼哇塞呢!

重复注解

自JDK5以来注解开始变得越来越流行,在各个框架中广泛应用,在上边我们也通过两个案例实现了自定义注解,并投入使用,不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。比如:

我有两组过滤方式,一个方法上不能写两个相同的注解

就有老程序员绕开这个机制,写一个大注解,里边包含一个小注解数组实现,如下

JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解

所以发现,其实和原本解决可重复注解的原理基本一致,将多个注解放到一个容器中,只不过Java8的这种方式将放入容器的动作隐藏起来了,编码使用注解时更方便,更直观,当我们通过反射获取注解时,发现获取到的其实是MyFilters注解

总结:

  • 如果需要两个相同的注解,有两种方式
  • 使用Java8之前的写法,声明一个大注解,包含需要重复的注解数组
  • 使用Java8之后的写法,仍然需要声明一个大注解,不过通过 @Repeatable 注解在需要重复的注解声明上指定大注解
  • 两者原理相同,通过反射获取的注解类型都是大注解类型

类型注解

类型注解在开发时使用较少,个人感觉不重要,如果你有使用的经验,还请指教。从Java 8开始,注解已经能应用于任何类型。这其中包括new操作符、类型转换、instanceof检查、泛型类型参数,以及implements和throws子句。这里,我们举了一个例子,这个例子中类型为String的变量name不能为空,所以我们使用了@NonNull对其进行注解

@NotNull String name = "添甄";

我们也可以在集合的泛型上使用类型注解

List<@NotNull String> names = new ArrayList();

利用好对类型的注解非常有利于我们对程序进行分析。这两个例子中,通过这一工具我们可以确保getName不返回空,names列表中的元素总是非空值。这会极大地帮助你减少代码中不期而至的错误,不过用的比较少,增加了代码量你懂的

总结:

  • 注解在Java编程中有举足轻重的作用,配合反射使用让编码实现业务变的更简洁,快速
  • 如果想要继续晋升技术,走向架构师,资深开发,注解是你绕不开的技能点,可以帮助团队封装更好用的功能,与第一点相辅相成
  • 注解的重要之处在于合理的自定义注解,合理的使用注解
  • 注解不是注释,新手小伙伴要分清楚,面试时千万不要说错
  • 注释的使用,框架中的注释作用,原理也是面试时的高频问题,多多练习,百炼成钢

admin

这个人很懒,什么都没留下

文章评论

您需要 登录 之后才可以评论