1、Java反射机制概述
Reflection (反射)是被视为动态语言(就是有了反射,才让java动态)的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(类对象:一个类只有一个Class对象) ,这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
补充:动态语言 和 静态语言
(1)动态语言
动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言: Object-C.C#、JavaScript, PHP,Python, Erlang..
(2)静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java.C.C++。
总结:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活
Exp:服务器已经跑起来了。前端Js发送请求想登陆,此时需要后台造登陆的对象;如果Js发送请求想注册,此时就需要后台造注册的对象,这就是动态运行造对象,此时就是用反射造对象。
1.1 Java反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
1.2 反射相关的主要API
java.lang.Class:代表一个类(Class是类 class是关键字) Class类,描述类的类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
2、关于 java.lang.Class类的理解
类的加载过程:程序经过javac.exe 命令以后, 会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件(带main函数的类)进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为CLass的一个实例。
说明:
① Class就对应着一个运行时类。即运行时,这个类就出来了。所以编译时就不用去new 一个 Class
② 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过exp中的3种方法来获取此运行时类
Class对象可以是以下所有类型:
① 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
② interface,接口
③ [ ] 数组
④ enum:枚举
⑤ annotation:注解@interface
⑥ primitive type:基本数据类型
⑦ void
特别说明:类只有一个,所以反射获取的同一个类,都是一个类(变量会指向同一地址)。数组本身默认的是指向数组第一个数的地址,所以只要数组的元素类型和数组维度(一维数组,二维数组)一样,就是同一个地址。
exp:
public class Person{ //本文章中的所有代码都使用这个类
private String name;
public int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void show(){
System.out.println("你好,我是08ff");
}
private String showNation(){
System.out.println("我的国籍是"+nation);
return nation;
}
}
public class ReflectionTest{
public void test3(){ //获取class实例的方式
//方式一:调用运行时类的属性:.class。用Class类对象clazz1直接调用类的属性Person.class,获取Person类的结构,泛型不用也可以,但有的地方就需要强转
Class<Person> clazz1 = Person.class;
//方式二:通过运行时的类对象,调用getClass()方法
Person p1 = new Person(); //运行时,已经通过构造器造好了Person类p1对象
Class clazz2 = p1.getClass(); //通过p1对象,反射找到Person类
//方式三:通过Class类自己的静态方法:forName(String classPath) classPath指类的完整名字(包含包)
Class clazz3 = Class.forName("com.tt.java.Person"); //使用频率最高的方法,最能体现动态性,必须掌握
//方式四:使用类的静态方法getClassLoader()获取类的加载器:ClassLoader 使用频率最低的方法,了解即可
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.tt.java.Person");
}
//反射之前,对于Person操作
public void test1(){
Person p1 = new Person("Tom",12); //1、用构造器创建Person类的对象
//2、通过对象,调用其内部的属性、方法
p1.age=10;
System.out.println(p1.toString());
p1.show();
//在Person类外部,不可以通过Person类的对象,调用其内部的私有结构
//比如:name、showNation()以及私有构造器
}
//反射之后,对于Person操作
public void test2(){
Class clazz= Person.class; //反射Person类
//通过反射,创建Person类对象
Constuctor cons = clazz.getConstructor(String.class,int.class); //获取Person类构造器。 int.class 反射基本数据类型int,和String一样,也是Class的对象
Object obj = cons.newInstance("Tom",12);//用反射获取的Person类构造器造对象
Person p=(Person) obj; //将obj转换成它本身的Person类
//通过反射,调用对象指定的属性、方法
Field age = clazz.getDeclaredField("age"); //getDeclaredFields()方法获取当前运行时类中指定变量名的属性
age.set(p,10); //设置属性
Method show = clazz.getDeclaredMethod("show");//通过反射调用show方法,
show.invoke(p);/*show方法没有形参,所以,直接填对象p就可以了 invoke()调用方法,参数1,方法的调用者(Person类的某个对象),参数2:给方法形参赋值实参*/
//通过反射,可以调用Person类的私有结构,比如私有构造器、方法、属性(封装性?)
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true); //保证当前属性可访问,否则访问private属性会报错
Person p1 = (Person) cons1.newInstance("Jerry"); //用反射再实例化一个对象
//调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"HanMeimei"); //直接将私有属性name的值修改成HanMeimei
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation",String.class);
showNation.setAccessible(true);
String nation = (String)showNation.invoke(p1,"中国");//相当于p1.showNation("中国"),nation来接收方法的返回值
}
}
疑问:反射机制与面向对象中的封装性是不是矛盾的?如何看待这两个技术
不矛盾。封装性和反射是两回事。封装性主要是告诉你,没必要再去调private的东西,我public的已经把功能做的很好了。反射是,我有能力去调用你的private的东西。
疑问:通过直接new的方式或反射的方式都可以调用公共的结构,用哪个更好
答:静态写代码时,直接用new的方式比较好。程序动态运行时,用反射造对象
3、关于类的加载与ClassLoader的理解
3.1 类加载器的作用:
(1)类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.class对象,作为方法区中类数据的访问入口。
(2)类缓存:标准的JavasE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
JVM定义了如下类型的类加载器:
exp:
public class ClassLoaderTest{
public void test1(){
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); //对于自定义类ClassLoaderTest,使用系统加载器进行加载
ClassLoader classLoader1 = classLoader.getParent(); //调用系统加载器的getParent()方法获取扩展类加载器
ClassLoader classLoader2 = classLoader1.getParent();//调用扩展类加载器的getParent()方法,无法获取引导类加载器,返回null
//特别说明:引导类加载器主要负责加载java的核心类库,无法加载自定义类
}
}
4、创建运行时类的对象(即用Class对象,创建对应类的对象
public void test4() throws IllegalAccessException, InstantiationException{ //第一个异常是空参构造器没有权限(private),第二个异常是没有空参构造器
Class<Person> clazz = Person.class; //获取Person类,赋值给Class对象 clazz
//Class类的newInstance()方法,会创建对应的运行时类(这里就是Person)的对象。内部调用了运行时类的空参构造器
Person obj = clazz.newInstance();//创建Person对象obj。注意如果不用泛型,则返回Object类型,就需要强转 即 = (Person)clazz.newInstance()
}
总结:
(1)要想newInstance()方法正常的创建运行时类的对象,要求:
① 运行时类必须提供空参的构造器
② 空参的构造器的访间权限得够。通常,设置为public.
(2)复习:在javabean中要求提供-个public的空参构造器。原因:
① 便于通过反射,创建运行时类的对象
② 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
5、体会反射动态性案例,随机造一个指定类的对象
public void testN(){
int num= new Random().nextInt(3);//随机生成0、1、2
String classPath = " ";
switch(num){ //根据不同随机数指定要反射的类
case 0:
classPath = "java.util.Date"; //反射util.Date
case 1:
classPath = "java.sql.Date"; //反射sql.Date 注意:抽到这个会报异常,因为sql.Date没有空参构造器
case 2:
classPath = "com.tt.java.Person"; //反射Person
}
Object obj = getInstance(classPath); //根据随机数,创建运行时类的对象
}
public Object getInstance() throws Exception(){
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
5、调用运行时类中指定的结构:属性、方法、构造器
exp:
public class ReflectionTest{
public void testField(){ //调用属性
Class clazz = Person.class;
Person p =(Person)clazz.newInstance();//通过反射创建运行时的Person类对象
Field name = clazz.getDeclaredField("name"); //getDeclaredField(String fieldName):获取运行类中指定变量名的属性
name.setAccessible(true); //保证当前属性是可访问的,否则访问private属性会报错
/*设置当前属性的值
set(对象名,属性值) 对象名:指明要设置哪个对象的属性。 属性值:将此属性值设置为多少*/
name.set(p,"Tom");
}
public void testMethod(){
Class clazz = Person.class;
//创建运行时类的对象
Person p = (Person) clazz.newInstance();
/*1、获取指定的某个方法
getDeclaredMethod():参数1:指明获取的方法的名称 参数2:指明获取的方法形参列表 */
Method show = clazz.getDeclaredMethod("show",String.class);
show.setAccessible(true); //2、保证当前属性是可访问的,否则访问private属性会报错
/*3、调用invoke()方法,参数1,方法的调用者(Person类的某个对象),参数2:给方法形参赋值实参
invoke()的返回值即为对应类中调用的方法的返回值*/
Object return Value=show.invoke(p,"CHN");
Method showDesc = clazz.getDeclaredMethod("showDesc");//加装showDesc()是Person中的static方法
showDesc.setAccessible(true);
//如果运行时类没有返回值,invoke()返回null。invoke()第一个参数是Person对象,
Object returnVal=showDesc.invoke(Person.class);
}
public void testConstructor() throws Exception{ //这种用法非常少,还是用反射空参构造器的时候多
Class clazz = Person.class;
//1.获取指定的构造器 getDeclaredConstructor():参数:指明构造器的参数列表
Constructor constructor = clazz.getDeclaredConstructor(String.class)
//2.保证此构造器是可访问的
constructor.setAccessible(true);
//3.调用此构造器创造运行时此类的对象
Person per = (Person)constructor.niwInstance("Tom");
}
}
6、反射的应用 动态代理 (一个通用代理类动态的去代理所有的被代理类)
6.1 代理模式的原理
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
6.2 动态代理的原理
(1)概述
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
(2)动态代理使用场合:
调试
远程方法调用
(3)动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
exp:静态代理举例
interface ClothFactory {
void produceCloth();
}
//代理类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory;//用被代理类对象实例化
public proxyClothFactory(ClothFactory factory){
this.factory = factory;
}
public void produceCloth(){
System.out.println("代理工厂做一些准备工作");
factory.produceClooth();
System.out.println("代理工厂做一些后续的收尾工作");
}
}
class NikeClothFactory implements ClothFactory{
public void produceCloth(){
System.out.println("NIke工厂生产一批运动服");
}
}
main(){
ClothFactory nike = new NikeClothFactory();//创建被代理类的对象
ClothFactory proxyClothFactory = new ProxyClothFactory(nike);//创建代理类对象
proxyClothFactory.produceCloth();
}
//动态代理:/
interface Human{
String getBelief();
void eat(String food);
}
//被代理类
class SuperMan implements Human{
public String getBelief(){
return "I believe I can fly!";
}
public void eat(String food){
System.out.println("我喜欢吃"+ food);
}
}
//代理类
/问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
//问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
public class ProxyFactory{ //代理类
//这个方法的目的是,可以返回一个代理类的对象(返回值Object类)。解决问题一
public static Object getProxyInstance(Object obj){ //形参obj为想要被代理的类的对象
MyInvocationHandler handler = new MyInvocationHandler(); //MyInvocationHandler类已实现InvocationHandler接口
handler.bind(obj); //handler对象的obj就绑定了代理类
/*Proxy类的静态方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler ) 这个构造器内部已经通过反射把被代理类的方法都拿到手了,有兴趣可以看源码*/
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterface(),handler) //前两个形参解决问题一,第三个形参解决问题二
}
}
class MyInvocationHandler implements InvocationHandler{ //实现InvocationHandler接口后,作为Proxy类的第三个参数
private Object obj; //需要把被代理类的对象进行赋值。
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用被代理类的方法a时,就会自动调用下面invoke方法
//因此我们需要将被代理类要执行的方法a的功能声明在invoke()中
public Object invoke(Object proxy, Method method, Object[ ] args) throws Throwable{ //InvocationHandler接口即重写invoke方法
//形参proxy就是上面的被代理类的对象obj.getClass().getClassLoader()这个加载器创建的对象,method是代理类代用的方法,
Object returnValue=method.invoke(obj,args); //通过代理类的invoke方法,调用被代理对象要调用的方法
return returnValue;//上述方法的返回值就作为当前类中的invoke的返回值
}
}
main(){
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFaceory.getProxyInstance(superMan); //proxyInstance即为已经获取Human类全部方法的代理类对象
//当通过代理类对象proxyInstance调用方法时,会自动调用被代理类 SuperMan中的同名方法
proxyInstance.getBelief();//调用上面的invoke()方法,上面的method就是这里的getBelief
proxyInstance.eat("四川麻辣烫");//也是调用invoke()方法,上面的method就是eat,这时四川麻辣烫,就是被调用参数
}