Springboot源码分析之番外篇

摘要:

大家都知道注解是实现了java.lang.annotation.Annotation接口,眼见为实,耳听为虚,有时候眼见也不一定是真实的。

    /**
     * The common interface extended by all annotation types.  Note that an
     * interface that manually extends this one does <i>not</i> define
     * an annotation type.  Also note that this interface does not itself
     * define an annotation type.
     *
     * More information about annotation types can be found in section 9.6 of
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * The {@link java.lang.reflect.AnnotatedElement} interface discusses
     * compatibility concerns when evolving an annotation type from being
     * non-repeatable to being repeatable.
     *
     * @author  Josh Bloch
     * @since   1.5
     */

元注解:

元注解 一般用于指定某个注解生命周期以及作用目标等信息。正如源码的注释一样,如果自定义的注解没有添加元注解就和平常的注释没有多大的区别,有了元注解就会让编译器将信息编译进字节码文件。

  • @Target

    @Target用于指明被修饰的注解最终可以作用的目标

ElementType 是一个枚举类型

    ElementType.TYPE:类,接口(包括注释类型)或枚举声明
    ElementType.FIELD:字段声明(包括枚举常量)
    ElementType.METHOD:方法声明
    ElementType.PARAMETER:正式参数声明
    ElementType.CONSTRUCTOR:构造器声明
    ElementType.LOCAL_VARIABLE:本地局部变量声明
    ElementType.ANNOTATION_TYPE:注解声明
    ElementType.PACKAGE:包声明
    ElementType.TYPE_PARAMETER:类型参数声明  jdk1.8新增
    ElementType.TYPE_USE:使用一种类型   jdk1.8新增
  • @Retention

@Retention用于指明当前注解的生命周期

RetentionPolicy是一个枚举类型

    RetentionPolicy.SOURCE:编译器将丢弃注释。
    RetentionPolicy.CLASS:注释将由编译器记录在类文件中,但在运行时不需要由VM保留。
    RetentionPolicy.RUNTIME:注释将由编译器记录在类文件中并且在运行时由VM保留,因此可以反射性地读取它们。
  • @Documented

@Documented 表示具有类型的注释将由javadoc记录和默认的类似工具。 这种类型应该用来注释注解影响注解使用的类型的声明客户的元素。 如果使用注解类型声明记录,其注解成为公共API的一部分注释元素。

  • @Inherited

@Inherited 表示自动继承注解类型。 如果注解类型上存在继承的元注解声明,用户查询类的注解类型声明,类声明没有此类型的注解,然后将自动查询该类的超类注解类型。 将重复此过程,直到为此注解找到类型,或类层次结构的顶部(对象)到达了。 如果没有超类具有此类型的注解,那么查询将指示有问题的类没有这样的注解。请注意,如果带注解,则此元注解类型无效type用于注解除类之外的任何内容。 另请注意这个元注解只会导致注解被继承来自超类; 已实现的接口上的注解没有效果。

注解实现

  • 如何自定义注解?
      package com.github.dqqzj.springboot.annotation;

      import org.springframework.core.annotation.AliasFor;
      import org.springframework.stereotype.Component;
      import org.springframework.stereotype.Service;

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

      /**
       * @author qinzhongjian
       * @date created in 2019-07-28 07:54
       * @description: TODO
       * @since JDK 1.8.0_212-b10
       */
      @Target(value = {ElementType.TYPE})
      @Retention(value = RetentionPolicy.RUNTIME)
      @Component
      public @interface Hello {
          @AliasFor(
                  annotation = Component.class
          )
          String value() default "hi" ;
      }
  • 如何获取注解元素信息?

如上图所示注解其实也是使用了代理,而且是JDK代理的。

注解原理分析

既然是运行时生成的代理类,我们就可以在启动类上添加System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true")或者

