目录
反射
Reflection(反射) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
反射的作用
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
如何使用反射
要想解剖一个类,必须先要获取到该类的字节码文件对象(Class对象)。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
获取Class对象的方法
1)调用某个对象的getClass()方法:Object.getClass()
Person p = new Person();
Class cls = p.getClass();
2)使用类的class属性,即:类名.class
Class clazz = Person.class;
3)使用Class类中的forName()静态方法:Class.forName(“类的全路径”)
Class clazz = Class.forName("类的全路径");
反射的常见API
我这里直接总结一下,我们常常通过反射获得:构造方法、成员变量、成员方法:可以获取公有的、私有的
1)获得变量及方法
构造方法
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有 getDeclaredConstructor(int.class,String.class)
成员方法
getMethods()//获取所有可见的方法,包括继承的方法 getMethod(方法名,参数类型列表) getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法 getDeclaredMethod(方法名,int.class,String.class)
成员变量
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
2)反射新建实例和访问方法
新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(222,"韦小宝");//执行有参构造创建对象 clazz.getConstructor(int.class,String.class)//获取构造方法
访问成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值
f.get(实例);//访问指定实例变量的值
调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法
反射的实际使用
案例一
我们都知道String类的值是没办法改变的,为什么?我们看看String的源码就知道了。
/** The value is used for character storage. */
private final char value[];
String是用字符数组表示字符串的,然而它是被final修饰的,我们可以通过反射来改变他的值。
public class StringReflection {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//定义一个字符串赋值为test
String str = "test";
System.out.println(str);
//获得String的类对象
Class<String> cls = String.class;
//通过API拿到String的私有变量value
Field value = cls.getDeclaredField("value");
//设置访问权限为true
value.setAccessible(true);
//通过API改变字符串对象str的value值
value.set(str,"string is changed".toCharArray());
System.out.println(str);
}
}
输出结果:
test string is changed
案例二
模拟Spring的包扫描,扫描带有某个注解的类。
首先新建三个测试类,然后给其中一个标注上@Componet
注解
public class Test1 {
}
public class Test2 {
}
@Component
public class Test3 {
}
然后开始通过反射完成这一系列操作
public class ScanDemo {
public static void main(String[] args) throws Exception {
//获得当前类的路径
Class<ScanDemo> scanDemoClass = ScanDemo.class;
String path = scanDemoClass.getResource(".").getPath();
//获取当前类的包名
String packageName = scanDemoClass.getPackage().getName();
//创建一个路径为path的File对象,并得到所有的子文件
File[] files = new File(path).listFiles();
//定义被扫描的类的全类名
String className = "";
for (File file : files) {
//获取文件名字,然后将.class删除就得到了类的名字
String name = file.getName().substring(0, file.getName().indexOf('.'));
//将包名和类名拼接,就得到了全类名
className = packageName + "." + name;
//通过全类名生成Class对象
Class<?> cls = Class.forName(className);
//判断是否被Component注解标注,如果标注就交给Spring处理
if (cls.isAnnotationPresent(Component.class)) {
System.out.println("类"+cls.getName()+"将被spring容器自动管理");
Object o = cls.newInstance();
System.out.println("实例出来的对象:"+o);
}
}
}
}
注解
官方:注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。可以被其他程序(比如:编译器等)读取。
-
注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
-
注解用来修饰,类、方法、变量、参数、包。
-
注解不会对所修饰的代码产生直接的影响,只是一个修饰的东西。
Java内置注解
-
Override
此注解只使用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。
-
Deprecated
此注解可以用于修辞方法、属性、类、表示不鼓励程序员使用这样的元素,就是平时看到的画斜线的方法,表示不建议使用
-
SuppressWarnings
用来抑制编译时的警告信息,需要添加一个参数才能正确使用(all、unchecked等参数)
元注解
元注解是组成其他注解的注解,元注解是标记其他注解的注解,例如:注解@A被注解@B注解了,那么@B就是@A的元注解
@Target
指定注解使用的目标范围(类、方法、字段等)
参数有:
ElementType.TYPE :
用于描述类、接口(包括注解类型) 或enum声明。
ElementType.METHOD :
用于描述方法。
ElementType.CONSTRUCTOR :
用于描述构造器。
ElementType.FIELD :
成员变量、对象、属性(包括enum实例)。
ElementType.LOCAL_VARIABLE:
用于描述局部变量。
ElementType.PACKAGE :
用于描述包。
ElementType.PARAMETER :
用于描述参数。
ElementType.ANNOTATION_TYPE:
用于描述参数
@Retention
指定注解的生命周期
参数有:
RetentionPolicy.SOURCE
在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。
RetentionPolicy.CLASS
在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。
RetentionPolicy.RUNTIME
始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
@Document
指定被标注的注解会包含在javadoc中。
@Inherited
是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
自定义注解
定义的方式:和类、接口等定义方式差不多
使用@interface来定义注解,后面跟注解的名称
public @interface MyAnnotation {
}
自定义注解传参
模板是:
[public] 数据类型 参数名字 ( ) [default];
被[]括起来的表示可以省略
public @interface MyAnnotation {
String name();
int sort() default 1;
int[] array();
}
定义参数的时候,有点像接口的抽象方法,实际上不是的;定义参数的时候数据类型前面默认是有public的,只是被省略了;数据类型是使用该注解的时候,传入参数的数据类型;()只是一个语法,没有实际意义;
注意点:
-
数据类型只能是:基本数据类型、String、Class、枚举类型、注解类型、数组
-
如果只有一个参数,建议将参数名写成value,传参时就可以省略value;
-
双括号只是一个语法,没有实际意义;
-
default代表默认值,默认值要和参数类型一致;如果没有默认值,使用注解时必须传参。
注解结合反射使用
新建一个注解
@Target(ElementType.TYPE)//表示注解在类上
@Retention(RetentionPolicy.RUNTIME)//表示该注解的生命周期是运行时
public @interface TestAnnotation {
String value();
String name() default "defaultName";
}
基本的使用
@Test
public void test1(){
//获取java的类对象
Class<User> cls = User.class;
//获取所有注解了User类的注解
Annotation[] annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//判断是否被@TestAnnotation注解
boolean b = cls.isAnnotationPresent(TestAnnotation.class);
System.out.println(b);
//获得注解的值
TestAnnotation annotation = cls.getAnnotation(TestAnnotation.class);
//获取value属性的值
String value = annotation.value();
System.out.println("value = "+value);
//获取name属性的值
String name = annotation.name();
System.out.println("name = "+name);
}
运行结果: