作用在代码上的功能注解(部分):
注解名称功能描述@Override检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误@Deprecated标记过时方法。如果使用该方法,会报编译警告@SuppressWarnings指示编译器去忽略注释解中声明的警告@FunctionalInterfacejava8支持,标识一个匿名函数或函数式接口让给程序员开发自定义注解的元注解(和关键字@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利用反射技术生成一个代理类,然后和被注释的代码(类,方法,属性等)关联起来TYPE_PARAMETER的用法示例
class D<@PTest T> { } // 注解@PTest作用于泛型TTYPE_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 {}运行示例
//注解类 @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 hellospring.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,控制台输出如下
查看对应的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