Java 反射机制
Java 类型信息
获取 Java 运行时的类型信息有两种方式
- RTTI (Run-Time Type Identification)
- Java 反射机制
RTTI
- 在运行时,可恶意识别一个对象的类型
- 从数组中取出元素时,会自动转为各个元素的公共基类使用。而且 Java 中所有的类型转换都是在运行的时候检查正确性的。
- 大部分代码尽量少地了解对象的具体类型,而是只与对象家族中的一个通用基类打交道
Java 反射机制
Java 反射机制是在运行状态中,对于任意一个类,都可以知道这个类的所有属性和方法;对于任意一个对象,都可以调动它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。
具体地,一般通过 Class 类的 forName() 方法返回类的类型,通过newInstance()方法创建实例
Class 类
Class 类是 Java 的一个基础类。实际上 JVM 每装载一个新类的时候,就会在堆中创建一个 名为 class 的Class 静态实例,这个实例就代表这个新类的类型,通过实例可以获取新类的类型信息
- Method[] getMethods()
- Field[] getFields()
- Constructor<?>[] getDeclaredConstructors()
getMethods() 方法用于获取类的方法,getField() 方法用于获取类的属性,最后一个用于获取所有的构造方法
Object 类
- hashCode(),计算对象的 hashcode
- equals(),返回对象是否同一
- clone():浅拷贝
- toString():可以重写
- notify():线程相关
- wait():线程相关
使用反射机制创建实例
package com.company.chap7one;
class AirPlane{
@Override
public String toString() {
return "In airPlane!";
}
}
public class CreateInstance {
public static void main(String[] args) throws Exception{
Class c1 = Class.forName("com.company.chap7one.AirPlane");
Object ap = null;
System.out.println(c1);
ap = c1.newInstance();
System.out.println(ap.toString());
}
}
运行结果如下:
class com.company.chap7one.AirPlane
In airPlane!
可见 ap 引用指向了一个 AirPlane 实例,通过 Class 创建对象的方法获得了和传统方法相同的效果。
使用 Method 类的 invoke() 方法获得类的方法
import java.lang.reflect.Method;
public class TestInvoke {
public void add(Integer p1, Integer p2){
System.out.println(p1.intValue()+p2.intValue());
}
public void stringAdd(String abc){
System.out.println("out"+abc);
}
public static void main(String[] args) {
try{
Method mth = TestInvoke.class.getMethod("add", new Class[]{Integer.class, Integer.class});
mth.invoke(TestInvoke.class.newInstance(), new Integer(1), new Integer(2));
Method mth_two = TestInvoke.class.getMethod("stringAdd", new Class[]{String.class});
mth_two.invoke(TestInvoke.class.newInstance(),"--test");
}catch (Exception e){}
}
}
运行结果如下:
3
out–test
Java 静态代理
代理模式
- 在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
- 代理模式的作用是可以为其它对象提供一种对某个对象的受限访问。
- 使用代理我们可以在使用对象的方法之外再做一些别的事
代理模式一般涉及到的角色
- 抽象角色:声明真实对象和代理对象的共同接口。
- 代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能够代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
- 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。
举例
abstract class Subject{
public abstract void request();
}
class RealSubject extends Subject{
@Override
public void request() {
System.out.println("From Real Subject!");
}
}
class ProxySubject extends Subject{
private RealSubject realSubject;
private void preRequest(){
System.out.println("Pre Request.");
}
private void postRequest(){
System.out.println("Post Request.");
}
@Override
public void request() {
preRequest();
if(null==realSubject){
realSubject = new RealSubject();
}
realSubject.request();
postRequest();
}
}
public class StaticProxyTest {
public static void main(String[] args) {
Subject subject = new ProxySubject();
subject.request();
}
}
结果:
Pre Request.
From Real Subject!
Post Request.
注意 ProxySubject 代理类里面的对象和方法是私有的。
静态代理的优缺点
优点:
- 业务类只需要关注业务逻辑本身,保证了业务类的重用性。这是代理的共有优点。
缺点: - 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
Proxy
Java 的动态代理类 java.lang.reflect.Proxy 是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及对象
public static InvocationHandler getInvocationHandler(Object proxy)
,获取指定代理对象所关联的调度处理器public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
,获取关联于指定类装载器和一组接口的动态代理类的类对象public static boolean isProxyClass(Class<?> cl)
,判断指定类对象是否是一个动态代理类public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
,为指定类加载器、一组接口以及调用处理器生成动态代理类实例
InvocationHandler
- 位于 java.lang.reflect.InvocationHandler
- 名为“调用处理器”的接口,自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
,集中处理动态代理类上的所有方法调用,第一个参数是代理类实例,第二个是被调用的方法对象,第三个是调用这个方法时需要传入的参数。调用处理器根据这三个参数进行预处理或者分派到委托类实例上执行。
举例如下
package com.company.chap7four;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Subject {
public void request();
}
class RealSubject implements Subject{
public RealSubject(){}
@Override
public void request() {
System.out.println("From real subject");
}
}
class DynamicSubject implements InvocationHandler{
private Object sub;
public DynamicSubject(){}
public DynamicSubject(Object ob){
sub = ob;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before calling "+method);
method.invoke(sub, args);
System.out.println("After calling "+method);
return null;
}
}
public class DynamicProxyTest {
public static void main(String[] args) throws Throwable {
Class cls = Class.forName("com.company.chap7four.RealSubject");
Object rs = cls.newInstance();
InvocationHandler ds = new DynamicSubject(rs);
Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), ds);
subject.request();
}
}
运行结果如下:
Before calling public abstract void com.company.chap7four.Subject.request()
From real subject
After calling public abstract void com.company.chap7four.Subject.request()
可以看到,动态代理不是通过继承抽象类或者实现接口实现。而是通过实现 InvocationHandler 接口实现。通过重写 invoke 函数可以实现任意方法的调用。只是一个代理类只能有一个 invoke 函数,势必对对不同的方法,一般采用同样的额外处理。如果想要对每个真实角色的方法采取不同的额外处理,还需要自己定制。
动态代理的特点
- 类名:格式是 “$ProxyN” ,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
- 类继承关系:该类的继承关系如图:
动态代理的优缺点
- 优点:动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler. invoke)。这样,在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
- 缺点:仅支持 interface 代理。
JVM 加载类原理
JVM 类加载的种类
-
JVM 自带的默认加载器
- 根加载器:bootstrap,由 C++ 编写,所有 Java 程序无法获得
- 扩展类的加载器:由 Java 编写
- 系统类、应用类的加载器:由 Java 编写
-
用户自定义的类加载器
- java.lang.ClassLoader 的子类,用户可以定制类的加载方式。每一个类都包含了加载他的 ClassLoader 的一个引用 —— getClassLoader()。如果返回的是 null,证明加载他的 ClassLoader 是跟加载器 bootstrap
类的加载方式
- 本地编译好的 class 文件中直接加载
- 网络加载,通过 java.net.URLClassLoader 可以通过 URL 在网络中加载
- 从 jar、zip 等压缩文件中加载类,自动解析 jar 文件找到 class 文件加载
- 从 Java 源代码文件动态编译为 class 文件。
类的加载步骤
- 加载
- 连接,包括:验证、准备、解析
- 类的初始化
ClassLoader 的加载顺序
- 根加载器
- 扩展类的加载器
- 应用类的加载器
- 用户自定义的加载器
如果最后一层能都加载不了,就会报 ClassNotFoudException 异常
ClassLoader 加载 Class 的过程
第1步:检测此 Class 是否载入过(即在 cache 中是否有此 Class ),如果有跳到第8步, 如果没有到第2步
第2步:如果 parent classloader 不存在(没有 parent,那 parent 一定是 bootstrap classloader 了),则跳到第4步
第3步:请求 parent classloader 载入,如果成功到第8步,不成功到第5步
第4步:请求 JVM 从 bootstrap classloader 中载入,如果成功到第8步
第5步:寻找 Class 文件(从与此 classloader 相关的类路径中寻找》。如果找不到则到第7步,
第6步:从文件中载入 Class,到第8步.
第7步:抛出 ClassNotFoundException.
第8步:返回 Class.