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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 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

相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
7天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
52 6
|
14天前
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
5天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
Java的集合框架中,Set接口以其“无重复”特性著称。本文解析了Set的实现原理,包括HashSet和TreeSet的不同数据结构和算法,以及如何通过示例代码实现最佳实践。选择合适的Set实现类和正确实现自定义对象的hashCode()和equals()方法是关键。
18 4
|
8天前
|
Java 编译器 数据库连接
Java中的异常处理机制深度解析####
本文深入探讨了Java编程语言中异常处理机制的核心原理、类型及其最佳实践,旨在帮助开发者更好地理解和应用这一关键特性。通过实例分析,揭示了try-catch-finally结构的重要性,以及如何利用自定义异常提升代码的健壮性和可读性。文章还讨论了异常处理在大型项目中的最佳实践,为提高软件质量提供指导。 ####
|
12天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
11天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
32 2
|
11天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
17天前
|
算法 Java 数据库连接
Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性
本文详细介绍了Java连接池技术,从基础概念出发,解析了连接池的工作原理及其重要性。连接池通过复用数据库连接,显著提升了应用的性能和稳定性。文章还展示了使用HikariCP连接池的示例代码,帮助读者更好地理解和应用这一技术。
31 1
|
11天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
12 0

推荐镜像

更多