注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
JDK中预定义的一些注解
@Override
:检测被该注解标注的方法是否是继承自父类(接口)的
@Deprecated
:该注解标注的方法表示该方法已过时
@SuppressWarnings
: 压制警告的注解,一般传递的参数为all,如:@SuppressWarnings(“all”)
自定义注解
格式:
public @interface 注解名称{ 属性列表; }
本质:注解本质上使用一个接口,该接口默认继承Annotation接口
属性:接口中的抽象方法
要求:
属性(抽象方法)的返回值类型需要是以下几种类型
定义了属性,在使用时需要给属性进行赋值:
如果定义属性时,使用default关键字给属性默认的初始值,则使用注解时,可以不进行属性的赋值
如果只有一个属性需要赋值,且属性的名称是value,则value可以省略,直接定义值即可。
数组赋值时,值使用{}包裹,如果数组汇总只有一个值,则{}可以省略
元注解:用于描述注解的注解
@Target:描述注解能够作用的位置,如:类、方法等 ElementType取值: TYPE:可以作用在类上 METHOD:可以作用在方法上 FIELD:可以作用在成员变量上 @Retention:描述注解被保留的阶段 RetentionPolicy取值 @Retention(RetentionPolicy.RUNTIME) (1)RUNTIME:一般自定义的注解取RUNTIME即可,表示表描述的注解作用于运行时阶段 @Documented:描述注解是否被抽取到api文档中 @Inherited:描述注解是否会被子类继承
注解的常见用途:
生成文档的注解,如@author,@param。
跟踪代码依赖性,实现替代配置文件功能,如spring mvc的注解。
编译时进行格式检查,如@override。
编译时进行代码生成补全,如lombok插件的@Data。
创建一个普通类:
package com.lhq.annotation; public class Person { private String name; private int age; ... public void eat(){ System.out.println("调用eat方法!"); } }
创建注解:
@Target(ElementType.TYPE) // 只作用在类上 @Retention(RetentionPolicy.RUNTIME) public @interface pro { String className(); String methodName(); }
使用一:
@pro(className = "com.lhq.annotation.Person",methodName = "eat") public class Demo { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 获取该类的字节码文件 Class demoClass = Demo.class; pro p = (pro)demoClass.getAnnotation(pro.class); // 这行代码相当于在内存中生产了一个注解接口的子类实现对象,相当于如下代码: /*class proImpl implements pro{ @Override public String className() { return "com.lhq.annotation.Person"; } @Override public String methodName() { return "eat"; } */ String className = p.className(); String methodName = p.methodName(); // 根据反射机制类名获取类对象 Class cls = Class.forName(className); // 根据类对象创建对象 Object o = cls.newInstance(); Method method = cls.getMethod(methodName); //调用invoke方法执行对象中的method方法 method.invoke(o); // output :调用eat方法! } }
使用二:配置文件
// yadi.properties // className=com.lhq.annotation.Person // methodName=eat public class Demo2 { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException { // 创建Properties对象 Properties pro = new Properties(); //通过本类的类加载器将properties文件中的内容加载到pro对象中 ClassLoader classLoader = Demo2.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("com/lhq/annotation/yadi.properties"); pro.load(is); //利用properties对象中getProperty方法利用key获取value String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); // 根据反射机制类名获取类对象 Class cls = Class.forName(className); // 根据类对象创建对象 Object o = cls.newInstance(); Method method = cls.getMethod(methodName); //调用invoke方法执行对象中的method方法 method.invoke(o); //output: 调用eat方法! } }
赋值注解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) @Inherited public @interface InitSex { enum SEX_TYPE {MAN,WOMAN}; SEX_TYPE sex() default SEX_TYPE.MAN; }
校验注解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) @Inherited public @interface ValidateAge { int min() default 18; int max() default 99; int value() default 20; }
**普通类:**使用注解
public class User { private String username; @ValidateAge(min = 20,max = 35,value = 22) private int age; @InitSex(sex = InitSex.SEX_TYPE.MAN) private String sex; ... }
测试:
public class Test { public static void main(String[] args) throws IllegalAccessException { User user = new User(); initUser(user); // 年龄为0,校验为通过情况 // output: 完成属性值的修改,修改值为:MAN boolean checkResult = checkUser(user); // output: 年龄值不符合条件 printResult(checkResult); // output: 校验未通过 // 重新设置年龄,校验通过情况 user.setAge(22); checkResult = checkUser(user); printResult(checkResult); // output: 校验通过 } static void initUser(User user) throws IllegalAccessException { // 获取User类中所有的属性(getFields无法获得private属性: ) // getFields: reflecting all the accessible public fields of the class // getDeclaredFields: objects reflecting all the fields declared by the class or interface Field[] fields = User.class.getDeclaredFields(); for(Field field:fields) { if(field.isAnnotationPresent(InitSex.class)) { InitSex init = field.getAnnotation(InitSex.class); field.setAccessible(true); // 设置属性性别 field.set(user,init.sex().toString()); System.out.println("完成属性值的修改,修改值为:" + init.sex().toString()); } } } static boolean checkUser(User user) throws IllegalAccessException { // 获取User类中所有的属性(getFields无法获得private属性) Field[] fields = User.class.getDeclaredFields(); boolean result = true; // 遍历所有属性 for (Field field : fields) { // 如果属性上有此注解,则进行赋值操作 if (field.isAnnotationPresent(ValidateAge.class)) { ValidateAge validateAge = field.getAnnotation(ValidateAge.class); field.setAccessible(true); int age = (Integer) field.get(user); if (age < validateAge.min() || age > validateAge.max()) { result = false; System.out.println("年龄值不符合条件"); } } } return result; } static void printResult(boolean checkResult) { if (checkResult) { System.out.println("校验通过"); } else { System.out.println("校验未通过"); } } }
public class PrivateSet { private String read; public String getReadOnly() { return read; } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { PrivateSet t = new PrivateSet(); Field f = t.getClass().getDeclaredField("read"); f.setAccessible(true); f.set(t,"write something..."); System.out.println(t.getReadOnly()); // output: write something... } }
invoke
源码如下:
public final class Method extends Executable { ... @CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { // 权限检查 if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } // ============================================ MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); // ============================================ } }
其实在Method
对象内部维护了一个接口MethodAccessor
,该接口有二个实现类,其中NativeMethodAccessorImpl
用来实现本地native
调用。而DelegatingMethodAccessorImpl
顾名思义,是一个委派实现类,该方法将invoke
操作委派给了native
方法。
public class ReflectSlow { public static void target(int i) { new Exception("#"+i).printStackTrace(); } public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { Class<?> kclass = Class.forName("com.lhq.annotation2.ReflectSlow"); // 首先Class.forName属于native方法,native方法就要经过语言执行层面转换。也就是java到c再到java的切换。 Method method = kclass.getMethod("target", int.class); // 而getMethod这个操作则会遍历该类的公有方法,如果没有命中,则还要去父类中查找。并且返回该 // method对象的一份copy。在查找成功之后,这份copy对象,则会占用堆空间,而无法进行内联优化, // 相反还会引起gc频率的提高。对性能也是一份影响。 method.invoke(null,0); } }
输出:
/** output: * java.lang.Exception: #0 * at com.lhq.annotation2.ReflectSlow.target(ReflectSlow.java:8) * at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) * at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) * at sun.reflect.DelegatingMethodAccessorImpl. invoke(DelegatingMethodAccessorImpl.java:43) * at java.lang.reflect.Method.invoke(Method.java:498) * at com.lhq.annotation2.ReflectSlow.main(ReflectSlow.java:14) */
换成循环多次method.invoke(null,i);
:
在程序调用第16次的时候,调用栈更改成了GeneratedMethodAccessor1
,而不再是native
方法。这是因为 Jvm 维护了一个阈值-Dsun.reflect.inflationThreshold
,默认为15。当反射native
调用超过15次就会触发 Jvm 的动态生成字节码,之后的操作,全部都会调用该动态实现。
动态实现与native
实现相比,动态实现的效率要快的多,这是因为native
的实现要在 Java 语言层面切换到 C 语言,然后再次切换到 Java 语言。但是,因为动态实现第一次生成的时候要生成字节码,而这个操作是比较耗时的。所以相比较起来单独一次调用的时候native
反而要比动态实现快的多。
总结一下就是 委派实现,用的少了就直接 native 。虽然 native 方法要切换,但是生成字节码更慢,invoke用的多了生成字节码就值的了,就生成字节码,省去了切换,就快了。二者都是:慢。
还有注解处理器,另开一篇吧。
一篇文章,全面掌握Java自定义注解(Annontation)
Java注解之自定义
Java 使用自定义注解替代配置文件案例(注解介绍)
反射为什么慢
反射调用为什么慢?细推反射细节!
本文同步发布于orzlinux.cn