
基础篇:深入解析JAVA注解机制
java实现注解的底层原理和概念
- java注解是JDK1.5引入的一种注释机制,java语言的类、方法、变量、参数和包都可以被注解标注。和Javadoc不同,java注解可以通过反射获取标注内容
- 在编译器生成.class文件时,注解可以被嵌入字节码中,而jvm也可以保留注解的内容,在运行时获取注解标注的内容信息
- java提供的注解可以分成两类:
作用在代码上的功能注解(部分):
注解名称 | 功能描述 |
@Override | 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误 |
@Deprecated | 标记过时方法。如果使用该方法,会报编译警告 |
@SuppressWarnings | 指示编译器去忽略注释解中声明的警告 |
@FunctionalInterface | java8支持,标识一个匿名函数或函数式接口 |
让给程序员开发自定义注解的元注解(和关键字@interface配合使用的注解)
元注解名称 | 功能描述 |
@Retention | 标识这个注释解怎么保存,是只在代码中,还是编入类文件中,或者是在运行时可以通过反射访问 |
@Documented | 标识这些注解是否包含在用户文档中 |
@Target | 标识这个注解的作用范围 |
@Inherited | 标识注解可被继承类获取 |
@Repeatable | 标识某注解可以在同一个声明上使用多次 |
- Annotation是所有注解类的共同接口,不用显示实现。注解类使用@interface定义(代表它实现Annotation接口),搭配元注解使用,如下
ATest的字节码文件,编译器让自定义注解实现了Annotation接口
- 自定义注解类型时,一般需要用@Retention指定注解保留范围RetentionPolicy,@Target指定使用范围ElementType。RetentionPolicy保留范围只能指定一个,ElementType使用范围可以指定多个
- 注解信息怎么和代码关联在一起,java所有事物都是类,注解也不例外,加入代码
System.setProperty("sum.misc.ProxyGenerator.saveGeneratedFiles","true");
可生成注解相应的代理类
- 在代码里定义的注解,会被jvm利用反射技术生成一个代理类,然后和被注释的代码(类,方法,属性等)关联起来
五种元注解详解
- @Retention:指定注解信息保留阶段,有如下三种枚举选择。只能选其一
- @Documented:作用是告诉JavaDoc工具,当前注解本身也要显示在Java Doc中(不常用)
- @Target:指定注解作用范围,可指定多个
TYPE_PARAMETER的用法示例
TYPE_USE的用法示例
- @Inherited:表示当前注解会被注解类的子类继承。即在子类Class<T>通过getAnnotations()可获取父类被@Inherited修饰的注解。而注解本身是不支持继承
- @Repeatable:JDK1.8新加入的,表明自定义的注解可以在同一个位置重复使用。在没有该注解前,是无法在同一个类型上使用相同的注解多次
使用动态代理机制处理注解
- 反射机制获取注解信息
运行示例
结果
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切面基础上,开发具有增强功能的自定义注解(对注解进行切面)
启动项目;执行curl http://127.0.0.1:8080/hello
,控制台输出如下
(题外)@FunctionalInterface原理介绍
- Lambda 表达式的结构:(...args)-> { ... code }
○ lambda在python,C++都对应的定义,java也有,lambda一般由入参,处理过程组成。如果处理代码只有一行,中括号{} 可以省略。其实就是简化的函数。在java里,lambda用函数式接口实现
- @FunctionalInterface作用于接口,接口可以接受lambda表达式作为右值,此类接口又叫函数式接口,其规定修饰的接口只能有一个抽象的方法(不包括静态方法和默认、私有方法)。attention:不加@FunctionalInterface修饰,只定义一个抽象方法的接口默认也是函数式接口
查看对应的Main.class字节码文件 javap.exe -p -v -c Main.class
从上面的字节码可看出,1:lambda表达式会被编译成一个私有静态方法和一个内部类;2:内部类实现了函数式接口,而实现方法会调用一个Main.class里一静态方法 3:静态方法lambda0里是我们自己写的代码逻辑。运行参数加上-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详解[1]
- Java注解(Annotation)原理详解[2]
- Java Lambda表达式 实现原理分析[3]
文章转载自公众号:潜行前行
