运行时类型识别 (RTTI, Run-Time Type Information) 是 Java 中非常有用的机制,在 java 中,有两种 RTTI 的方式,一种是传统的,即假设在编译时已经知道了所有的类型;还有一种,是利用反射机制,在运行时再尝试确定类型信息。
本篇博文会结合 Thinking in Java 的 demo 和实际开发中碰到的例子,对 Java 反射和获取类型信息做总体上整理。文章主要分为三块:
在学习 RTTI 的时候,首先需要知道 Java 中类是如何加载的,java 又是如何根据这些 class 文件得到 JVM 中需要的信息(备注:我在此处实在是想不到更好的描述,望读者可以给出更好的描述)
类加载器子系统包含一条加载器链,只有一个 "原生的类加载器" 他是 jvm 实现的一部分,可以用来记载本地 jar 包内的 class,若涉及加载网络上的类,或者是 web 服务器应用,可以挂接额外的类加载器。
所有的类都是第一次使用的时候,动态加载到 JVM 中。创建对类的静态成员的引用,加载这个类。Java 程序在开始运行的时候并非完全加载,类都是用的地方在加载,这就是动态加载
①:首先检查这个类是否被加载
②:如果没有加载,再去根据类名查找. class 文件,加载类的字节码,并校验是否存在不良代码,
测试代码如下:
- //candy.java
- public class Candy {
- static {
- System.out.println("loading Candy");
- }
- }
- //cookie.java
- public class Cookie {
- static {
- System.out.println("loading Cookie");
- }
- }
- //Gum.java
- public class Gum {
- static {
- System.out.println("loading Gum");
- }
- }
- //TestMain.java
- public class TestMain {
- public static void main(String[] args) {
- System.out.println("inside main");
- new Candy();
- System.out.println("After create Candy");
- try {
- Class.forName("com.RuntimeTypeInformation.Gum");
- } catch(ClassNotFoundException e) {
- System.out.println("Could not find Class");
- }
- System.out.println("After Class.forName");
- new Cookie();
- System.out.println("After new Cookie()");
- }
- static void printClassInfo(Class c) {
- System.out.println("Class Name :" + c.getName() + "is interface? :" + c.isInterface() + "simple Name " + c.getSimpleName());
- }
从输出结果可以清楚看到; class 对象仅在需要的时候才会加载,static 初始化是在类加载的时候进行
验证类中的字节码,为静态域分配存储空间。如果必须的话,将解析这个类创建的对其他类的所有引用
如果该类存在超类,对其初始化,执行静态初始化器和静态代码块。初始化延迟至 对静态方法或者非静态方法首次引用时执行
实际开发中,需求并不是一成不变的(准确来说是经常变),而每新添加需求如果代码的改动量越小肯定是越能提高效率。比如:
- package com.RuntimeTypeInformation.circle;
- import java.util.Arrays;
- import java.util.List;
- abstract class Shape {
- void draw() {
- System.out.println(this + ".draw()");
- }
- abstract public String toString();
- }
- class Circle extends Shape {@Override public String toString() {
- return "Circle";
- }
- }
- class Triangle extends Shape {@Override public String toString() {
- return "Triangle";
- }
- }
- public class Shapes {
- public static void main(String[] args) {
- //题外话,Arrays.asList 可变参数列表,可以把传入的多个对象转为一个list
- List shapes = Arrays.asList(new Triangle(), new Circle());
- for (Shape shape: shapes) {
- shape.draw();
- }
- }
- }
当我想要添加一个新的形状,比如说长方形,我只需要编写一个新类继承 Shape 即可,而不需要修改调用的地方 。在这里用到了 "多态"(虽然调用的都是 shpe 的方法,但是 JVM 能在运行期
准确的知道应该调用具体哪个子类的方法)
当你第一次了解 "多态",你可能是简单知道堕胎就是这么一回事,那么,现在我们去研究一下,java 是怎样处理的.
① 当把 Triangle,Circle 放到 List<Shape> 时,会向上转型为 Shape, 丢失具体的类型
② 当从容器中取出 Shape 对象的时候,List 内实际存放的是 Object, 在运行期自动将结果转为 Shape,这就是 RTTI 的工作( 在运行时识别一个对象的类型)
这时候,如果客户需求又改了,说不希望画的结果存在圆形。应对这种需求,我们可以采用 RTTI 查询某个 shape 引用所指向的具体类型(具体怎么用,可以接着往下看)
Java 的核心思想就是:" 一切皆是对象 ",比如我们对形状抽象,得到圆形类,三角形类。但我们 对这些类在做一次抽象,得到 class 用于描述类的一般特性
上图是我用画图画的(有点捞见谅),如果我们可以拿到对象的 class, 我们就可以利用 RTTI 得到具体的 java 类。至于如何拿到 Class 和怎样用 Class 得到准确的类,继续往下看。
每一个类都存在与之对应的 Class 对象(保存在. class 文件中),根据 class 得到具体的对象,请参考 "第一章节 类的加载和初始化"
①:Class.forName("全限定类名"),得到 Class 对象,副作用是 "如果对应的类没有加载,则会加载类"。找不到会抛出 ""ClassNotFoundException"
②:如果有对象,可以直接用对象得到与之对应的 Class 对象 比如
- Shape shape = new Circle();
- shape.getClass()
③ ; 通过类字面常量 : Shape.class. 推荐用该方法,第一是编译器会做检查,第二是根除了对 forName 的调用,提高效率
方法名 | 说明 |
---|---|
forName() | (1)获取 Class 对象的一个引用,但引用的类还没有加载 (该类的第一个对象没有生成) 就加载了这个类。 (2) 为了产生 Class 引用,forName() 立即就进行了初始化。 |
Object-getClass() | 获取 Class 对象的一个引用,返回表示该对象的实际类型的 Class 引用。 |
getName() | 取全限定的类名 (包括包名),即类的完整名字。 |
getSimpleName() | 获取类名 (不包括包名) |
getCanonicalName() | 获取全限定的类名 (包括包名) |
isInterface() | 判断 Class 对象是否是表示一个接口 |
getInterfaces() | 返回 Class 对象数组,表示 Class 对象所引用的类所实现的所有接口。 |
getSupercalss() | 返回 Class 对象,表示 Class 对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 |
newInstance() | 返回一个 Oject 对象,是实现 "虚拟构造器" 的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有 getMethods 和 getConstructors。 |
getDeclaredFields | 获得某个类的自己声明的字段,即包括 public、private 和 proteced,默认但是不包括父类声明的任何字段。类似的还有 getDeclaredMethods 和 getDeclaredConstructors。 |
Class 引用表示它所指向的对象的确切类型,java1.5 之后,允许开发者对 Class 引用所指向的 Class 对象进行限定,也就是添加泛型。
- public static void main(String[] args) {
- Class intclass = int.class;
- intclass = Integer.class;
- }
这样可以在编译器进行类型检查,当然可以通过 "通配符" 让引用泛型的时候放松限制 ,语法 : Class<?>
目的:
①:为了可以在编译器就做类型检查
② : 当 Class<Circle> circle = circle.getClass(); circle.newInstance() 会得到具体的类型 。但此处需注意:
- public class Shapes {
- public static void main(String[] args) throws InstantiationException,
- IllegalAccessException {
- Class circles = Circle.class;
- Circle circle = circles.newInstance(); //第一:泛化class.newInstance可以直接得到具体的对象
- Classsuper Circle > shape = circles.getSuperclass();
- Object shape1 = shape.newInstance(); //第二:它的父类,只能用逆变的泛型class接收,newInstance得到的是Object类型
- }
- }
①:传统的类型转换,比如我们在上边的 demo 中用到的 shape.draw();
②:利用 Class,获取运行时信息。
③:得到具体的对象
如果不知道某一个对象引用的具体类型(比如已经上转型的对象),RTTI 可以得到。但前提是这个类型编译器必须已知(那些是编译期不可知呢? 磁盘文件或者是网络连接中获取一串代表类的字节码)
跨网络的远程平台上提供创建和运行对象的能力 这被称为 RMI(远程方法调用),下面会具体的介绍一下 RMI 的实现方式
反射提供了一种机制,用于检查可用的方法,并返回方法名,调用方法。
Java 中提供了 jar 包 ,Java.lang.reflect 和 Class 对象一起对反射的概念提供支持。
该类库中包含了 Field Method Constructor. 这些类型的对象在 JVM 运行时创建, 用于表示未知类里对应的成员。从而:
①:用 Constructor 创建对象,用 get set 读取 Field 内的字段
②:用 Method.invoke() 调用方法
③:用 getFields()、getMethods()、getConstuctors() 得到与之对应的数组
检查对象,查看对象属于哪个类,加载类的 class 文件
①:RTTI 会在编译期打开和检查.class 文件
②:RMI 在编译期是 看不到.class 文件。只能在运行期打开和检查.class 文件
- public interface Subject {
- public void doSomething();
- }
- public class RealSubject implements Subject {
- public void doSomething() {
- System.out.println("call doSomething()");
- }
- }
- public class ProxyHandler implements InvocationHandler {
- private Object proxied;
- public ProxyHandler(Object proxied) {
- this.proxied = proxied;
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- //在转调具体目标对象之前,可以执行一些功能处理
- //转调具体目标对象的方法
- return method.invoke(proxied, args);
- //在转调具体目标对象之后,可以执行一些功能处理
- }
- }
① 动态代理使用步骤:
1. 通过实现 InvocationHandler 接口来自定义自己的 InvocationHandler;
2. 通过 Proxy.getProxyClass 获得动态代理类
- public class MyProxy {
- public interface IHello {
- void sayHello();
- }
- static class Hello implements IHello {
- public void sayHello() {
- System.out.println("Hello world!!");
- }
- }
- //自定义InvocationHandler
- static class HWInvocationHandler implements InvocationHandler {
- //目标对象
- private Object target;
- public HWInvocationHandler(Object target) {
- this.target = target;
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("------插入前置通知代码-------------");
- //执行相应的目标方法
- Object rs = method.invoke(target, args);
- System.out.println("------插入后置处理代码-------------");
- return rs;
- }
- }
- public static void main(String[] args) throws NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException,
- InstantiationException {
- //生成$Proxy0的class文件
- System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
- IHello ihello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), //加载接口的类加载器
- new Class[] {
- IHello.class
- },
- //一组接口
- new HWInvocationHandler(new Hello())); //自定义的InvocationHandler
- ihello.sayHello();
- }
- }
② :动态代理的原理,列举一下参考文献把:(本质上还是用到了反射)
1、JDK 动态代理实现原理
2、Java 动态代理机制分析及扩展
③ 动态代理应用以及备注说明 :
JDK 实现动态代理需要实现类通过接口定义业务方法 (接下来我会简单说一下 Cglib 实现动态代理)。第二是动态代理非常重要 是反射一个极其重要的模块,很多框架都离不开动态代理,比如 Spring 。所以,推荐读者在多去研究一下。
④:Cglib 实现动态代理
参考文档: cglib 动态代理介绍 (一)
CGLIB 是一个强大的高性能的代码生成包。它广泛的被许多 AOP 的框架使用,例如 spring AOP 和 dynaop,为他们提供方法的 interception(拦截)。最流行的 OR Mapping 工具 hibernate 也使用 CGLIB 来代理单端 single-ended(多对一和一对一) 关联(对集合的延迟抓取,是采用其他机制实 现的)。EasyMock 和 jMock 是通过使用模仿(moke)对象来测试 Java 代码的包。它们都通过使用 CGLIB 来为那些没有接口的类创建模仿 (moke)对象。
CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类。除了 CGLIB 包,脚本语言例如 Groovy 和 BeanShell,也是使用 ASM 来生成 java 的字节码。当不鼓励直接使用 ASM,因为它要求你必须对 JVM 内部结构包括 class 文 件的格式和指令集都很熟悉
"在运行期扩展 java 类及实现 java 接口",补充的是 java 动态代理机制要求必须实现了接口,而 cglib 针对没实现接口的那些类,原理是通过继承这些类,成为子类,覆盖一些方法,所以 cglib 对 final 的类也不生效
cglib 实现动态代理的 demo:参考 CGLib 动态代理原理及实现
这是要代理的类:
- public class SayHello {
- public void say() {
- System.out.println("hello everyone");
- }
- }
代理类的核心
- public class CglibProxy implements MethodInterceptor {
- private Enhancer enhancer = new Enhancer();
- public Object getProxy(Class clazz) {
- //设置需要创建子类的类
- enhancer.setSuperclass(clazz);
- enhancer.setCallback(this);
- //通过字节码技术动态创建子类实例
- return enhancer.create();
- }
- //实现MethodInterceptor接口方法
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- System.out.println("前置代理");
- //通过代理类调用父类中的方法
- Object result = proxy.invokeSuper(obj, args);
- System.out.println("后置代理");
- return result;
- }
- }
测试结果:
- public class DoCGLib {
- public static void main(String[] args) {
- CglibProxy proxy = new CglibProxy();
- //通过生成子类的方式创建代理类
- SayHello proxyImp = (SayHello) proxy.getProxy(SayHello.class);
- proxyImp.say();
- }
- }
通常,我们在一般的业务需求中是用不到反射的,但我们在更加动态的代码时,我们就可以选择反射来实现(例如对象序列化和 JavaBean)。主要的逻辑我在上边都已经说明了,所以接下来 更多的是代码展示:
实际开发中,在运行时得到 Class 信息,获取 method ,通过反射 method.invoke() 调用方法。这样做是出于 AOP 的设计思想。举例来说,我一个传统的 web 项目,我可以同过 http 直接传递请求给后台 servlet,假如我想添加一个记录日志,或者是在请求的 session 中添加一个信息,如果只有一个请求,我可以直接在 htttp 加,但实际上请求会很多,这是我为什么在 sevlet 外在抽出一层,通过反射调用 servlet
当然,很多框架其实也为我们提供了拦截的配置(这是后话)
- doPost(..) {
- //这是项目中的setvlet统一的拦截层,接下来我们看一下 actionInvoker.invoke
- ...
- else if (requestType.equalsIgnoreCase("image")) {
- try {
- ActionClassInfo actionClassInfo = actionInvoker.getClassInfo(action, request, response);
- actionClassInfo.setArgs(queryStringMap);
- Object object = actionInvoker.invoke(actionClassInfo);
- response.addHeader("accept-ranges", "bytes");
- byte[] bytes = (byte[]) object;
- response.addHeader("Content-type", "application/png");
- response.addHeader("content-length", String.valueOf(bytes.length));
- response.getOutputStream().write(bytes, 0, bytes.length);
- } catch(Exception e) {
- e.printStackTrace();
- } catch(Throwable e) {
- e.printStackTrace();
- } finally {
- response.getOutputStream().flush();
- response.getOutputStream().close();
- }
- }
- }
actionInvoker.invoke()方法代码如下: 在这方法内,我就可以添加我想要的处理,比如先判断是否在缓存中存在,核心的只有 method.invoke
- public Object invoke(ActionClassInfo action) throws Exception {
- // 执行方法之前
- Object cache = null;
- for (Object object: action.getProxys()) {
- if (object instanceof Intercepter) {
- cache = ((Intercepter) object).before(action);
- if (cache != null && object instanceof RedisCacheHandler) {
- return cache; //缓存的结果直接返回
- }
- }
- }
- Method method = action.getMethod();
- Object business = action.getClazz();
- Map args = action.getArgs();
- method.setAccessible(true);
- Object result = method.invoke(business, args);
- // 执行方法后
- for (Object object: action.getProxys()) {
- if (object instanceof Intercepter) result = ((Intercepter) object).after(result, action);
- }
- return result;
- }
来源: http://www.cnblogs.com/ldh-better/p/7148975.html