[转]Java 反射在实际开发中的应用

正文

  运行时类型识别(RTTI, Run-Time Type Information)是Java中非常有用的机制,在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型;还有一种,是利用反射机制,在运行时再尝试确定类型信息。

  本篇博文会结合Thinking in Java 的demo 和实际开发中碰到的例子,对Java反射和获取类型信息做总体上整理。文章主要分为三块:

  •   Java类加载和初始化
  •   Java中RTTI
  •   Java利用反射获取运行时类型信息

回到顶部

一:Java类加载和初始化

  在学习RTTI的时候,首先需要知道Java中类是如何加载的,java又是如何根据这些class文件得到JVM中需要的信息(备注:我在此处实在是想不到更好的描述,望读者可以给出更好的描述)

1.1 类加载器(类加载的工具)

  类加载器子系统包含一条加载器链,只有一个“原生的类加载器”他是jvm实现的一部分,可以用来记载本地jar包内的class,若涉及加载网络上的类,或者是web服务器应用,可以挂接额外的类加载器。

1.2 Java使用一个类所需的准备工作

1.2.1 动态加载

  所有的类都是第一次使用的时候,动态加载到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初始化是在类加载的时候进行

1.2.2 链接

  验证类中的字节码,为静态域分配存储空间。如果必须的话,将解析这个类创建的对其他类的所有引用

1.2.3 初始化

  如果该类存在超类,对其初始化,执行静态初始化器和静态代码块。初始化延迟至 对静态方法或者非静态方法首次引用时执行

回到顶部

二:Java中RTTI  

2.1 :为什么要用到运行时类型信息(就是RTTI)

实际开发中,需求并不是一成不变的(准确来说是经常变),而每新添加需求如果代码的改动量越小肯定是越能提高效率。比如:

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<Shape> 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引用所指向的具体类型(具体怎么用,可以接着往下看)

2.2  :RTTI在运行时如何表示

  Java的核心思想就是:”一切皆是对象“,比如我们对形状抽象,得到圆形类,三角形类。但我们 对这些类在做一次抽象,得到class用于描述类的一般特性

上图是我用画图画的(有点捞见谅),如果我们可以拿到对象的class,我们就可以利用RTTI得到具体的java类。至于如何拿到Class和怎样用Class得到准确的类,继续往下看。

2.3   :  Class对象

   每一个类都存在与之对应的Class对象(保存在.class文件中),根据class得到具体的对象,请参考“第一章节 类的加载和初始化”

2.3.1 Class对象获取的方式

    ①:Class.forName("全限定类名"),得到Class对象,副作用是“如果对应的类没有加载,则会加载类”。找不到会抛出“”ClassNotFoundException”

    ②:如果有对象,可以直接用对象得到与之对应的Class对象  比如

Shape shape  = new Circle();
shape.getClass()

    ③ ;通过类字面常量  : Shape.class.推荐用该方法,第一是编译器会做检查,第二是根除了对forName的调用,提高效率

2.3.2: Class对象的常用方法  

方法名 说明
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。

2.3.3  泛化的Class 

  Class引用表示它所指向的对象的确切类型,java1.5之后,允许开发者对Class引用所指向的Class对象进行限定,也就是添加泛型。

public static void main(String[] args) {
        Class<Integer> 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<Circle> circles = Circle.class;
        Circle circle = circles.newInstance();//第一:泛化class.newInstance可以直接得到具体的对象
        Class<? super Circle> shape = circles.getSuperclass();
        Object shape1 = shape.newInstance();//第二:它的父类,只能用逆变的泛型class接收,newInstance得到的是Object类型
     }
}

2.3 : RTTI形式总结:

  ①:传统的类型转换,比如我们在上边的demo中用到的  shape.draw();

  ②:利用Class,获取运行时信息。

  ③:得到具体的对象

回到顶部

三:Java利用反射获取运行时类型信息

  如果不知道某一个对象引用的具体类型(比如已经上转型的对象),RTTI可以得到。但前提是这个类型编译器必须已知(那些是编译期不可知呢? 磁盘文件或者是网络连接中获取一串代表类的字节码)

