关于面试技巧:基础篇深入解析JAVA注解机制

7次阅读

共计 12127 个字符,预计需要花费 31 分钟才能阅读完成。

java 实现注解的底层原理和概念

  • java 注解是 JDK1.5 引入的一种正文机制,java 语言的类、办法、变量、参数和包都能够被注解标注。和 Javadoc 不同,java 注解能够通过反射获取标注内容
  • 在编译器生成.class 文件时,注解能够被嵌入字节码中,而 jvm 也能够保留注解的内容,在运行时获取注解标注的内容信息
  • java 提供的注解能够分成两类:

<br/>作用在代码上的性能注解(局部):

注解名称 性能形容
@Override 查看该办法是否是重写办法。如果发现其父类,或者是援用的接口中并没有该办法时,会报编译谬误
@Deprecated 标记过期办法。如果应用该办法,会报编译正告
@SuppressWarnings 批示编译器去疏忽正文解中申明的正告
@FunctionalInterface java8 反对,标识一个匿名函数或函数式接口

<br/>让给程序员开发自定义注解的元注解(和关键字 @interface 配合应用的注解)

元注解名称 性能形容
@Retention 标识这个正文解怎么保留,是只在代码中,还是编入类文件中,或者是在运行时能够通过反射拜访
@Documented 标识这些注解是否蕴含在用户文档中
@Target 标识这个注解的作用范畴
@Inherited 标识注解可被继承类获取
@Repeatable 标识某注解能够在同一个申明上应用屡次
  • Annotation 是所有注解类的独特接口,不必显示实现。注解类应用 @interface 定义(代表它实现 Annotation 接口),搭配元注解应用,如下
package java.lang.annotation;
public interface Annotation {boolean equals(Object obj);
    int hashCode();
    String toString();
    // 返回定义的注解类型,你在代码申明的 @XXX, 相当于该类型的一实例
    Class<? extends Annotation> annotationType();}
----- 自定义示例 -----
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface ATest {String hello() default  "siting";
}

ATest 的字节码文件,编译器让自定义注解实现了 Annotation 接口

public abstract @interface com/ATest implements java/lang/annotation/Annotation {
  // compiled from: ATest.java
  @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)
  @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.TYPE})
  // access flags 0x401
  public abstract hello()Ljava/lang/String;
    default="siting"
}
  • 自定义注解类型时,个别须要用 @Retention 指定注解保留范畴 RetentionPolicy,@Target 指定应用范畴 ElementType。RetentionPolicy 保留范畴只能指定一个,ElementType 应用范畴能够指定多个
  • 注解信息怎么和代码关联在一起,java 所有事物都是类,注解也不例外,退出代码System.setProperty("sum.misc.ProxyGenerator.saveGeneratedFiles","true"); 可生成注解相应的代理类


在代码里定义的注解,会被 jvm 利用反射技术生成一个代理类,而后和被正文的代码(类,办法,属性等)关联起来

五种元注解详解

  • @Retention:指定注解信息保留阶段,有如下三种枚举抉择。只能选其一
public enum RetentionPolicy {
    /** 注解将被编译器抛弃,生成的 class 不蕴含注解信息 */
    SOURCE,
    /** 注解在 class 文件中可用,但会被 JVM 抛弃; 当注解未定义 Retention 值时,默认值是 CLASS */
    CLASS,
    /** 注解信息在运行期 (JVM) 保留(.class 也有),能够通过反射机制读取注解的信息,
      * 操作方法看 AnnotatedElement(所有被正文类的父类) */
    RUNTIME
}
  • @Documented:作用是通知 JavaDoc 工具,以后注解自身也要显示在 Java Doc 中(不罕用)
  • @Target:指定注解作用范畴,可指定多个
public enum ElementType {
    /** 适用范围:类、接口、注解类型,枚举类型 enum */
    TYPE,
    /** 作用于类属性 (includes enum constants) */
    FIELD,
    /** 作用于办法 */
    METHOD,
    /** 作用于参数申明 */
    PARAMETER,
    /** 作用于构造函数申明 */
    CONSTRUCTOR,
    /** 作用于局部变量申明 */
    LOCAL_VARIABLE,
    /** 作用于注解申明 */;,
    /** 作用于包申明 */
    PACKAGE,
    /** 作用于类型参数(泛型参数)申明 */
    TYPE_PARAMETER,
    /** 作用于应用类型的任意语句(不包含 class) */
    TYPE_USE
}

TYPE_PARAMETER 的用法示例

class D<@PTest T> {} // 注解 @PTest 作用于泛型 T 

TYPE_USE 的用法示例

// 用于父类或者接口 
class Test implements @Parent TestP {} 

// 用于构造函数
new @Test String("/usr/data")

