【盘点Java框架常用技术】 Java注解

当我们阅读框架源码时,会看到其中包含着大量的注解,注解被广泛使用的原因在于,可以生成一些通用的“模板化”代码,来避免重复性的工作。使用注解的工作模式是,通过注解来描述我们的意图,然后用注解解析工具对注解进行解析。

【一】实验:自定义注解

首先我们通过 @interface关键字定义一个注解@Tree,定义注解时,需要定义两个内容:元注解,注解属性。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tree {

    String name() default "tree";
}

元注解:

可以看到我在上面还添加了@Target和@Retention,这个是元注解,也就是添加到注解之上的注解,元注解有5种:

  • @Retention:声明注解的的存活时间

    RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

    RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。

    RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

  • @Target:声明注解运用的地方

    ElementType.ANNOTATION_TYPE 应用到注解

    ElementType.CONSTRUCTOR 应用到构造方法

    ElementType.FIELD 应用到属性

    ElementType.LOCAL_VARIABLE 应用到局部变量

    ElementType.METHOD 应用到方法

    ElementType.PACKAGE 应用到包

    ElementType.PARAMETER 应用到方法内的参数

    ElementType.TYPE 应用到类型(类、接口、枚举)

  • @Documented:将注解中的元素包含到 Javadoc 中

  • @Inherited:使用了这个注解的子类,就继承了该注解

  • @Repeatable:Java1.8新增特性,应用于注解的值可以取多个的场景

注解属性:

可以类比为普通类中的成员变量,注解中只有成员变量没有方法,在使用该注解时为该属性赋值,也可以在定义时赋默认值。

【注解处理器】

在注解处理器中,我们可以为注解定义逻辑,例如在下面的例子中,就是调用AnnotationClient类中所有方法,把@Tree中name属性值注入到方法中。

public class TreeProcessor {
    public void parseMethod(final Class<?> clazz) throws Exception {
        final Object obj = clazz.getConstructor(new Class[] {}).newInstance(new Object[] {});
        final Method[] methods = clazz.getDeclaredMethods();
        for (final Method method : methods) {
            final Tree my = method.getAnnotation(Tree.class);
            if (null != my) {
                method.invoke(obj, my.name());
            }
        }
    }
}

接下来做一下测试:

public class AnnotationClient {

    @Tree
    public static void sayHello(final String name) {
        System.out.println("==>> Hi, " + name + " [sayHello]");
    }

    @Tree(name = "Someone")
    public static void sayHelloToSomeone(final String name) {
        System.out.println("==>> Hi, " + name + " [sayHelloToSomeone]");
    }

    public static void main(final String[] args) throws Exception {
        final TreeProcessor treeProcessor = new TreeProcessor();
        treeProcessor.parseMethod(AnnotationClient.class);
    }

}

【二】深入理解注解

如果换一个角度理解注解:它的本质是一个继承了Annotation接口的接口

当我们通过getAnnotation()方法获取一个注解的时候,JDK会通过动态代理生成注解的代理类$Proxy1,这个代理类代理了Tree中的所有方法,其实本质上还是通过反射来实现的,但是我们逐步递进的分析,先研究注解,下一步研究JDK动态代理,最后才能到达反射。

JAVA 中专门用于处理注解的 Handler:

sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler有如下几个属性:

private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;

其中memberValues在初始化后,key是注解的属性值,value是我们为属性的赋值,可能你已经忘了我们在程序中是怎么做的了 : @Tree(name = “Someone”)

在这里插入图片描述

所有动态代理类生成的方法,都会走这个invoke()方法。

而这个invoke方法做的事情,概括起来就是:通过方法名获取属性值。

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }
                    return var6;
                }
            }
        }
    }

从代码中我们可以看到,如果匹配为toString(),hashCode(),annotationType,会专门返回相应的实现,否则就返回memberValues对应key中相应的value,这样看起来还是比较清晰的。

于是,在分析完注解的实现后,我们同时也有了下一步的研究目标:JDK动态代理的实现。

【三】servlet 3.0引入的新注解

此篇文章的缘起,是由于在阅读SpringBoot源码时看到了@HandlesTypes注解,这是Tomcat的SCI机制用到的一个注解,被它标明的类需要作为参数值传入到onStartup方法。这是Servlet3.0新增的特性,所以在这里也列举一下Servlet3.0中新增的一些注解:

  • HandlesTypes –该注解用来表示一组传递给ServletContainerInitializer的应用类。

  • HttpConstraint – 该注解代表所有HTTP方法的应用请求的安全约束,和ServletSecurity注释中定义的HttpMethodConstraint安全约束不同。

  • HttpMethodConstraint – 指明不同类型请求的安全约束,和ServletSecurity 注解中描述HTTP协议方法类型的注释不同。

  • MultipartConfig –该注解标注在Servlet上面,表示该Servlet希望处理的请求的 MIME 类型是 multipart/form-data。

  • ServletSecurity 该注解标注在Servlet继承类上面,强制该HTTP协议请求遵循安全约束。

  • WebFilter – 该注解用来声明一个Server过滤器;

  • WebInitParam – 该注解用来声明Servlet或是过滤器的中的初始化参数,通常配合 @WebServlet 或者 @WebFilter 使用。

  • WebListener –该注解为Web应用程序上下文中不同类型的事件声明监听器。

  • WebServlet –该注解用来声明一个Servlet的配置。

发布了23 篇原创文章 · 获赞 3 · 访问量 1858

猜你喜欢

转载自blog.csdn.net/HoyingHan/article/details/98739343