Java类型信息与应用--动态代理

Java类型信息与应用--动态代理

本文结构

  • 一、前言
  • 二、为什么需要RTTI
  • 三、RTTI在java中的工作原理
  • 四、类型转化前先做检测
  • 五、动态代理
  • 六、动态代理的不足

一、前言

运行时信息使你可以在程序运行时发现和使用类型信息

Java在运行时识别对象和类的信息的方式:

1.一种是RTTI,它假定我们在编译时已经知道了所有的类型。

2.另一种是“反射“机制,它允许我们在运行时发现和使用类的信息。

这带来的好处是,你可以在程序运行时发现和使用类型信息

二、为什么需要RTTI

以多态为例,如下图基类是Shape(泛型),而派生出来的具体类有Circle,Square和Triangle。

abstract class Shape {
    // this 调用当前类的toString()方法,返回实际的内容
    void draw(){ System.out.println(this + "draw()"); }

    abstract public String toString();
}

class Circle extends Shape {
    public String toString(){ return "Circle"; }
}

class Square extends Shape {
    public String toString(){ return "Square"; }
}

class Triangle extends Shape {
    public String toString(){ return "Triangle"; }
}

public static void main(String[] args){
    // 把Shape对象放入List<Shape>的数组的时候会向上转型为Shape,从而丢失了具体的类型信息
    List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
    // 从数组中取出时,这种容器,实际上所有的元素都当成Object持有,会自动将结果转型为Shape,这就是RTTI的基本的使用。
    for(Shape shape : shapeList){
        shape.draw();
    }
}

打印出以下结果

Circledraw()
Squaredraw()
Triangledraw()

RTTI在运行时识别一个对象类型,Shape对象具体执行什么的代码,由引用所指向的具体对象Circle、Square、或Triangle而决定

三、RTTI在java中的工作原理

在运行时获取类型信息是通过Class对象实现的,java通过Class对象(每个类都有一个Class对象)来执行其RTTI,而Java虚拟机通过“类加载器“的子系统,生成Class对象。

所有的类在第一次使用时,都会动态加载到JVM中。流程如下图:

Created with Rapha?l 2.1.0开始1、第一个对类的静态成员引用2、类加载器查找.class文件3、将Class对象加载进入内存4、用Class对象创建这个类的所有对象结束


class Bird{
     public static final int foot=2;
     static{
         System.out.println("Bird coming");
     }
 }
 class Tiger{
     public static int hair=(int)(Math.random()*100);
     static{
         System.out.println("tiger coming");
     }
 }
public class Zoo {
    public static void main(String[] args) throws ClassNotFoundException{
     Class tiger=Class.forName("com.jaking.RTTI.Tiger");
     System.out.println("Class forName初始化");
     print(Tiger.hair);
     //--------------------------
    Bird cat= new Bird();
    System.out.println("new 初始化");
    print(Bird.foot);
    }

    public static void print(int str) {
        System.out.println(str);
    }

}

打印出以下结果

tiger coming
Class forName初始化
57
Bird coming
new 初始化
2

可以看出通过用forName()或是new构造器都会触发上述流程,

3.2、类字面常量

java有两种获取class对象的方法

  1. Class.getName(类的全限定名);
  2. FancyToy.class

万物皆对象,这句话在java中体现的淋漓尽致,

基本类型也可以通过方法二获取Class对象,如int.class

而通过方法二获取Class对象在加载中会有些区别,我们先看下面代码

 class Dog{
    public static final int foot=4;
    public static final int hair=(int)(Math.random()*100);//runtime
    static{
        System.out.println("dog coming");
    }
}
public class Zoo {
    public static void main(String[] args) throws ClassNotFoundException{
     Class<Dog> dog=Dog.class;//
     System.out.println("类字面常量初始化");
     print(Dog.foot);//compile constant
     print(Dog.hair);//jiazai
     }
    public static void print(int str) {
        System.out.println(str);
    }

}

