Java注解详解,自定义注解,利用反射解析注解

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 概要这篇文章将会带领你了解Java注解,注解的使用,注解的解析,利用反射解析运行时注解,相信有一定Java基础的小伙伴一定会接触大量的注解,Spring , Hibernate , MyBatis等著名的框架也有很多关于注解方面的应用,对于注解的使用...

概要

这篇文章将会带领你了解Java注解,注解的使用,注解的解析,利用反射解析运行时注解,相信有一定Java基础的小伙伴一定会接触大量的注解,Spring , Hibernate , MyBatis等著名的框架也有很多关于注解方面的应用,对于注解的使用小伙伴们应该一点都不陌生,那么如何自定义注解呢?学会自定义注解有什么好处呢?
下面就随笔者进入注解的世界

注解的作用

很多小伙伴在学习注解之前,都不知道学习注解到底可以用来干什么,可以给自身带来什么好处,那么在这里,笔者描述学习注解的几点好处

  • 用过Hibernate的小伙伴,应该使用过Hibernate的配置文件来描述ORM(数据库关系映射),Hibernate实现此功能除了写配置文件之外,当然还包含注解,那么第一个好处就是,注解可以替代配置文件完成对某些功能的描述,减少程序配置
  • 在没有配置文件的情况下,我们去观察代码,并不需要同时打开两个文件来观察这个字段到底对应数据库的哪个列,减少了程序繁琐性,使得代码更加清晰易懂
  • 目前市面上流行的框架基本上都包含了注解配置,那么针对于开源项目,我们在阅读项目代码时,不懂注解如何实现,真的是举步难坚,所以,学习注解也可以加强我们对开源项目源码的解读
  • 最重要的一点,会使用注解和会自定义注解完全是两码事,我的意思是,让别人可以高看你一眼(zhuang bi)

了解注解

注解是Java1.5,JDK5.0引用的技术,与类,接口,枚举处于同一层次 。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释 。

在Java中,自带了三种注解,这三种注解存在于java.lang包中,首先我们讲一讲这些注解

  • Override——它的作用是对覆盖超类中方法的方法进行标记,如果被标记的类并没有实际覆盖超类,则编译器会发出错误警告。
    很常见的一个注解,了解JavaOOP的小伙伴这个注解应该较为常用,告诉编译器,我这个方法是重写了父类方法,当然如果你的方法并没有实际重写父类方法时,那么编译器就会显示警告信息
  • Deprecated——它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息
    当一个方法名或者类名上面此注解之后,编译器会认为这个方法属于过期方法,明显的区别在于类名或者方法名上会画一道删除线,标识过期方法不影响方法的继续使用
  • SuppressWarnings——这个仅仅是告诉编译器忽略特定的警告信息,例如在泛型中使用原生数据类型
    例如我们在使用一些以Deprecated注解的方法时,编译器会提出黄线警告,那么只要在使用的地方加上@SuppressWarnings(“deprecation”)就可以使编译器忽略这个警告
    此注释常用的参数值有 : deprecation(忽略使用过时类或者方法),unchecked(忽略执行了未检查装换时警告) , fallthrough(忽略switch直接指向到下一个case块没有break警告),path(忽略类路径,源文件路径中有不存在路径时警告),serial(忽略可序列化类中没有serialVersionUID时的警告),finally(任何finally不能正常执行时的警告),all(以上所有)

自定义注解须知

首先,自定义注解我们必须了解四个元注解,什么是元注解?元注解指作用于注解之上的元数据或者元信息,简单通俗的讲,元注解就是注解的注解 .

  • Documented——指明拥有这个注解的元素可以被javadoc此类的工具文档化。这种类型应该用于注解那些影响客户使用带注释的元素声明的类型。如果一种声明使用Documented进行注解,这种类型的注解被作为被标注的程序成员的公共API 。
  • Inherited——指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。
  • Retention——指明在什么级别显示此注解
  • Target——指明该类型的注解可以注解的程序元素的范围

Documented与Inherited是典型的标识性注解,也就是说在注解内部并没有成员变量,没有成员变量的注解称为标识注解
Target主要的参数类型包括以下几种

  • ElementType.TYPE 用于类,接口,枚举但不能是注解
  • ElementType.FIELD 作用于字段,包含枚举值
  • ElementType.METHOD 作用于方法,不包含构造方法
  • ElementType.PARAMETER 作用于方法的参数
  • ElementType.CONSTRUCTOR 作用于构造方法
  • ElementType.LOCAL_VERIABLE 作用于本地变量或者catch语句
  • ElementType.ANNOTATION_TYPE 作用于注解
  • ElementType.PACKAGE 作用于包

Retention主要的参数类型包括以下几种

  • RetentionPolicy.SOURCE 注解存在于源代码中,编译时会被抛弃
  • RetentionPolicy.CLASS 注解会被编译到class文件中,但是JVM会忽略
  • RetentionPolicy.RUNTIME JVM会读取注解,同时会保存到class文件中

自定义注解

首先,我们来看一段自定义注解实现的代码

@Documented
@Inherited
//该注解可以作用于方法,类与接口
@Target({ElementType.METHOD,ElementType.TYPE})
//JVM会读取注解,所以利用反射可以获得注解
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    //定义成员变量
    //成员变量可以通过default指定默认值
    //如果成员变量不指定默认值的情况下
    //我们在引用接口时则必须给没有默认值的成员变量赋值
    String name() ; 
    int age() default 18 ;
}