我们来分析一下生成的代理类

    package com.sun.proxy;

    import com.github.dqqzj.springboot.annotation.Hello;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;

    public final class $Proxy1 extends Proxy implements Hello {
        private static Method m1;
        private static Method m2;
        private static Method m4;
        private static Method m0;
        private static Method m3;

        public $Proxy1(InvocationHandler var1) throws  {
            super(var1);
        }

        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }

        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        public final Class annotationType() throws  {
            try {
                return (Class)super.h.invoke(this, m4, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        public final String value() throws  {
            try {
                return (String)super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }

        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m4 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("annotationType");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                m3 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("value");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

这里的InvocationHandler实际上是我们的AnnotationInvocationHandler,这里有一个memberValues,它是一个Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。接下来我调试代码给大家分享一下奥秘。

Hello hello = TestAnnotation.class.getAnnotation(Hello.class)这个部分的调试代码我会忽略直接调试

AnnotationInvocationHandler的相关方法。

注解奥秘的准备工作

  • 反编译注解文件,发现注解确实是实现了Annotation接口的

熟悉jdk规范的就会发现最底部的s#7RuntimeVisibleAnnotations这个是运行时可访问的注解信息,可供我们反射获取。

虚拟机规范定义了一系列和注解相关的属性表,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:

RuntimeVisibleAnnotations:运行时可见的注解
RuntimeInVisibleAnnotations:运行时不可见的注解
RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
AnnotationDefault:注解类元素的默认值`

注解奥秘调试

说明: 明明只有一个@Hello注解为什么左侧会出现2个代理类的原因就在这个地方,会多出一个代理类

    public final class $Proxy0 extends Proxy implements Retention {
       //省略无关代码.......
    }

反射注解工作原理:

  1. 我们通过键值对的形式可以为注解属性赋值,像这样:@Hello(value = "hi")
  2. 用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表
  3. 虚拟机把生命周期在 RUNTIME的注解取出并通过动态代理机制生成一个实现注解接口的代理类

如何动态修改代理值?

我们已经知道了注解的值是存放在Map<String, Object> memberValues中的,那么我们就可以使用反射获取并重新赋值。

原文地址:https://www.cnblogs.com/qinzj/p/11391774.html

时间: 2024-10-03 06:44:35

Springboot源码分析之番外篇的相关文章

UVW源码漫谈(番外篇)—— Emitter

这两天天气凉了,苏州这边连续好几天都是淅淅沥沥的下着小雨,今天天气还稍微好点.前两天早上起来突然就感冒了,当天就用了一卷纸,好在年轻扛得住,第二天就跟没事人似的.在这里提醒大家一下,天气凉了,睡凉席的可以收起来了,体质不太好的,也要适当加点衣服. 本来是想接着看源码的,早上起来又把Emitter鼓捣了一下,跟大家说说. emitter.hpp是可以从源码中剥离出来的,只要去除里面的libuv的东西就行了.Emitter其实就是实现的即时回调,没有异步事件处理的功能.但是我们有时候是需要用并发来提

MyVoix2.0.js 源码分析 WebSpeech与WebAudio篇

楔 子 随着移动互联网时代的开启,各种移动设备走进了我们的生活.无论是日常生活中人手一部的手机,还是夜跑者必备的各种智能腕带,亦或者是充满未来科技感的google glass云云,它们正渐渐改变着我们的生活习惯以及用户交互习惯.触摸屏取代了实体按键,Siri开始慢慢释放我们的双手,而leap motion之类的硬件更是让我们彻底不需要接触IT设备便能通过手势控制它们.在这样的大背景下,前端的交互将涉及越来越多元的交叉学科,我们正如十几年前人们经历Css的诞生一样,见证着一场带动整个行业乃至社会的

JVM源码分析之堆外内存完全解读

概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们大家接触最多的,我们在jvm参数里通常设置-Xmx来指定我们的堆的最大值,不过这还不是我们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我们在jvm参数里通常还会加一个参数-XX:MaxPermSize来指定持久代的最大值,那么我们认识的Java堆的最大值其实是-Xmx和-XX:MaxPermSize的总和,在分代算法下,新生代,老生代和持久代是连续的虚拟地址,因为它们是一起分配的,那么剩下的都可以认为是堆外内存

[SpringBoot]源码分析SpringBoot的异常处理机制

微信号:GitShare微信公众号:爱折腾的稻草如有问题或建议,请在公众号留言[1] 前续 为帮助广大SpringBoot用户达到"知其然,更需知其所以然"的境界,作者将通过SpringBoot系列文章全方位对SpringBoot2.0.0.RELEASE版本深入分解剖析,让您深刻的理解其内部工作原理. 正文 在SpringBoot启动时,会查找并加载所有可用的SpringBootExceptionReporter,其源码如下: //7 使用SpringFactoriesLoader在

Vue.js 源码分析(二十) 指令篇 v-once指令详解

数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值,例如:<span>Message: {{ msg }}</span>,以后每当msg属性发生了改变,插值处的内容都会自动更新. 可以给DOM节点添加一个v-once指令,这样模板只会在第一次更新时显示数据,此后再次更新该DOM里面引用的数据时,内容不会自动更新了,例如: <!DOCTYPE html> <html lang="en"> <head>

jdk源码分析红黑树——插入篇

红黑树是自平衡的排序树,自平衡的优点是减少遍历的节点,所以效率会高.如果是非平衡的二叉树,当顺序或逆序插入的时候,查找动作很可能会遍历n个节点 红黑树的规则很容易理解,但是维护这个规则难. 一.规则 1.每个节点要么是红色.要么是黑色 2.根节点一定是黑色 3.红色节点不可以连续出现(父节点.子节点不可同时为红) 4.从任意节点出发,到树底的所有路线,途径的黑节点数量必须相同 在修改红黑树的时候,切记要维护这个规则.一般默认插入红色节点(除非是root节点),插入后再进行旋转和颜色变换 二.旋转

Tornado源码分析之http服务器篇

一. Tornado是什么? Facebook发布了开源网络服务器框架Tornado,该平台基于Facebook刚刚收购的社交聚合网站FriendFeed的实时信息服务开发而来.Tornado由Python编写,是一款轻量级的Web服务器,同时又是一个开发框架.采用非阻塞I/O模型(epoll),主要是为了应对高并发 访问量而被开发出来,尤其适用于comet应用. 二. 为什么要阅读Tornado的源代码 Tornado由前google员工开发, 代码非常精练, 实现也很轻巧, 加上清晰的注释和

Springboot源码分析之EnableAspectJAutoProxy

摘要: Spring Framwork的两大核心技术就是IOC和AOP,AOP在Spring的产品线中有着大量的应用.如果说反射是你通向高级的基础,那么代理就是你站稳高级的底气.AOP的本质也就是大家所熟悉的CGLIB动态代理技术,在日常工作中想必或多或少都用过但是它背后的秘密值得我们去深思.本文主要从Spring AOP运行过程上,结合一定的源码整体上介绍Spring AOP的一个运行过程.知其然,知其所以然,才能更好的驾驭这门核心技术. @Target({ElementType.TYPE})

SpringBoot源码分析之---SpringBoot项目启动类SpringApplication浅析

源码版本说明 本文源码采用版本为SpringBoot 2.1.0BUILD,对应的SpringFramework 5.1.0.RC1 注意:本文只是从整体上梳理流程,不做具体深入分析 SpringBoot入口类 @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args