打印出了

类字面常量初始化
4
dog coming
75

“类字面常量初始化“在“dog coming“前面打印,可以看出通过方法二获取Class对象时,不会立刻初始化,而是被延迟到了对静态方法或非常量静态域进行首次引用才开始执行。

3.3、泛化的Class引用

Class引用表示的是它所指向的对象的确切类型,而该对象便是Class类的一个对象。在JavaSE5中,可以通过泛型对Class引用所指向的Class对象进行限定,并且可以让编译器强制执行额外的类型检查:

Class intCls = int.class;
// 使用泛型限定Class指向的引用
Class<Integer> genIntCls = int.class;
// 没有使用泛型的Clas可以重新赋值为指向任何其他的Class对象
intCls = double.class;
// 下面的编译会出错
// genIntCls = double.class;

使用通配符?放松泛型的限定:

Class<?> genIntCls = int.class;
genIntCls = double.class;//编译通过

? extends Number将引用范围限制为Number及其子类

Class<? extends Number> num = int.class;
// num的引用范围为Number及其子类
num = double.class;
num = Number.class;

? super B将引用范围限制为B及其父类

class A{}
class B extends A{}
class C extends A{}
public class Zoo {
    public static void main(String[] args){
        Class<? super B>  aClass=A.class;//将引用
        Class<? super B>  aClass=B.class;
        //Class<? super B>  aClass=C.class;//编译不通过
    }
}

四、类型转化前先做检测

有些类型如果你强制进行类型转化,如果不匹配的话就会抛出ClassCastException异常,通过关键字instanceof,告诉我们对象是不是某个特定类型的实例,再执行转化。

class A{}
class B extends A{}
class C extends A{}
public class Zoo {
    public static void main(String[] args){
        A a=new B();
        if (a instanceof B) {
            a=(B)a;//上转型为B
        }
    }
}

五、动态代理

5.1静态代理模式

下面是展示代理结构的简单例子:

 interface Subject {
  public void doSomething();
}
 //被代理类
  class RealSub implements Subject {
   public void doSomething(){
     System.out.println( "RealSub call doSomething()" );
   }
 }
 //代理类
 class SubjectProxy implements Subject  {
    Subject subimpl = new RealSub();
    public void doSomething(){
       subimpl.doSomething();
    }
  }

public class ProxyClient {
    public static void main(String[] args) {
        Subject sub = new SubjectProxy();
        sub.doSomething();
    }

}

代理模式的类图

  1. 抽象主题角色(Subject):该类的主要职责是声明真实主题与代理的共同接口方法。
  2. 真实主题角色(RealSub):也称为委托角色或者被代理角色。定义了代理对象所代表的真实对象。
  3. 代理主题角色(SubjectProxy):也叫委托类、代理类。该类持有真实主题类的引用,再实现接口的方法中调用真实主题类中相应的接口方法执行,以此起到代理作用。
  4. 客户类(ProxyClient) :即使用代理类的类型

    ?代理模式又分为静态代理和动态代理。静态代理是由用户创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。动态代理是在程序运行时,通过运用反射机制动态的创建而成。

5.2、动态代理

Java 动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。

5.2.1、Jdk动态代理

?Jdk的动态代理是基于接口的。现在想要为RealSubject这个类创建一个动态代理对象,Jdk主要会做一下工作:

  1. 获取RealSubject上的所有接口列表
  2. 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyN(包名与这些接口的包名相同,生成代理类的类名,格式“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率);
  3. 根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
  4. 创建InvocationHandler实例handler,用来处理Proxy所有方法的调用;
  5. Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象;
  6. Jdk通过java.lang.reflect.Proxy包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance()获取代理对象。

下面为实例代码

package com.jaking.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//抽象主题类B ,JDK的动态代理是基于接口的,所以一定要是interface
interface SubjectA {
    public abstract void request();
}

// 抽象主题类B
interface SubjectB {
    public abstract void doSomething();
}

