一 反射机制的引入
在介绍反射机制之前,我们来看一看java程序运行的过程:Java程序从源文件创建到程序运行要经过两大步骤:
1、源文件由编译器编译成字节码(ByteCode)
2、字节码由java虚拟机解释运行。因为java程序既要编译同时也要经过JVM的解释运行
也就是 a.java经过编译编程 a.class,然后通过类加载器(ClassLoader)加载到JVM中去执行。从上面的过程可以看出,一旦一个java对象被编译后,执行的结果就无法改变了,但是如果我们需要改变怎么办呢?这时候java反射就出现了。
二反射机制的主要功能
一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
如下图使用反射 和配置文件动态改变类的属性
三 Class
在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的。
每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte,char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合,Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段:
- 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
- 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
- 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。
在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以用它来创建这个类的所有对象
反射主要方法和用法
反射只能动态调用类的方法和属性,改变不了类的方法和属性,如果类的方法和属性的类型定义为泛型,则可以实现类型更改,因为泛型只在编译时有效运行时为Object类型,最常见的例子就是改变创建好的集合元素类型。
使用反射创建对象
通过反射来生成对象有两种方式:
1、通过Class对象的newInstance()方法来创建Class对象对应类的实例。这个方法是使用Class对象对应类的默认构造器创建对象,这就要求Class对象对应类必须要有默认构造器。
2、使用Class对象获取指定的Constructor对象,调用Constructor对象的newInstance()方法来创建Class对象对应类的实例。这个方法可以使用Class对象对应类的任意指定的构造器来创建实例。
第一种方法比较常见,在spring这些框架中,会根据配置文件自动创建类的实例并注入到依赖此类的类中。这时候用的最多的就是默认构造器。像是在spring的配置文件中,我们提供的是某个类的全类名,这种情况要创建类的实例,就必须使用反射了。
newInstance()使用步骤
第一步:获得字节码:
classc = Class.forName("Example");
第二步:调用newInstance();方法创建对象
factory = (ExampleInterface)c.newInstance();
构造器创建对象使用步骤
第一步获得字节码
classc = Class.forName("Example");
第二步获得有参或无参构造器
Constructorc1 = clazz.getConstructor(String.class,int.class,char.class,double.class);
第三步:使用newInstance创建对象
Object o1 =c1.newInstance("张三",18,'男',151);
获得类的字节码
1.创建对象,调用getClass方法,
Classclazz = new Person().getClass();
2.使用类名.class
Classclazz2 = Person.class;
3.将全类名传入Class.forName()中,推荐使用,扩展性强
Classclazz3 = Class.forName("cn.edu360.javase31.day20.Person");
获取类内信息
构造器Constructor<T> getConstructor(Class<?>... parameterTypes)
包含的方法 Method getMethod(String name,Class<?>... parameterTypes)
包含的属性 Field getField(String name)
包含的Annotation <A extends Annotation> AgetAnnotation(Class<A> annotationClass)
内部类Class<?>[] getDeclaredClasses()
外部类Class<?> getDeclaringClass()
所实现的接口 Class<?>[] getInterfaces()
修饰符 intgetModifiers()
所在包 PackagegetPackage()
类名 String getName()
简称 String getSimpleName()
获取class
主有三种获得class的途径,使用时要注意区别
1,类型.class 如: String.class使用类名加“.class”的方式即会返回与该类对应的Class对象。这个方法可以直接获得与指定类关联的Class对象,而并不需要有该类的对象存在。
2,Class.forName("类名");该方法可以根据字符串参数所指定的类名获取与该类关联的Class对象。如果该类还没有被装入,该方法会将该类装入JVM。forName方法的参数是类的完整限定名(即包含包名)。通常用于在程序运行时根据类名动态的载入该类并获得与之对应的Class对象。
3 obj.getClass();所有Java对象都具备这个方法,该方法用于返回调用该方法的对象的所属类关联的Class
获取构造方法
Class类提供了四个public方法,用于获取某个类的构造方法:
Constructor getConstructor(Class[] params)根据构造函数的参数,返回一个具体的具有public属性的构造函数
Constructor getConstructors() 返回所有具有public属性的构造函数数
Constructor getDeclaredConstructor(Class[] params) 根据构造函数的参数,返回一个具体的构造函数(不分public和非public属性)
Constructor getDeclaredConstructors() 返回该类中所有的构造函数数组(不分public和非public属
1
反射出无参的构造方法并得到对象
* 注意:
*
1在Class
.forName()
中应该传入含有包名的类全名
*
2 newInstance()
方法的本质是调用类的无参Public构造方法
*/
String className1=
"cn.testreflect.Worker";
Class clazz1=Class
.forName(className1)
;
Object object1=clazz1
.newInstance()
;
System
.out.println(
"object1.toString()="+object1
.toString())
;
/**
* 2 反射出带参数的构造方法并得到对象
*/
String className2=
"cn.testreflect.Worker";
Class clazz2=Class
.forName(className2)
;
Constructor constructor1=clazz2
.getConstructor(int
.class,String
.class)
;
Object object2=constructor1
.newInstance(
18,
"小明")
;
System
.out.println(
"object2.toString()="+object2
.toString())
;
获取类的成员方法
Method的invoke方法能调用任意方法
Method类中的invoke方法,允许调用包装在Method对象中的方法
Objectinvoke(Object obj, Object... args) //对应隐式参数和显式参数
API-Method-invoke
0.如果底层方法是静态的,那么可以忽略指定的obj
参数。该参数可以为 null;如果底层方法所需的形参数为 0,则所提供的args
数组长度可以为 0 或 null
1.第一个参数是隐式参数(比如this,传进你要委托的对象);其余的可变参数提供了显式参数(Java SE 5.0以前没有可变参数,必须传递对象数组或者null)
对于静态方法(属于类),第一个参数设置为null,作为隐式参数来传递
2.如果有返回类型,invoke方法将返回一个名义上的Object类型,实际类型由方法内部决定,所以还要进行强制类型转换;
而因此如果返回类型是基本类型,为了统一返回类型,将会返回其包装器类型:
与获取构造方法的方式相同,存在四种获取成员方法的方式。
Method getMethod(Stringname, Class[] params) 根据方法名和参数,返回一个具体的具有public属性的方法
Method[] getMethods() 返回所有具有public属性的方法数组
Method getDeclaredMethod(Stringname, Class[] params) 根据方法名和参数,返回一个具体的方法(不分public和非public属性)
其中privateGetDeclaredMethods
方法从缓存或JVM中获取该Class
中申明的方法列表,searchMethods
方法将从返回的方法列表里找到一个匹配名称和参数的方法对象
Method[] getDeclaredMethods() 返回该类中的所有的方法数组(不分public和非public属性)
调用对象的带参数的方法
String className5=
"cn.testreflect.Worker";
Class clazz5=Class
.forName(className5)
;
Method method=clazz5
.getMethod(
"printMessage",
String
.class,int
.class,int
.class)
;
Object object5=clazz5
.newInstance()
;
method
.invoke(object5,
"周星星",
50,
9527)
;
} catch (Exception e) {
System
.out.println(e
.toString())
;
}
获取类的成员变量(成员属性)
存在四种获取成员属性的方法
Field getField(String name)
根据变量名,返回一个具体的具有
public属性的成员变量
Field[]
getFields()
返回具有
public属性的成员变量的数组
Field
getDeclaredField(Stringname)
根据变量名,返回一个成员变量(不分
public和非
public属性)
Field[]
getDelcaredFields()
返回所有成员变量组成的数组(不分
public和非
public属性)
*
1
获取类的私有字段
* 注意:
* 获取共有字段应调用clazz3
.getField(name)
方法
*/
String className3=
"cn.testreflect.Worker";
Class clazz3=Class
.forName(className3)
;
Field ageField1=clazz3
.getDeclaredField(
"age")
;
System
.out.println(
"ageField1="+ageField1)
;
/**
* 2 获取和更改某个对象的私有字段
* 即模拟get()和set()方法
*/
String className4=
"cn.testreflect.Worker";
Class clazz4=Class
.forName(className4)
;
Field ageField2=clazz4
.getDeclaredField(
"age")
;
Object object4=constructor1
.newInstance(
18,
"小明")
;
//取消访问私有字段的合法性检查
ageField2
.setAccessible(true)
;
//获取对象的私有字段
Object ageObject4=ageField2
.get(object4)
;
System
.out.println(
"ageObject4="+ageObject4)
;
//再更改对象的私有字段的值
ageField2
.set(object4,
9527)
;
//重新获得
Object ageObject5=ageField2
.get(object4)
;
System
.out.println(
"ageObject5="+ageObject5)
;
动态代理
有两个重要的类或接口
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
java.lang.reflect 类Proxy
Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类
Proxy方法最常用的就是;
| newProxyInstance |
invoke 方法:
我们来看看InvocationHandler这个接口的唯一一个方法invoke 方法:
Objectinvoke(Object proxy, Method method, Object[] args) throwsThrowable
我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?
Objectinvoke(Object proxy, Method method, Object[] args) throws Throwable
如果又返回值,要这样写,没有也可以这样写
Object ob=method.invoke(t, args);
proxy: 指代我们所代理的那个真实对象
method: 指代的是我们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
如果不是很明白,等下通过一个实例会对这几个参数进行更深的讲解。
接下来我们来看看Proxy这个类:
Proxyprovides static methods for creating dynamic proxy classes and instances, and it is alsothe superclass of all dynamic proxy classes created by those methods.
Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance 这个方法:
newProxyInstance 这个方法
注:最后newProxyInstance的到的对象要转化成目标对象实现的接口
因为:Proxy.newProxyInstance(loader, interfaces,h);返回的是一个指定接口的代理类实例
所以不能使用目标类啦。(子类强转不成父类)
Objectob=Proxy.newProxyInstance(loader, interfaces,h);
IntefaceA ob2=(IntefaceA)ob;
public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces, InvocationHandler h) throws IllegalArgumentException
Returns an instance of a proxy class for the specifiedinterfaces that dispatches method invocations to the specified invocationhandler.
这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader: 一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h: 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:
首先我们定义了一个Subject类型的接口,为其声明了两个方法:
public interface Subject
{
public void rent();
public void hello(String str);
}
接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:
public class RealSubject implements Subject
{
@Override
public void rent()
{
System.out.println("I want to rent my house");
}
@Override
public void hello(String str)
{
System.out.println("hello: " + str);
}
}
下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现InvocationHandler 这个接口,因此我们这个动态代理类也不例外:
public class DynamicProxy implements InvocationHandler
{
// 这个就是我们要代理的真实对象
private Object subject;
// 构造方法,给我们要代理的真实对象赋初值
publicDynamicProxy(Object subject)
{
this.subject= subject;
}
@Override
public Objectinvoke(Object object, Method method, Object[] args)
throwsThrowable
{
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before renthouse");
System.out.println("Method:"+ method);
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after renthouse");
return null;
}
}
最后,来看看我们的Client类:
public class Client
{
public static void main(String[] args)
{
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = newDynamicProxy(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler,我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subjectsubject =(Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(),realSubject
.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}
动态代理为什么只能代理有接口的
Java动态代理,动态生成的代理类默认已经继承了Proxy类。而java是单继承,所以无法代理一个类,只能根据接口来进行代理!
动态代理后生成的代理类是这个样子的:
public final class $Proxy0 extends Proxy implementsYourInterface {}
所以,只能代理接口,而无法代理一个具体的类