// 用于强制转换和 instanceof 查看, 留神这些注解中用于内部工具
// 它们不会对类型转换或者 instanceof 的查看行为带来任何影响
String path=(@Test String)input;
if(input instanceof @Test String) // 注解不会影响

// 用于指定异样
public Person read() throws @Test IOException.

// 用于通配符绑定
List<@Test ? extends Data>
List<? extends @Test Data>

@Test String.class // 非法,不能标注 class
  • @Inherited:示意 以后注解会被注解类的子类继承 。即在子类 Class<T> 通过 getAnnotations() 可获取父类被 @Inherited 润饰的注解。而注解自身是不反对继承
@Inherited
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
public @interface ATest { }
---- 被 ATest 注解的父类 PTest----
@ATest
public class PTest{ }

---Main 是 PTest 的子类 ----
public class Main extends PTest {public static void main(String[] args){Annotation an = Main.class.getAnnotations()[0];
          //Main 能够拿到父类的注解 ATest,因为 ATest 被元注解 @Inherited 润饰
        System.out.println(an);
    }
}  
---result--
@com.ATest()  
  • @Repeatable:JDK1.8 新退出的,表明自定义的注解能够在同一个地位重复使用。在没有该注解前,是无奈在同一个类型上应用雷同的注解屡次
  //Java8 前无奈重复使用注解
  @FilterPath("/test/v2")
  @FilterPath("/test/v1")
  public class Test {}

应用动静代理机制解决注解

  • 反射机制获取注解信息
--- 作用于注解的注解 ----
@Inherited
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface AnnotationTest {String value() default "AnnotationTest";
}
------ 父类 -------
public class PTest {}
------ 被注解润饰的 package-info.java------
//package-info.java
@AnTest("com-package-info")
package com;
-------------
@AnnotationTest("AnnotationTest")
@Inherited
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE_USE,ElementType.PACKAGE,ElementType.FIELD,
        ElementType.TYPE_PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
public @interface AnTest  {String value() default  "siting";
}

运行示例

// 注解类
@AnTest("mainClass")
// 注解泛型参数                                       // 注解继承父类
public class Main<@AnTest("parameter") T > extends @AnTest("parent") PTest {@AnTest("constructor")  // 注解构造函数
    Main(){}
    // 注解字段域
    private @AnTest("name") String name;
    // 注解泛型字段域
    private @AnTest("value") T value;
    // 注解通配符
    private @AnTest("list")List<@AnTest("generic") ?>list;
    // 注解办法
    @AnTest("method")                       // 注解办法参数
    public String hello(@AnTest("methodParameter") String name)
            throws @AnTest("Exception") Exception { // 注解抛出异样
        // 注解局部变量, 当初运行时临时无奈获取(疏忽)@AnTest("result") String result;
        result = "siting";
        System.out.println(name);
        return  result;
    }

    public static void main(String[] args) throws Exception {Main<String>  main = new Main<> ();
        Class<Main<Object>> clazz = (Class<Main<Object>>) main.getClass();
        //class 的注解
        Annotation[] annotations = clazz.getAnnotations();
        AnTest testTmp = (AnTest) annotations[0];
        System.out.println("润饰 Main.class 注解 value:"+testTmp.value());
        // 结构器的注解
        Constructor<Main<Object>> constructor = (Constructor<Main<Object>>) clazz.getDeclaredConstructors()[0];
        testTmp = constructor.getAnnotation(AnTest.class);
        System.out.println("润饰结构器的注解 value:"+testTmp.value());
        // 继承父类的注解
        AnnotatedType annotatedType = clazz.getAnnotatedSuperclass();
        testTmp = annotatedType.getAnnotation(AnTest.class);
        System.out.println("润饰继承父类的注解 value:"+testTmp.value());
        // 注解的注解
        AnnotationTest annotationTest = testTmp.annotationType().getAnnotation(AnnotationTest.class);
        System.out.println("润饰注解的注解 AnnotationTest-value:"+annotationTest.value());
        // 泛型参数 T 的注解
        TypeVariable<Class<Main<Object>>> variable = clazz.getTypeParameters()[0];
        testTmp = variable.getAnnotation(AnTest.class);
        System.out.println("润饰泛型参数 T 注解 value:"+testTmp.value());
        // 一般字段域 的注解
        Field[] fields = clazz.getDeclaredFields();
        Field nameField = fields[0];
        testTmp = nameField.getAnnotation(AnTest.class);
        System.out.println("润饰一般字段域 name 注解 value:"+testTmp.value());
        // 泛型字段域 的注解
        Field valueField = fields[1];
        testTmp = valueField.getAnnotation(AnTest.class);
        System.out.println("润饰泛型字段 T 注解 value:"+testTmp.value());
        // 通配符字段域 的注解
        Field listField = fields[2];
        AnnotatedParameterizedType annotatedPType = (AnnotatedParameterizedType)listField.getAnnotatedType();
        testTmp = annotatedPType.getAnnotation(AnTest.class);
        System.out.println("润饰泛型注解 value:"+testTmp.value());
        // 通配符注解 的注解
        AnnotatedType[] annotatedTypes = annotatedPType.getAnnotatedActualTypeArguments();
        AnnotatedWildcardType annotatedWildcardType = (AnnotatedWildcardType) annotatedTypes[0];
        testTmp = annotatedWildcardType.getAnnotation(AnTest.class);
        System.out.println("润饰通配符注解 value:"+testTmp.value());
        // 办法的注解
        Method method = clazz.getDeclaredMethod("hello", String.class);
        annotatedType = method.getAnnotatedReturnType();
        testTmp = annotatedType.getAnnotation(AnTest.class);
        System.out.println("润饰办法的注解 value:"+testTmp.value());
        // 异样的注解
        annotatedTypes =  method.getAnnotatedExceptionTypes();
        testTmp = annotatedTypes[0].getAnnotation(AnTest.class);
        System.out.println("润饰办法抛出谬误的注解 value:"+testTmp.value());
        // 办法参数的注解
        annotatedTypes = method.getAnnotatedParameterTypes();
        testTmp = annotatedTypes[0].getAnnotation(AnTest.class);
        System.out.println("润饰办法参数注解 value:"+testTmp.value());
        // 包的注解
        Package p = Package.getPackage("com");
        testTmp = p.getAnnotation(AnTest.class);
        System.out.println("润饰 package 注解 value:"+testTmp.value());
        main.hello("hello");
    }
}

