java - 反射,注解
反射的笔记:看注解之前必须掌握反射
https://www.cnblogs.com/clamp7724/p/11655237.html
https://www.cnblogs.com/clamp7724/p/11658557.html
注解:
注解的作用:
1.作为注释使用 只是提示,没有实际意义
2.校验 提示代码错误,比如@override会校验下面的方法是不是正确重写了父类方法,如果有错会在编译前显示出来
3.携带一些信息 作为容器携带信息,类似变量?
注解的使用:
package AnnotationTest; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class AnnotationClass { //注解的使用格式: //@注解 //注解的位置: //类,构造方法,方法,属性的上面 //注解的作用: //1 作为注释使用 //2 校验 //比如下面Override就表示这个是重写的方法,如果方法格式不对,会编译错误(有些编译器里Override下面会有红线),让我们知道不符合重写规范。 @Override public String toString() { return super.toString(); } //3.携带一些信息 @SuppressWarnings({"unused","serial"}) String s = "aaa"; //SuppressWarning里面加String[],可以用来消除一些警告,不建议使用,因为警告一般都是表示有编译问题 // 比如加unused表示下面的变量没有被使用 // serial 继承Serializable版本不添加序列号 //deprecation 方法过时 // uncheck 不要检查 泛型问题不检测 (偶尔会用) // all 不警告所有问题 (最好别用) //自己的注解的使用
//注解结构: 具体看注解类定义中的注释
//@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) //能在属性,方法,构造方法前使用,还有其他元素,用ElementType.获取 //@Retention(RetentionPolicy.RUNTIME) //运行时使用,还有源代码时,编译时,用RetentionPolicy.获取 //@Inherited //注解能被继承 //@Documented //能被变为文档, 不常用 //public @interface MyAnnotation { //结构类似interface(接口) // public static final String s = "aaa"; // public abstract String test(); //} //如果自定义的注解中有方法,使用时需要给方法传值。 方法名 = 返回值类型的值 //注解作为容器把传的值输出给别人,方法需要自己赋值,属性定义注解时已经赋值了 //如果只有一个方法,方法名叫value可以省略方法名直接写值:比如java自带的注解SuppressWarnings,里面只有一个方法 String[] value(); @MyAnnotation(testInt = 1, testString = "aaa", testStringArray = {"aaa","bbb","ccc"}) private String str; public static void main(String[] args){ try { //注解的应用: 利用反射 Class c1 = AnnotationClass.class; //获取类 Field f1 = c1.getDeclaredField("str"); //根据属性名字获取有注解的属性 //获取注解,解析内容 MyAnnotation ma1 = f1.getAnnotation(MyAnnotation.class); int value1_1 = ma1.testInt(); String value1_2 = ma1.testString(); String[] value1_3 = ma1.testStringArray(); System.out.println("value1 = " + value1_1 + "value2 = " + value1_2 + "value3[0] = " + value1_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa //感觉作用像全局变量。。。 //完全利用反射进行解析内容 Class c2 = AnnotationClass.class; //获取类 Field f2 = c2.getDeclaredField("str"); //根据属性名字获取有注解的属性 Annotation ma2 = f2.getAnnotation(MyAnnotation.class); Class clazz2 = ma2.getClass(); Method m2_1 = clazz2.getDeclaredMethod("testInt"); //一般为了方便使用,注解一般内部都是只有一个String[] value 方法,这样这里就可以统一处理了 int value2_1 = (int) m2_1.invoke(ma2); Method m2_2 = clazz2.getDeclaredMethod("testString"); String value2_2 = (String) m2_2.invoke(ma2); Method m2_3 = clazz2.getDeclaredMethod("testStringArray"); String[] value2_3 = (String[]) m2_3.invoke(ma2); System.out.println("value1 = " + value2_1 + "value2 = " + value2_2 + "value3[0] = " + value2_3[0] ); //value1 = 1value2 = aaavalue3[0] = aaa } catch (Exception e) { e.printStackTrace(); } } }
自定义的一个注解
package AnnotationTest; import java.lang.annotation.*; //自定义注解: //注解中只能包含如下类型信息: //基本类型 //String //枚举 enum //注解 @ //以上4种类型的数组 [] //自定义注解使用 @interface, 需要添加元注解(java自带注解,用来说明注解) //元注解Target,说明当前注解可以使用的位置,里面是个enum @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) //属性,方法,构造方法 //元注解Retention,描述当前注解存在于什么作用域中 // 源文件.java --编译-- 字节码文件.class ---执行-- 内存 // 源文件:注解只用来作为注释 SOURCE // 字节码文件:编译时使用,可以检测 CLASS // 内存:用来执行 RUNTIME @Retention(RetentionPolicy.RUNTIME) //运行时使用 @Inherited //注解能被继承 @Documented //能被变为文档, 不常用 //注解内部结构与interface基本一致 public @interface MyAnnotation { //可以描述public static final的属性,不写修饰符也可以,默认为public static final public static final String s = "aaa"; //方法类型为 public abstract, 没有方法体,public abstract可以省略 //必须有返回值(interface中的方法可以用void,注解不行) //注解的方法主要为了动态传递信息 public abstract int testInt(); public abstract String testString(); public abstract String[] testStringArray(); }
//注解的应用和优点,看一个例子:看不懂看我之前写的反射的笔记。。。
模拟一个场景,两个公司合作开发项目,A写底层,B用A的底层生成对象来使用
客户突然有一天说要加属性,A把底层Class改了 (javaBean),属性个数不同了,构造方法也要变了。
按传统情况,B就不得不把所有上层中用到这种class的地方全改一遍!万一这个工程非常大,上百个class,工作量将会很可怕,而且容易遗漏。
这时候就要用到Spring的思想:IOC控制反转(A的创建交给别人,不用new 构造方法的方式创建), Di依赖注入(不是用new 对象,然后调用方法的方式赋值),这样A的class的结构改变了也不会影响到B
大幅降低耦合度,让A修改class对B的影响降到最低。
写一个注解用来存值传值
package AnnotationTest; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.CONSTRUCTOR}) @Retention(RetentionPolicy.RUNTIME) public @interface AnAnnotation { String[] value(); }
A的底层类: 这里用注解@的方法存值,这样可以方便开发的时候修改,测试一类的。
package AnnotationTest; public class ObjectTest { private String name; private Integer age; private String sex; @AnAnnotation({"一个名字","24", "男"}) public ObjectTest(){ } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
B用来生成A所写class的对象的方法
package AnnotationTest; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.text.Format; public class AnnotationTest { public static void main(String[] args){ AnnotationTest at = new AnnotationTest(); Object obj = at.createObject("AnnotationTest.ObjectTest"); ObjectTest objectTest = (ObjectTest)obj;//泛型 System.out.println("名字为:" + objectTest.getName()); System.out.println("年龄为:" + objectTest.getAge()); System.out.println("性别为:" + objectTest.getSex()); //那么问题来了。。。这么干的好处是什么呢! //可以试着把ObjectTest的Class类修改一下,比如把性别sex和相关的getSex,setSex方法去掉,修改注解@,下面这个生成对象的方法依旧可以使用,不用修改任何代码。 //如果在项目中,上百个class互相之间相互调用,一旦一个class发生改变(比如客户要求添加数据库新字段),传统方法就不得不把所有用到这个类的对象的地方全重新改一遍, // 而用反射就可以只改这个class而不用改其他地方了。 //这样可以减少类之间的耦合度,ObjectTest修改对AnnotationTest的影响大幅降低。 } //ioc //写一个方法,根据class名字利用构造方法上面的注解传值,直接生成对象 public Object createObject(String objectClassName){ Object obj = null; //用于返回生成的对象 try { Class c = Class.forName(objectClassName);//获得生成对象的class Constructor constructor = c.getConstructor();//获得构造函数 AnAnnotation annotation = (AnAnnotation)constructor.getAnnotation(AnAnnotation.class);//获取注解内的值 -> object的属性值 String[] values = annotation.value(); obj = constructor.newInstance(); //利用无参数构造方法生成object对象 Field[] fields = c.getDeclaredFields(); //获得class的所有属性 //给属性赋值 for(int i = 0; i < fields.length; i++){ System.out.println("--------------开始处理第" + i +"个属性---------------"); Class fieldType = fields[i].getType();//获取属性类型 //获取set方法名 set + 属性名首字母大写 String firstFieldName = fields[i].getName().substring(0,1).toUpperCase(); String lastFieldName = fields[i].getName().substring(1); StringBuilder setMethodName = new StringBuilder("set"); setMethodName.append(firstFieldName); setMethodName.append(lastFieldName); System.out.println("获得set方法:" + setMethodName); Method setMethod = c.getMethod(setMethodName.toString(), fieldType); //用set方法名和属性类型获得set方法 Constructor constructorField = fieldType.getConstructor(String.class); //获取set方法参数的类型的String为参数的构造函数,因为values[]中值都为String setMethod.invoke(obj,constructorField.newInstance(values[i])); //运行set方法,把values[]中对应的值传给obj中对应的方法,比如Integer类型的,就用 Integer("24")来把String变成Integer System.out.println("第一个属性赋值:" + fields[i].getName() + " = " + values[i] ); } } catch (Exception e) { e.printStackTrace(); } System.out.println("---------------Object赋值结束------------------"); return obj; //把创建赋值完毕的object返回 } }
输出结果:
--------------开始处理第0个属性---------------
获得set方法:setName
第一个属性赋值:name = 一个名字
--------------开始处理第1个属性---------------
获得set方法:setAge
第一个属性赋值:age = 24
--------------开始处理第2个属性---------------
获得set方法:setSex
第一个属性赋值:sex = 男
---------------Object赋值结束------------------
名字为:一个名字
年龄为:24
性别为:男
想体会一下反射的好处,可以修改一下ObjectTest类