@interface 用于定义注解接口,接口中只能定义成员变量,且定义的成员变量必须以()结尾,可以使用default关键字为成员变量指定默认值,如果不为成员变量指定默认值的情况,则必须在引用注解时,对没有默认值的成员变量进行赋值操作

注解的使用规则:

//@注解名(变量1=变量1值,变量2=变量2值,...)
//如果注解中拥有数组类型,假设是String类型,那么赋值方式可以如下
//@注解名(String数组名称={"tset1","test2","test3"})

@TestAnnotation(name="Taro")

//因为我们注解中的age()是拥有默认值的,所以这边可以不为age()赋值
//如果我们的注解中只有一个成员变量,且成员变量的名称为value()
//那么可以使用如下赋值方式
//@注解名(属性值)
//如果我们的注解中没有成员变量,那么此时的注解被称为标识注解

注解中可以定义的数据类型是受到限制的,除了基本类型之外,String,Enums,Annotation,Class还有这些类型的数组
如何使用我们刚刚定义的注解呢?刚刚的注解我们声明了是针对方法和类或者接口生效,那么我们来看看使用方法

@TestAnnotation(name="I'm class annotation")
public class Test {

    @TestAnnotation(name="I'm method annotation")
    public static void showAnnotation(){

    }

}

怎么样,是不是很easy呢?

解析注解

主要使用Java的反射原理实现对注解的解析,不太懂反射的小伙伴通过笔者的注释看起来也不会很难

PS :下一篇博文是对Java反射的详解

public static void main(String[] args) {
    //解析注解
    //获得我们需要解析注解的类
    Class<Test> clz = Test.class;

    //解析Class
    //由于我们的注解是可以给类使用的,所以首先判断类上面有没有我们的注解
    //判断类上面是否有注解
    boolean clzHasAnnotation = clz.isAnnotationPresent(TestAnnotation.class);
    if(clzHasAnnotation){
        //类存在我们定义的注解
        //获得注解
        TestAnnotation clzAnnotation = clz.getAnnotation(TestAnnotation.class);
        //输出注解在类上的属性
        System.out.println("name="+clzAnnotation.name()+"\tage="+clzAnnotation.age());
    }

    //解析Method
    //两种解析方法上的注解方式
    //获得类中所有方法
    Method[] methods = clz.getMethods();
    //第一种解析方法
    for(Method m : methods){
        //获得方法中是否含有我们的注解
        boolean methodHasAnnotation = m.isAnnotationPresent(TestAnnotation.class);
        if(methodHasAnnotation){
            //注解存在
            //获得注解
            TestAnnotation methodAnnotation = m.getAnnotation(TestAnnotation.class);

            System.out.println("name="+methodAnnotation.name()+"\tage="+methodAnnotation.age());
        }
    }
    //第二种解析方式
    for(Method m : methods){
        //获得方法上所有注解
        Annotation[] annotations = m.getAnnotations();
        //循环注解
        for(Annotation a : annotations){
            //如果是我们自定义的注解
            if(a instanceof TestAnnotation){
                //输出属性,需要强制装换类型
                System.out.println("name="+((TestAnnotation)a).name()+"\tage="+((TestAnnotation)a).age());
            }
        }
    }

}

最后得出结果,因为我们使用了两种解析Method注解的方式,所以最终会得到两个Method上面的字符串

img_22d431950df15e6f7d87b43d5c70de52.png
结果

例子:利用切面打印web日志


import java.lang.annotation.*;

/**
 * @description 环绕通知,增强controller接口响应
 * @author jack-cooper
 * @create 2018-05-31 11:22
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface ApiResult {
}

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @desc 环绕通知,增强controller接口响应
 * <pre>
 *          使用@Before在切入点开始处切入内容
 *
 *          使用@After在切入点结尾处切入内容
 *
 *          使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
 *
 *          使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
 *
 *          使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
 * </pre>
 *
 * <pre>
 *     https://blog.csdn.net/rainbow702/article/details/52185827
 * </pre>
 *
 * @author jack-cooper
 * @create 2018-05-31 11:28
 */
@Aspect
@Component
public class ApiResultAspect {

    private static final Logger logger = LoggerFactory.getLogger(ApiResultAspect.class);

    /**
     * 切入点
     */
    @Pointcut("@annotation(ApiResult)")
    public void apiResultPointCut(){}


    /**
     * 环绕通知,增强controller接口响应
     * @param joinPoint
     * @return
     */
    @Around(value = "apiResultPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        logger.info("====> 请求 URL : {} , HTTP_METHOD :{} , IP : {} , CLASS_METHOD : {} , ARGS : {}" ,
                request.getRequestURL().toString(),
                request.getMethod(),
                request.getRemoteAddr(),
                joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),
                JSON.toJSONString(joinPoint.getArgs())
        );
        //执行目标方法
        final Object proceed = joinPoint.proceed();
        //所需时间
        long proceedTime =  System.currentTimeMillis() - startTime ;
        JSONObject result = new JSONObject();
        result.put("data", proceed);
        result.put("proceedTime", proceedTime);
        result.put("code", "200");
        result.put("message", "成功");
        logger.info("====> 响应结果:{}",result.toJSONString());
        return result;
    }

}



资料:
https://blog.csdn.net/rainbow702/article/details/52185827

相关文章
|
13天前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
76 34
|
21天前
|
Java 编译器
Java 泛型详细解析
本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
32 2
Java 泛型详细解析
|
22天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
50 12
|
19天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
19天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
21天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
23天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
69 2
|
23天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
7天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
37 6
|
22天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####

推荐镜像

更多
下一篇
DataWorks