反射的笔记:看注解之前必须掌握反射
注解:
注解的作用:
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类