跨网络的远程平台上提供创建和运行对象的能力 这被称为 RMI(远程方法调用),下面会具体的介绍一下 RMI的实现方式

  反射提供了一种机制,用于检查可用的方法,并返回方法名,调用方法。

3.1 : 获取的方式

   Java中提供了jar包 ,Java.lang.reflect 和Class对象一起对反射的概念提供支持。

3.1.1 Java.lang.reflect :

  该类库中包含了Field Method  Constructor.这些类型的对象在JVM运行时创建,用于表示未知类里对应的成员。从而:

  ①:用Constructor创建对象,用get set读取Field内的字段

  ②:用Method.invoke()调用方法

  ③:用getFields()、getMethods()、getConstuctors() 得到与之对应的数组

3.1.2 RTTI和RMI的区别

   检查对象,查看对象属于哪个类,加载类的class文件

  ①:RTTI会在编译期打开和检查.class文件

  ②:RMI  在编译期是 看不到.class文件。只能在运行期打开和检查.class文件

3.2 :   动态代理

3.2.1 我假设你对“代理模式”存在一定的了解(还是简单说一下,代理模式就是在接口和实现之前加一层,用于剥离接口的一些额外的操作)下面是代理模式的示例代码:

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);  

    //在转调具体目标对象之后,可以执行一些功能处理
  }
}

3.2.2  动态代理就是:动态的创建代理并动态地处理对其所代理的方法的调用。可以参考 "彻底理解JAVA动态代理" ,"深度剖析JDK动态代理机制"。可以理解为更加灵活的代理模式

①  动态代理使用步骤:

  1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;

  2.通过Proxy.getProxyClass获得动态代理类

  3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)

  4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入

  5.通过代理对象调用目标方法

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();
 }
}  

回到顶部

四: Java反射在实际开发中应用

  通常,我们在一般的业务需求中是用不到反射的,但我们在更加动态的代码时,我们就可以选择反射来实现(例如对象序列化和 JavaBean)。主要的逻辑我在上边都已经说明了,所以接下来 更多的是代码展示:

    实际开发中,在运行时得到Class信息,获取method ,通过反射method.invoke()调用方法。这样做是出于AOP的设计思想。举例来说,我一个传统的web项目,我可以同过http直接传递请求给后台servlet,假如我想添加一个记录日志,或者是在请求的session中添加一个信息,如果只有一个请求,我可以直接在htttp加,但实际上请求会很多,这是我为什么在sevlet外在抽出一层,通过反射调用servlet 

    当然,很多框架其实也为我们提供了拦截的配置(这是后话)

4.1  :在web项目中创建统一的拦截层 

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<Object, Object> 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;
    }

4.2 : 用于webService服务 :和servlet做同意拦截,用反射去调用方法的目的一样(添加一些想要的处理,比如校验用户)。核心也是反射调用方法