后果

润饰 Main.class 注解 value: mainClass
润饰结构器的注解 value: constructor
润饰继承父类的注解 value: parent
润饰注解的注解 AnnotationTest-value: AnnotationTest
润饰泛型参数 T 注解 value: parameter
润饰一般字段域 name 注解 value: name
润饰泛型字段 T 注解 value: value
润饰泛型注解 value: list
润饰通配符注解 value: generic
润饰办法的注解 value: method
润饰办法抛出谬误的注解 value: Exception
润饰办法参数注解 value: methodParameter
润饰 package 注解 value: com-package-info
hello

spring.AOP 和注解机制

spring.AOP 相当于 动静代理和注解机制 在 spring 框架的联合实现

  • 前要常识:面向切面编程 (AOP) 和动静代理

    • C 是面向过程编程的,java 则是面向对象编程,C++ 则是两者兼备,它们都是一种标准和思维。面向切面编程也一样,能够简略了解为:切面 编程专一的是部分代码,次要为某些点植入加强代码
    • 思考要部分退出加强代码,应用动静代理则是最好的实现。在被代理办法调用的前后,能够退出须要的加强性能;因而 spring 的切面编程是基于动静代理的
    • 切面的概念
概念 形容
告诉(Advice) 须要切入的减少代码逻辑被称为告诉
切点(Pointcut) 定义加强代码在何处执行
切面(Aspect) 切面是告诉和切点的汇合
连接点(JoinPoint) 在切点根底上,指定加强代码在切点执行的机会(在切点前,切点后,抛出异样后等)
指标(target) 被加强指标类
  • spring.aop 提供的切面注解
切面编程相干注解 性能形容
@Aspect 作用于类,申明以后办法类是加强代码的切面类
@Pointcut 作用于办法,指定须要被拦挡的其余办法。以后办法则作为拦挡汇合名应用
  • spring 的告诉注解其实是 告诉 + 指定连接点 组成,分五种(Before、After、After-returning、After-throwing、Around)
spring 告诉(Advice)注解 性能形容
@After 加强代码在 @Pointcut 指定的办法之后执行
@Before 加强代码在 @Pointcut 指定的办法之前执行
@AfterReturning 加强代码在 @Pointcut 指定的办法 return 返回之后执行
@Around 加强代码能够在被拦挡办法前后执行
@AfterThrowing 加强代码在 @Pointcut 指定的办法抛出异样之后执行
  • 在 spring 切面根底上,开发具备加强性能的自定义注解 (对注解进行切面)
