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表达式 实现原理剖析
发表回复