反射定义
Java反射机制是指在运行状态中
对于任意一个类,都能知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任何一个方法和属性;
这样动态获取新的以及动态调用对象方法的功能就叫做反射。
主要的几个类
类 | 用途 |
java.lang.Class | 编译后的class文件的对象 |
java.lang.reflect.Constructor | 构造方法 |
java.lang.reflect.Field | 类的成员变量(属性) |
java.lang.reflect.Method | 类的成员方法 |
java.lang.reflect.Modifier | 方法类型 |
java.lang.annotation.Annotation | 类的注解 |
1. 获取Class
//获取类的三种方法:
Class c = Class.forName("java.lang.String");//这里一定要用完整的包名
Class c1 = String.class;
String str = new String();
Class c2 = str.getClass();
这里以String类为例,获取的c,c1以及c2都是相等的,一般用第一种写法。
2. Field-获取类的属性(成员变量) API
A Field provides information about, and dynamic access to, a single field of a class or an interface. The reflected field may be a class (static) field or an instance field.
A Field permits widening conversions to occur during a get or set access operation, but throws an IllegalArgumentException if a narrowing conversion would occur.
Class.getFields() | 返回一个包含某些 Field 对象的数组,该数组包含此 Class 对象所表示的类或接口的所有可访问公共字段 |
Class.getField(String) | 返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段 |
Class.getDeclaredFields() | 返回 Field 对象的一个数组,该数组包含此 Class 对象所表示的类或接口所声明的所有字段(包括私有成员) |
Class.getDeclaredField(String) | 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段(包括私有成员) |
For example:
//获取类的属性(成员变量)
Field[] fields = c.getDeclaredFields();
StringBuffer sb = new StringBuffer();
//遍历每一个属性
for(Field field : fields){
sb.append("\t"); //空格
sb.append(Modifier.toString(field.getModifiers()) + " ");//获得属性的修饰符
sb.append(field.getType().getSimpleName() + " ");//属性的类型的名字
sb.append(field.getName() + ";\n");//属性的名字 + 回车
}
3. Method-获取类的方法 API
A Method provides information about, and access to, a single method on a class or interface. The reflected method may be a class method or an instance method (including an abstract method).
A Method permits widening conversions to occur when matching the actual parameters to invoke with the underlying method’s formal parameters, but it throws an IllegalArgumentException if a narrowing conversion would occur.
Class.getMethods() |
Class.getMethod(String, Class[]) |
Class.getDeclaredMethods() |
Class.getDeclaredMethod(String, Class[]) |
For example:
//获取所有的方法
Method[] ms = c.getDeclaredMethods();
StringBuffer sb = new StringBuffer();
for(Method method : ms){
sb.append("\t"); //空格
sb.append(Modifier.toString(method.getModifiers()) + " ");
sb.append(method.getReturnType().getSimpleName() + " ");
sb.append(method.getName() + ";\n");
}
注解
Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。
Annotation是被动的元数据,永远不会有主动行为
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface Bind {
int value() default 1;
boolean canBeNull() default false;
}
用 @interface 表明这是一个注解,Annotation 只有成员变量,没有方法。Annotation 的成员变量在 Annotation 定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
元注解
可以看到自定义注解里也会有注解存在,给自定义注解使用的注解就是元注解。
@Rentention
用来标记自定义注解的有效范围,他的取值有几下三种:
RetentionPolicy.SOURCE | 只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override |
RetentionPolicy.CLASS | 默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的 |
RetentionPolicy.RUNTIME | 注解不仅 能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的 |
@Target
@Target指定Annotation 用于修饰哪些程序元素。
@Target也包含一个名为”value“的成员变量,该value成员变量类型为ElementType[ ],ElementType为枚举类型,值有如下几个:
- ElementType.TYPE:能修饰类、接口或枚举类型
- ElementType.FIELD:能修饰成员变量
- ElementType.METHOD:能修饰方法
- ElementType.PARAMETER:能修饰参数
- ElementType.CONSTRUCTOR:能修饰构造器
- ElementType.LOCAL_VARIABLE:能修饰局部变量
- ElementType.ANNOTATION_TYPE:能修饰注解
- ElementType.PACKAGE:能修饰包
@Documented
使用了 @Documented 的可以再 javadoc 中找到
@Interited
使用了@Interited表示注解里的内容可以被子类继承,比如父类中某个成员使用了上述@From(value),From中的value能给子类使用到。
反射 & 注解 的使用
1. 属性值使用注解
- 自定义两个注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindPort {
String value() default "8080";
}
指定BindPort 可以保留到运行时,并且可以修饰成员变量,包含一个成员变量默认值为”8080“。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindAddress {
String value() default "127.0.0.0";
}
这个和上面类似,只是默认值为”127.0.0.0”。
- 修饰属性:
@BindAddress()
private String address;
@BindPort()
private String port;
public void printInfo() {
System.out.println("info is " + address + ":" + port);
}
这里我们将 address 和 port 两个变量分别用这里定义的注解进行修饰,由于我们在定义注解时有默认值,所以这里的注解可以不写参数。
- 使用反射获取注解信息
前面已经说了,Annotation是被动的元数据,永远不会有主动行为,所以我们需要通过使用反射,才能让我们的注解产生意义。
通过反射可以获取Class的所有属性和方法,因此获取注解信息也不在话下。我们看代码:
public void reflectTest() {
try{
//获取类
Class c = Class.forName("com.example.test.TestClass"); //这里一定要用完整的包名
//实例化一个TestClass对象
TestClass tc = (TestClass) c.newInstance();
//获取类的属性(成员变量)
Field[] fields = c.getDeclaredFields();
//遍历每一个属性
for(Field field : fields){
if(field.isAnnotationPresent(TestClass.BindPort.class)){
TestClass.BindPort port = field.getAnnotation(TestClass.BindPort.class);
field.setAccessible(true);
field.set(tc,port.value());
}
if(field.isAnnotationPresent(TestClass.BindAddress.class)){
TestClass.BindAddress address = field.getAnnotation(TestClass.BindAddress.class);
field.setAccessible(true);
field.set(tc,address.value());
}
}
tc.printInfo();
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}
}
程序得到输出:info is 127.0.0.0:8080
- 代码逻辑
首先遍历循环所有的属性,如果当前属性被指定的注解所修饰,那么就将当前属性的值修改为注解中成员变量的值。
上面的代码中,找到被BindPort修饰的属性,然后将BindPort中value的值赋给该属性。
这里setAccessible(true)的使用时因为,我们在声明port变量时,其类型为private,为了确保可以访问这个变量,防止程序出现异常。
理论上来说,这样做是不安全的,不符合面向对象的思想,这里只是为了说明注解和反射举例。但是,你也会发现,反射给我们提供了一种在运行时改变对象的方法
- 继续修改
@BindAddress("http://www.google.com.cn")
private String address;
@BindPort("8888")
private String port;
- 再次运行结果:info is http://www.google.com.cn:8888
这时候由于我们在给成员变量设定注解时,写了参数,反射时也取到了相应的值。
2. 方法使用注解
- 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BindGet {
String value() default "";
}
- 修饰方法
@BindGet("mike")
void getHttp(String param) {
String url = "http://www.baidu.com/?username" + param;
System.err.println("get------>" + url);
}
- 使用反射
public void reflectTest() {
try{
//获取类
Class c = Class.forName("com.example.test.TestClass"); //这里一定要用完整的包名
//实例化一个TestClass对象
TestClass tc = (TestClass) c.newInstance();
//获取类的方法
Method[] ms = c.getDeclaredMethods();
for(Method method : ms){
if(method.isAnnotationPresent(TestClass.BindGet.class)){
TestClass.BindGet bindGet = method.getAnnotation(TestClass.BindGet.class);
String param = bindGet.value();
method.invoke(tc,param);
}
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}catch (InvocationTargetException e){
e.printStackTrace();
}
}
这里的逻辑和对属性的解析相似,依旧是判断当前方法是否被指定的注解(BindGet)所修饰,如果是的话,就使用注解中的参数作为当前方法的参数去调用他自己。
- 运行结果:get——>http://www.baidu.com/?usernamemike
这里我们就可以通过注解动态的实现username参数的修改,甚至getHttp方法整个http url地址的修改。
(假设我们这里的getHttp方法是做网络请求)