新建 spring-web + aop 我的项目;新建如下 class
------ 指标 Controller ------
@RestController
public class TestController {
    @STAnnotation
    @RequestMapping("/hello")
    public String hello(){  return "hello@csc";}
}
------ Controller 注解 -------
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface STAnnotation {String value() default "注解 hello!";
}
------ Controller 切面 ------
@Aspect
@Component
public class ControllerAspect {// 切点: 注解指定关联 (对注解进行切面)
    @Pointcut("@annotation(STAnnotation)")
    public void controllerX(){}
    // 切点: 门路指定关联
    @Pointcut("execution(public * com.example.demo.TestController.*(..))")
    public void controllerY(){}
    // 在 controllerY()切点执行之前的连接点退出告诉
    @Before("controllerY()")
    public void yBefore(JoinPoint joinPoint) throws Throwable {
        // 能够退出加强代码
        MethodSignature methodS = (MethodSignature)joinPoint.getSignature();
        Method method = methodS.getMethod();
        if (method.isAnnotationPresent(STAnnotation.class)) {STAnnotation annotation = method.getAnnotation(STAnnotation.class);
            System.out.println(annotation.value());
        }
        System.out.println("controllerY");
    }
    //controllerX()切点执行之后的连接点退出告诉
    @After("controllerX()")
    public void xBefore(JoinPoint joinPoint) throws Throwable {
        // 能够退出加强代码
        System.out.println("controllerX");
    }
}

启动我的项目;执行curl http://127.0.0.1:8080/hello, 控制台输入如下

(题外)@FunctionalInterface 原理介绍

  • Lambda 表达式的构造:(…args)-> {… code}

    • lambda 在 python,C++ 都对应的定义,java 也有,lambda 个别由入参,处理过程组成。如果解决代码只有一行,中括号{} 能够省略。其实就是简化的函数。在 java 里,lambda 用函数式接口实现
  • @FunctionalInterface 作用于接口,接口能够承受 lambda 表达式作为右值,此类接口又叫函数式接口,其规定润饰的接口只能有一个形象的办法(不包扣静态方法和默认、公有办法)。attention: 不加 @FunctionalInterface 润饰,只定义一个形象办法的接口默认也是函数式接口
@FunctionalInterface
public interface Func {void hello(String name); }
---------------------
public static void main(String[] args) {Func func = (name) -> System.out.println(name);
        func.hello("siting");
    }

查看对应的 Main.class 字节码文件 javap.exe -p -v -c Main.class

Constant pool:
   #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
   // 常量值中后面的 #0 示意疏导办法取 BootstrapMethods 属性表的第 0 项(字节码在最上面)
   #2 = InvokeDynamic      #0:#33         // #0:hello:()Lcom/Func;
   #3 = String             #34            // siting
   #4 = InterfaceMethodref #35.#36        // com/Func.hello:(Ljava/lang/String;)V
   #5 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = Methodref          #39.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #7 = Class              #41            // com/Main
 .... // main 执行字节码
 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
           // 动静取得一个 CallSite 对象,该对象是一个外部类,实现了 Func 接口
         0: invokedynamic #2,  0              // InvokeDynamic #0:hello:()Lcom/Func;
         5: astore_1
         6: aload_1
         7: ldc           #3                  // String siting
         // 调用 CallSite 对象的 hello 办法
         9: invokeinterface #4,  2            // InterfaceMethod com/Func.hello:(Ljava/lang/String;)V
        14: return
.... //lambda 表达式 会编译出公有动态类        
private static void lambda$main$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
.... //lambda 表达式 会编译出一个对应的外部类         
SourceFile: "Main.java"
InnerClasses:
     public static final #59= #58 of #62; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #30 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lan
g/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #31 (Ljava/lang/String;)V
      // 调用 Main 办法里的 lambda$main$0 静态方法(真正执行 lambda 的逻辑的办法)#32 invokestatic com/Main.lambda$main$0:(Ljava/lang/String;)V
      #31 (Ljava/lang/String;)V

从下面的字节码可看出,1:lambda 表达式会被编译成一个公有静态方法和一个外部类;2:外部类实现了函数式接口,而实现办法会调用一个 Main.class 里一静态方法 3:静态方法 lambda$main$0 里是咱们本人写的代码逻辑。运行参数加上 -Djdk.internal.lambda.dumpProxyClasses 能够查看 lambda 对应外部类的具体信息

  • 罕用函数式接口
接口 形容
Predicate 判断:传入一个参数,返回一个 bool 后果,办法为 boolean test(T t)
Consumer 生产:传入一个参数,无返回值,办法为 void accept(T t)
Function 转化解决:传入一个参数,返回一个后果,办法为 R apply(T t)
Supplier 生产:无参数传入,返回一个后果,办法为 T get()
BiFunction 转化解决:传入两个个参数,返回一个后果,办法 R apply(T t, U u)
BinaryOperator 二元操作符,传入的两个参数的类型和返回类型雷同,继承 BiFunction

欢送指注释中谬误

关注公众号,一起交换

参考文章

  • Annotation 详解
  • Java 注解(Annotation)原理详解
  • Java Lambda 表达式 实现原理剖析

正文完
 0