(原文地址:  https://www.cnblogs.com/ldh-better/p/7148975.html#_label1_0 )

原文地址:https://www.cnblogs.com/jianyungsun/p/8207044.html

时间: 2024-10-27 08:06:59

[转]Java 反射在实际开发中的应用的相关文章

Java 反射在实际开发中的应用

运行时类型识别(RTTI, Run-Time Type Information)是Java中非常有用的机制,在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型:还有一种,是利用反射机制,在运行时再尝试确定类型信息. 本篇博文会结合Thinking in Java 的demo 和实际开发中碰到的例子,对Java反射和获取类型信息做总体上整理.文章主要分为三块: Java类加载和初始化 Java中RTTI Java利用反射获取运行时类型信息 一:Java类加载和初始

java新手在实际开发中所遇到的问题及解决方法小结,(持续更新遇到的问题)

?从事开发一年有余,想到自己初入公司时的困窘,在此把我记忆中在实际开发中所遇到的问题做一总结性的小结,为自己以后方便查阅,以及后来者遇到相同问题时解决更加方便快捷,希望大家集思广益把自己遇到的问题及解决方法写出来,添砖加瓦.为后来者给予一点帮助! 实用案例 如何使用Java实现汉诺塔问题 Java中定时器的使用方法 Java打印杨辉三角的具体实现代码 Java中如何实现分页功能 Java读取大文件如何高效率 Java中生成随机数的几种方法 Java zip压缩单个文件实现方法 如何计算Java对

[Java Web]2\Web开发中的一些架构

1.企业开发架构: 企业平台开发大量采用B/S开发模式,不管采用何种动态Web实现手段,其操作形式都是一样的,其核心操作的大部分都是围绕着数据库进行的.但是如果使用编程语言进行数据库开发,要涉及很多诸如事务.安全等操作问题,所以现在开发往往要通过中间件进行过渡,即,程序运行在中间件上,并通过中间件进行操作系统的操作,而具体一些相关的处理,如事务.安全等完全由中间件来负责,这样程序员只要完成具体的功能开发即可. 2.Java EE架构: Java EE 是在 Java SE 的基础上构建的,.NE

Java 设计模式之模板方法开发中应用

模板方法差不多是Java设计模式中除单例之外的另一种非常简单也是我们在写程序时非常常用的一种方法了.以至于当你看到模板方法的设计模式时你会感觉到,这不是我在程序中经常用到的方法么. 定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类不可以改变一个算法的结构即可重定义该算法的某些特定步骤. 下面是我总结的模板方法中具体的代码实现通用框架 1:抽象的模板类: package template; public abstract class AbstractClass { protect

Java反射及其在Android中的应用学习总结

一. Java反射机制 Reflection 是Java被视为动态(或准动态)语言的一个关键性质.这个机制同意程序在执行时透过Reflection APIs取得不论什么一个已知名称的class的内部信息,包含其modifiers(诸如public, static 等等).superclass(比如Object).实现之interfaces(比如Serializable).也包含fields和methods的全部信息,并可于执行时改变fields内容或调用methods(包含被声明为private

Java IO在实际开发中的应用

IO是java绕不过去的槛,在开发中io无处不在, 正如同 世界上本没有路,java io写多了,也就知道了大体是什么意思,在读完thinking in java 感觉就更清晰了,结合具体的业务场景,整理一下 ,什么是IO.为什么JAVA要这么设计IO. 先来一道开胃菜 我想要读取控制台输入的字符 解释一下:我从控制台读取一行字符,然后打印一下.这就是一个简单的流了. 整理一下: 就是我先 得到一个用于读取 控制台输入的流,然后 我·打印我得到的东西,这里有个细节就是 流一定得关闭,这是底线,关

Java 数据类型在实际开发中应用

在前边的博文中,我已经介绍了Java核心的容器IO等,现在我来说一下java中的数据类型.在java中,一切东西皆为对象(这句话意思是java中绝大数情况都用对象),极少数不是对象的,也存在与之对应的对象(比如基本数据类型存在与之对应的包装类,数组有List对象可以代替) Java中数据类型 主要有“基本数据类型”.“String”.“引用类型” (基本的引用类型不多做介绍,在下一篇博文中着重介绍“枚举”,也算是引用类型的一种) 一:基本数据类型 1.1基本数据类型的定义 byte.char.i

Android学习探索之Java 8 在Android 开发中的应用

前言: Java 8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新.但是Android上,一直没有看到支持Java8的消息.Android到底会不会支持Java8呢?答案是肯定的,Android N已经开始支持Java 8 了. 关于Java 8 (1.)使用Lambda表达式 Java 8的一大亮点是引入Lambda表达式,使用它设计的代码会更加简洁.当开发者在编写Lambda表达式时

利用java反射实现tomcat运行中添加新类

个人博客地址:http://www.cnblogs.com/wdfwolf3/.转载注明出处,谢谢. Java 反射一个是可以获取程序在运行时刻的内部结构,二是在运行时刻对一个Java对象进行操作.主要用途有以下几点: 1.工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了 2.数据库JDBC中通过Class.forName(Driver)来获得数据库连接驱动 3.分析类文件:得到类中的方法等等,访问一些不能访问的变量或属性(破解别人代码). 之前