// 真实主题类A,即被代理类
class RealSubjectA implements SubjectA {
    public void request() {
        System.out.println("RealSubjectA request() ...");
    }
}

// 真实主题类B,即被代理类
class RealSubjectB implements SubjectB {
    public void doSomething() {
        System.out.println("RealSubjectB doSomething() ...");
    }
}

// 动态代理类,实现InvocationHandler接口
class DynamicProxy implements InvocationHandler {
    Object obj = null;

    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    /**
     * 覆盖InvocationHandler接口中的invoke()方法
     *
     * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
     * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊 代码切入的扩展点了。
     *
     * @param proxy
     *            ,表示执行这个方法的代理对象;
     * @param method
     *            ,表示真实对象实际需要执行的方法(关于Method类参见Java的反射机制);
     * @param args
     *            ,表示真实对象实际执行方法时所需的参数。
     */
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        /*
         * before :doSomething();
         */
        Object result = method.invoke(this.obj, args);

        /*
         * after : doSomething();
         */
        return result;
    }
}

// 测试类
public class Client {
    public static void main(String[] args) {

        // 被代理类的实例
        SubjectA realSubjectA = new RealSubjectA();
        // loader,表示类加载器,对于不同来源(系统库或网络等)的类需要不同的类加载器来加载,这是Java安全模型的一部分。
        // 可以使用null来使用默认的加载器;
        ClassLoader loader = realSubjectA.getClass().getClassLoader();
        // interfaces,表示接口或对象的数组,它就是前述代理对象和真实对象都必须共有的父类或者接口;
        Class<?>[] interfaces = realSubjectA.getClass().getInterfaces();
        // handler,表示调用处理器,它必须是实现了InvocationHandler接口的对象,其作用是定义代理对象中需要执行的具体操作。
        InvocationHandler handler = new DynamicProxy(realSubjectA);

        // 获得代理的实例 A
        SubjectA proxyA = (SubjectA) Proxy.newProxyInstance(loader, interfaces,
                handler);

        proxyA.request();
        RealSubjectB realSubjectB = new RealSubjectB();
        // 获得代理的实例 B
        SubjectB proxyB = (SubjectB) Proxy.newProxyInstance(realSubjectB
                .getClass().getClassLoader(), realSubjectB.getClass()
                .getInterfaces(), new DynamicProxy(realSubjectB));

        proxyB.doSomething();

        // 打印生成代理类的类名
        System.out.println(proxyA.getClass().getSimpleName());
        System.out.println(proxyB.getClass().getSimpleName());
    }
}

运行打印出

RealSubjectA request() ...
RealSubjectB doSomething() ...
$Proxy0
$Proxy1

控制台打印出$Proxy0,$Proxy1可以证明$Proxy0和$Proxy1是JVM在运行时生成的动态代理类,这也就是动态代理的核心所在,我们不用为每个被代理类实现一个代理类,只需要实现接口InvocationHandler,并在客户端中调用静态方法Proxy.newProxyInstance( )就能获取到代理类对象

下图为动态代理类$ProxyN的继承图

由图可见,

  1. Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
  2. 被代理的一组接口有以下特点。

    (1)要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。

    (2)这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。

    (3)需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。

    (4)接口的数目不能超过 65535,这是 JVM 设定的限制。

六、动态代理的不足

动态代理的性能会比较差一些。理由很简单,因为反射地分派方法而不是采用内置的虚方法分派,可能有一些性能上的成本,但是通过动态代理可以简化大量代码,大大减低耦合度,如Spring中的AOP,Struts2中的拦截器就是使用动态代理,至于性能与便捷有时需要权衡使用。

时间: 2024-11-03 06:49:01

Java类型信息与应用--动态代理的相关文章

Java的反射机制和动态代理

