Annotation

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

2018-05-02  本文已影响108人  jackcooper

概要

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

注解的作用

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

了解注解

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

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

自定义注解须知

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

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

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

自定义注解

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

@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上面的字符串

结果

例子:利用切面打印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

上一篇 下一篇

猜你喜欢

热点阅读