**
反射
**
在介绍反射之前,我先简单说一下Java代码的三个阶段与一些必要的概念。
Java代码的三个阶段:
1.Source源代码阶段(此时的代码还没有进入内存)
2.Class类对象阶段(通过类加载器把class文件加载入内存,此时会封装类的各个组成部分)
3.Runtime运行时阶段(创建对象)
框架:半成品的软件。在框架的基础上进行软件开发,简化编码
反射:框架设计的灵魂,将类的各个组成部分封装为其他对象(在内存中封装)
所有的成员变量封装为Field[] fields
所有的构造方法封装为Constructor[] constructors
所有的成员方法封装成Method[] methods
Java的反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用其任意一个方法和属性。总而言之,反射就是将类的各个组成部分封装为其他对象
通过上面的描述,我们来获取任意的一个类(这里以一个Person类作为示例)
Person类:
package Reflection;
public class Person {
private String name;
private int age;
public String a;
public String b;
public Person()
{
}
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name=name;
}
public int getAge()
{
System.out.println("getAge方法");
return age;
}
public void setAge(int age)
{
this.age=age;
}
@Override
public String toString()
{
return name+" "+age;
}
}
获取一个类的Class对象(三种方式):
//获取Class对象的第一种方式,多数用于配置文件,读取文件,加载类
Class<?> cls1=Class.forName("Reflection.Person");//参数传递全类名(包名.类名)
System.out.println(cls1);
//获取Class对象的第二种方式,多用于参数的传递
Class<?> cls2=Person.class;
System.out.println(cls2);
//获取Class对象的第三种方式,多用于通过对象获取
Person p=new Person();
Class<?> cls3=p.getClass();
System.out.println(cls3);
虽然有三种方式来获取Class对象,但不管用第几种方式获取的Class对象都是同一个,Class字节码文件只会被加载一个
获取了Class对象,我们来看一下Class对象的功能
Class对象的功能:
1.获取成员变量们
2.获取构造方法们
3.获取成员方法们
4.获取类名
1.获取成员变量们:
Person p1=new Person();
Class<? extends Person> personClass=p1.getClass();
Field[] fields=personClass.getFields();//获取所有public修饰的成员变量
for(Field f:fields)
{
System.out.println(f);
}
System.out.println();
Field a=personClass.getField("a");//获取指定名称的public成员变量
System.out.println(a);
System.out.println();
Field[] declaredFields=personClass.getDeclaredFields();//获取所有的成员变量
for(Field f:declaredFields)
{
System.out.println(f);
}
System.out.println();
Field declaredField=personClass.getDeclaredField("name");//获取指定的成员变量
System.out.println(declaredField);
System.out.println();
a.set(p1,"小明");//获取成员变量后,set方法可以设置值
System.out.println(a.get(p1));//返回成员变量的值
System.out.println();
declaredField.setAccessible(true);//正常情况下不可以set于get私有成员变量,但这句话可以无视修饰符,称为暴力反射
declaredField.set(p1, "gfx");
System.out.println(declaredField.get(p1));
结果
public java.lang.String Reflection.Person.a
public java.lang.String Reflection.Person.b
public java.lang.String Reflection.Person.a
private java.lang.String Reflection.Person.name
private int Reflection.Person.age
public java.lang.String Reflection.Person.a
public java.lang.String Reflection.Person.b
private java.lang.String Reflection.Person.name
小明
gfx
2.获取构造方法们:
Constructor constructor=personClass.getConstructor(String.class,int.class);//获取public的有参数的构造方法
System.out.println(constructor);
Object p2=constructor.newInstance("小红",18);//用来创建对象
System.out.println(p2);
//还有getConstructor(),getDeclaredConstructor(.....),getDeclaredConstructor()三种,不多赘述
结果
public Reflection.Person(java.lang.String,int)
小红 18
3.获取成员方法
Method[] methods=personClass.getDeclaredMethods();//获取所有自己声明的的成员方法
for(Method m:methods)
{
System.out.println(m);
}
methods[3].invoke(p2);//某method.invoke可以调用某成员方法
结果
public java.lang.String Reflection.Person.toString()
public java.lang.String Reflection.Person.getName()
public void Reflection.Person.setName(java.lang.String)
public int Reflection.Person.getAge()
public void Reflection.Person.setAge(int)
getAge方法
因为通过Class对象获取构造方法或者成员方法的方式,与获取成员变量的方式都很类似,所以我只在获取成员变量的时候完整的写了四个方法。
我们来写两个简单的框架作为练习。
第一个框架要求: 可以创建任意的类,执行其中的方法
因为这是一个框架,所以像前面那样在main方法中获取Class对象显然是行不通的,所以我们用配置文件,里面写上要用的类与其中的方法就可以了。
配置文件内容:
className=Reflection.Person
methodName=getAge
代码实现这个框架:
public class A_Simple_Frame {
public static void main(String[] args) throws Exception
{
//1.加载配置文件
Properties pro=new Properties();
ClassLoader classLoader=A_Simple_Frame.class.getClassLoader();//创建一个类加载器的对象
InputStream is=classLoader.getResourceAsStream("pro.properties");//用创建的一个类加载器对象寻找我们的配置文件的路径并且返回一个字节
pro.load(is);
//2.获取配置文件中定义的数据
String className=pro.getProperty("className");
String methodName=pro.getProperty("methodName");
//3.加载该类进内存
Class cls=Class.forName(className);
//4.创建对象
Object obj=cls.newInstance();
//5.获取方法对象
Method method=cls.getMethod(methodName);//若有参数就写到第二个参数位置上
//6.执行方法
method.invoke(obj);
}
}
结果
getAge方法
这样一个简单的框架就完成了,若要使用其他类的方法,只需要把配置文件改一下就可以,主要代码不需要改变。
第二个框架要求:一个简单的测试框架,可以测试任何类
当main方法执行的时候,会自动检测所有加了Check注解的方法,记录异常到桌面的文件中
这个框架包含了上一篇关于注解的知识,同样也是使用配置文件完成的框架,先来看一下我们要测试的类:
public class Caculator {
@Check//自定义的一个注解
public int add()
{
return 1+0;
}
@Check
public int sub()
{
return 0-1;
}
@Check
public int mul()
{
return 1*0;
}
@Check
public int div()
{
return 1/0;//将来这个错误要被记录到文件中
}
}
Check注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
配置文件
className=Annotation_in_check.Caculator
代码实现这个框架
public class TestCheck {
@SuppressWarnings("all")
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
int ececeptionNum=0;//错误数量
BufferedWriter bw=new BufferedWriter(new FileWriter("/Users/ahlahmlahlah/Desktop/bug.txt"));
Properties pro=new Properties();
ClassLoader classLoader=TestCheck.class.getClassLoader();
InputStream is=classLoader.getResourceAsStream("pro.properties");
pro.load(is);
String className=pro.getProperty("className");
Class cls=Class.forName(className);
Object obj=cls.newInstance();
//上面的代码获取要测试的类并且创建此类的对象
//获取所有方法
Method[] methods=cls.getMethods();
for(Method m:methods)
{
if(m.isAnnotationPresent(Check.class))//这一句话判断方法前也没有注解
{
try {
m.invoke(obj);
} catch (Exception e) {
//捕获异常并把它记录到文件中
ececeptionNum++;
bw.write(m.getName()+"方法出现异常");
bw.newLine();
bw.write("异常的名称是:"+e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因是:"+e.getCause().getMessage());
bw.newLine();
bw.write("------------------------");
bw.newLine();
}
}
}
bw.write("本次测试一共出现"+ececeptionNum+"次异常");
bw.flush();
bw.close();
}
}
结果:
这样这个框架就完成了,下次更新完JDBC的知识,就要进入JavaWeb的部分了,博主尽量在开学前更新完。。