介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大的功能,可以原生实现AOP中 的方法拦截功能.正如英文单词reflection的含义一样,使用反射API的时候就好像在看一个Java类在水中的倒影一样.知道了Java类的内部 结构之后,就可以与它进行交互,包括创建新的对象和调用对象中的方法等.这种交互方式与直接在源代码中使用的效果是相同的,但是又额外提供了运行时

Thinking in Java -- 类型信息RTTI

Thinking in Java – 类型信息 个人感觉 java 中的比較难的部分了,在看了些netty源代码发现事实上这块很实用. 这章重点是RTTI和反射.先说下自己的理解 RTTI是执行时识别.在c++中是用virtual来实现的,在编译期会忽略对象的详细类型信息,假定我们已经知道,并在执行时详细识别. Java反射机制实在执行状态中,对于随意一个类,都能够知道这个类的全部属性和方法.对于随意一个对象.都能够调用它的随意一个方法和属性,这样的动态获取的信息以及动态调用对 象的方法的功能称

Java基础 -- 深入理解Java类型信息(Class对象)与反射机制

一 RTTI概念 认识Claa对象之前,先来了解一个概念,RTTI(Run-Time Type Identification)运行时类型识别,对于这个词一直是 C++ 中的概念,至于Java中出现RTTI的说法则是源于<Thinking in Java>一书,其作用是在运行时识别一个对象的类型和类的信息,这里分两种: 传统的”RTTI”:它假定我们在编译期已知道了所有类型(在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好): 反射机制,它允许我们在运

java反射机制中的动态代理

java反射机制中的动态代理 动态代理模式及其使用 步骤1:定义一个接口 //接口 interface Subject{ void action(); } 步骤2:定义一个接口的实现类,也就是被代理类 //被代理类 class RealSubject implements Subject { @Override public void action() { System.out.println("我是被代理类,请执行我"); } } 步骤3:定义一个实现InvocationHandle

JAVA类型信息——Class对象(转载)

JAVA类型信息--Class对象 一.RTTI概要 1.类型信息RTTI :即对象和类的信息,例如类的名字.继承的基类.实现的接口等. 2.类型信息的作用:程序员可以在程序运行时发现和使用类型信息. 3.RTTI真正含义:运行时,识别一个对象的类型. 4.如何在程序运行时识别对象和类的信息? 1)传统RTTI:即在编译时已知道了所有的类型. 2)反射机制:在程序运行时发现和使用类的信息. 5.RTTI的使用 import java.util.*;      //List支持泛型 //impor

Java基础之反射和动态代理

1,反射是依赖于Class对象,然后根据Class对象,去操作该类的资源的.Class对象是发射的基石! 问题1:人这类事物用什么表示?汽车这类事物用什么表示>计算机文件用什么表示?有如此多的事物该用什么表示? 答案:Person类,Car类,File类,这么多的类也是一类事物,这类事物用Class表示. 问题2:Person类的对象,我们知道代表一个具体的人.那么Class类的对象,又代表什么? 一个类,在硬盘上表示一个.class文件,JVM启动的时候,把文件加载到内存上,占用一片空间,称为

关于java的一个典型的动态代理

今天看书的一个过程中,看到一个动态代理看下代码 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxy { public static void testDynamicProxy(){ Calculator calculator = new CalculatorImpl(); LogH

Java学习之:JDK动态代理与CGLIB动态代理

代理的概念:简单的理解就是通过为某一个对象创建一个代理对象,我们不直接引用原本的对象,而是由创建的代理对象来控制对原对象的引用. 动态代理:是指在程序运行时由Java反射机制动态生成,无需手动编写代码.动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类. 代理原理:代理对象内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象.同时,代理对象可以在执行真实对象操作时,附加其他的操作

java 27 - 9 反射之 动态代理的概述和实现

代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象. 举例:春季回家买票让人代买 动态代理: 在程序运行过程中产生的这个对象 而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理 在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象.JDK提供的代理只能针对接口做代理.我们有更强大的代理cglib Proxy类中的方法创建动态代