Java 反射教程

Java 反射机制教程

Java 反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)检查类,接口,变量以及方法的信息。反射还可以让我们在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值。

具体的理论细节可以通过某度进行搜索. 可以查询到更多理论性的解答..今天我们就以代码的形式进行科普

我们先进行一个简单的例子介绍

Method[] methods = MyClass.class.getMethods();
for(Method method : methods){
    System.out.println("method = " + method.getName());
}

在这个例子中通过调用 MyClass 类的 class 属性获取对应的 Class 类的对象,通过这个 Class 类的对象获取 MyObject 类中的方法集合。迭代这个方法的集合并且打印每个方法的名字。

java 类

使用 Java 反射机制可以在运行时期检查 Java 类的信息,检查 Java 类的信息往往是你在使用 Java 反射机制的时候所做的第一件事情,通过获取类的信息你可以获取以下相关的内容:

  • Class 对象
  • 类名
  • 修饰符
  • 包信息
  • 父类
  • 实现的接口
  • 构造器
  • 方法
  • 变量
  • 注解
    除了上述这些内容,还有很多的信息你可以通过反射机制获得,请参考 官方文档.

Class 对象

在你想检查一个类的信息之前,你首先需要获取类的 Class 对象。Java 中的所有类型包括基本类型(int, long, float等等),即使是数组都有与之关联的 Class 类的对象。如果你在编译期知道一个类的名字的话,那么你可以使用如下的方式获取一个类的 Class 对象。

Class myClass = MyClass.class;

如果你在编译期不知道类的名字,但是你可以在运行期获得到类名的字符串,那么你则可以这么做来获取 Class 对象:

String className = ... ;//在运行期获取的类名字符串
Class class = Class.forName(className);

在使用 Class.forName() 方法时,你必须提供一个类的全名,这个全名包括类所在的包的名字。例如 MyClass 类位于 com.enoch.driver 包,那么他的全名就是 com.enoch.driver.MyClass。 如果在调用Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出ClassNotFoundException。

大家看到Class.forName()的同学应该都会想到JDBC连接数据库时,驱动的调用就是通过此方法

类名

你可以从 Class 对象中获取两个版本的类名。
通过 getName() 方法返回类的全限定类名(包含包名)

Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String className = aClass.getName();

如果你仅仅只是想获取类的名字(不包含包名),那么你可以使用 getSimpleName()方法:

Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String simpleClassName = aClass.getSimpleName();

修饰符

可以通过 Class 对象来访问一个类的修饰符, 即public,private,static 等等的关键字,你可以使用如下方法来获取类的修饰符:

Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
int modifiers = aClass.getModifiers();

修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。 可以使用 java.lang.reflect.Modifier 类中的方法来检查修饰符的类型
Modifier.isAbstract(int modifiers); Modifier.isFinal(int modifiers); Modifier.isInterface(int modifiers); Modifier.isNative(int modifiers); Modifier.isPrivate(int modifiers); Modifier.isProtected(int modifiers); Modifier.isPublic(int modifiers); Modifier.isStatic(int modifiers); Modifier.isStrict(int modifiers); Modifier.isSynchronized(int modifiers); Modifier.isTransient(int modifiers); Modifier.isVolatile(int modifiers);

包信息

可以使用 Class 对象通过如下的方式获取包信息:

Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
Package package = aClass.getPackage();

通过 Package 对象你可以获取包的相关信息,比如包名,你也可以通过 Manifest 文件访问位于编译路径下 jar 包的指定信息,比如你可以在 Manifest 文件中指定包的版本编号。更多的 Package 类信息可以阅读 java.lang.Package。

父类

通过 Class 对象你可以访问类的父类,如下例:

Class superclass = aClass.getSuperclass();

可以看到 superclass 对象其实就是一个 Class 类的实例,所以你可以继续在这个对象上进行反射操作。

实现的接口

可以通过如下方式获取指定类所实现的接口集合:

Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
Class[] interfaces = aClass.getInterfaces();

由于一个类可以实现多个接口,因此 getInterfaces(); 方法返回一个 Class 数组,在 Java 中接口同样有对应的 Class 对象。 注意:getInterfaces() 方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的 Class 集合中的,尽管实际上当前类其实已经实现了父类接口。

构造器

你可以通过如下方式访问一个类的构造方法:

Constructor[] constructors = aClass.getConstructors();

方法

你可以通过如下方式访问一个类的所有方法:

Method[] method = aClass.getMethods();

变量

你可以通过如下方式访问一个类的成员变量:

Field[] method = aClass.getFields();

注解

你可以通过如下方式访问一个类的注解:

Annotation[] annotations = aClass.getAnnotations();

获取 Constructor 对象

我们可以通 过 Class 对象来获取 Constructor 类的实例:

Class aClass = ...//获取Class对象
Constructor[] constructors = aClass.getConstructors();

返回的 Constructor 数组包含每一个声明为公有的(Public)构造方法。 如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为 String 类型:

Class aClass = ...//获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});

构造方法参数

你可以通过如下方式获取指定构造方法的方法参数信息:

Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();

利用 Constructor 对象实例化一个类

你可以通过如下方法实例化一个类:

Constructor constructor = MyClass.class.getConstructor(String.class);
MyClass myObject = (MyClass)
 constructor.newInstance("一个构造函数");

constructor.newInstance()方法的方法参数是一个可变参数列表,但是当你调用构造方法的时候你必须提供精确的参数,即形参与实参必须一一对应。在这个例子中构造方法需要一个 String 类型的参数,那我们在调用 newInstance 方法的时候就必须传入一个 String 类型的参数。

内容索引

  • 获取 Field 对象
  • 变量名称
  • 变量类型
  • 获取或设置(get/set)变量值

获取 Field 对象

可以通过 Class 对象获取 Field 对象,如下例:

Class aClass = ...//获取Class对象
Field[] methods = aClass.getFields();

返回的 Field 对象数组包含了指定类中声明为公有的(public)的所有变量集合。 如果你知道你要访问的变量名称,你可以通过如下的方式获取指定的变量:

Class  aClass = MyClass.class
Field field = aClass.getField("someField");

上面的例子返回的Field类的实例对应的就是在 MyClass 类中声明的名为 someField 的成员变量,就是这样:

public class MyClass{
  public String someField = null;
}

在调用 getField()方法时,如果根据给定的方法参数没有找到对应的变量,那么就会抛出 NoSuchFieldException。

变量名称

一旦你获取了 Field 实例,你可以通过调用 Field.getName()方法获取他的变量名称,如下例:

Field field = ... //获取Field对象
String fieldName = field.getName();

变量类型

Field field = aClass.getField("someField");
Object fieldType = field.getType();

获取或设置(get/set)变量值

一旦你获得了一个 Field 的引用,你就可以通过调用 Field.get()或 Field.set()方法,获取或者设置变量的值,如下例:

Class  aClass = MyClass.class
Field field = aClass.getField("someField");
MyClass objectInstance = new MyClass();
Object value = field.get(objectInstance);
field.set(objetInstance, value);

传入 Field.get()/Field.set()方法的参数 objetInstance 应该是拥有指定变量的类的实例。在上述的例子中传入的参数是 MyObjec t类的实例,是因为 someField 是 MyObject 类的实例。 如果变量是静态变量的话(public static)那么在调用 Field.get()/Field.set()方法的时候传入 null 做为参数而不用传递拥有该变量的类的实例。(译者注:你如果传入拥有该变量的类的实例也可以得到相同的结果)

内容索引

  • 获取 Method 对象
  • 方法参数以及返回类型
  • 通过 Method 对象调用方法

使用 Java 反射你可以在运行期检查一个方法的信息以及在运行期调用这个方法,通过使用 java.lang.reflect.Method 类就可以实现上述功能。在本节会带你深入了解 Method 对象的信息。

获取 Method 对象

可以通过 Class 对象获取 Method 对象,如下例:

Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();

返回的 Method 对象数组包含了指定类中声明为公有的(public)的所有变量集合。

如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,下面这个例子中返回方法对象名称是“doSomething”,他的方法参数是 String 类型:

Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});

如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出 NoSuchMethodException。 如果你想要获取的方法没有参数,那么在调用 getMethod()方法时第二个参数传入 null 即可,就像这样:

Class  aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", null);

方法参数以及返回类型

你可以获取指定方法的方法参数是哪些:

Method method = ... //获取Class对象
Class[] parameterTypes = method.getParameterTypes();

你可以获取指定方法的返回类型:

Method method = ... //获取Class对象
Class returnType = method.getReturnType();

通过 Method 对象调用方法

你可以通过如下方式来调用一个方法:

//获取一个方法名为doSomesthing,参数类型为String的方法

Method method = MyClass.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");

传入的 null 参数是你要调用方法的对象,如果是一个静态方法调用的话则可以用 null 代替指定对象作为 invoke()的参数,在上面这个例子中,如果 doSomething 不是静态方法的话,你就要传入有效的 MyClass 实例而不是 null。 Method.invoke(Object target, Object … parameters)方法的第二个参数是一个可变参数列表,但是你必须要传入与你要调用方法的形参一一对应的实参。就像上个例子那样,方法需要 String 类型的参数,那我们必须要传入一个字符串。

Java 注解

内容索引

  • 什么是注解
  • 类注解
  • 方法注解
  • 参数注解
  • 变量注解

利用 Java 反射机制可以在运行期获取 Java 类的注解信息。

什么是注解

注解是 Java 5 的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用 Java 反射机制进行处理。下面是一个类注解的例子:

@MyAnnotation(name="someName",  value = "Hello World")
public class TheClass {
}

在 TheClass 类定义的上面有一个@MyAnnotation 的注解。注解的定义与接口的定义相似,下面是MyAnnotation注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)

public @interface MyAnnotation {
  public String name();
  public String value();
}

在 interface 前面的@符号表名这是一个注解,一旦你定义了一个注解之后你就可以将其应用到你的代码中,就像之前我们的那个例子那样。 在注解定义中的两个指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),说明了这个注解该如何使用。 @Retention(RetentionPolicy.RUNTIME)表示这个注解可以在运行期通过反射访问。如果你没有在注解定义的时候使用这个指示那么这个注解的信息不会保留到运行期,这样反射就无法获取它的信息。 @Target(ElementType.TYPE) 表示这个注解只能用在类型上面(比如类跟接口)。你同样可以把Type改为Field或者Method,或者你可以不用这个指示,这样的话你的注解在类,方法和变量上就都可以使用了。 关于 Java 注解更详细的讲解可以访问 Java Annotations tutorial。

类注解

你可以在运行期访问类,方法或者变量的注解信息,下是一个访问类注解的例子:

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你还可以像下面这样指定访问一个类的注解:

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

方法注解

下面是一个方法注解的例子:

public class TheClass {
  @MyAnnotation(name="someName",  value = "Hello World")
  public void doSomething(){}
}

你可以像这样访问方法注解:

Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("name: " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
}

你可以像这样访问指定的方法注解:

Method method = ... // 获取方法对象
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

参数注解

方法参数也可以添加注解,就像下面这样:

public class TheClass {
  public static void doSomethingElse(
        @MyAnnotation(name="aName", value="aValue") String parameter){
  }
}

你可以通过 Method对象来访问方法参数注解:

Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
  Class parameterType = parameterTypes[i++];

  for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
        MyAnnotation myAnnotation = (MyAnnotation) annotation;
        System.out.println("param: " + parameterType.getName());
        System.out.println("name : " + myAnnotation.name());
        System.out.println("value: " + myAnnotation.value());
    }
  }
}

需要注意的是 Method.getParameterAnnotations()方法返回一个注解类型的二维数组,每一个方法的参数包含一个注解数组。

变量注解

下面是一个变量注解的例子:

public class TheClass {

  @MyAnnotation(name="someName",  value = "Hello World")
  public String myField = null;
}

你可以像这样来访问变量的注解:

Field field = ... //获取方法对象
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){
   if(annotation instanceof MyAnnotation){
     MyAnnotation myAnnotation = (MyAnnotation) annotation;
     System.out.println("name: " + myAnnotation.name());
     System.out.println("value: " + myAnnotation.value());
   }
}

你可以像这样访问指定的变量注解:

Field field = ...//获取方法对象
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){
   MyAnnotation myAnnotation = (MyAnnotation) annotation;
   System.out.println("name: " + myAnnotation.name());
   System.out.println("value: " + myAnnotation.value());
}

Java 泛型

内容索引

  • 运用泛型反射的经验法则
  • 泛型方法返回类型
  • 泛型方法参数类型
  • 泛型变量类型

泛型方法返回类型

 public class MyClass {

  protected List stringList = ...;

  public List getStringList(){
    return this.stringList;
  }
}

我们可以获取 getStringList()方法的泛型返回类型,换句话说,我们可以检测到 getStringList()方法返回的是 List 而不仅仅只是一个 List。如下例:

Method method = MyClass.class.getMethod("getStringList", null);

Type returnType = method.getGenericReturnType();

if(returnType instanceof ParameterizedType){
    ParameterizedType type = (ParameterizedType) returnType;
    Type[] typeArguments = type.getActualTypeArguments();
    for(Type typeArgument : typeArguments){
        Class typeArgClass = (Class) typeArgument;
        System.out.println("typeArgClass = " + typeArgClass);
    }
}

这段代码会打印出 “typeArgClass = java.lang.String”,Type[]数组typeArguments 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。

泛型方法参数类型

你同样可以通过反射来获取方法参数的泛型类型,下面这个例子定义了一个类,这个类中的方法的参数是一个被参数化的 List:

public class MyClass {
  protected List stringList = ...;

  public void setStringList(List list){
    this.stringList = list;
  }
}

你可以像这样来获取方法的泛型参数:

method = Myclass.class.getMethod("setStringList", List.class);

Type[] genericParameterTypes = method.getGenericParameterTypes();

for(Type genericParameterType : genericParameterTypes){
    if(genericParameterType instanceof ParameterizedType){
        ParameterizedType aType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = aType.getActualTypeArguments();
        for(Type parameterArgType : parameterArgTypes){
            Class parameterArgClass = (Class) parameterArgType;
            System.out.println("parameterArgClass = " + parameterArgClass);
        }
    }
}

这段代码会打印出”parameterArgType = java.lang.String”。Type[]数组 parameterArgTypes 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了Type接口。

泛型变量类型

同样可以通过反射来访问公有(Public)变量的泛型类型,无论这个变量是一个类的静态成员变量或是实例成员变量。你可以在“Java Reflection: Fields”中阅读到有关如何获取 Field 对象的相关内容。这是之前的一个例子,一个定义了一个名为 stringList 的成员变量的类。

public class MyClass {
  public List stringList = ...;
}

Field field = MyClass.class.getField("stringList");

Type genericFieldType = field.getGenericType();

if(genericFieldType instanceof ParameterizedType){
    ParameterizedType aType = (ParameterizedType) genericFieldType;
    Type[] fieldArgTypes = aType.getActualTypeArguments();
    for(Type fieldArgType : fieldArgTypes){
        Class fieldArgClass = (Class) fieldArgType;
        System.out.println("fieldArgClass = " + fieldArgClass);
    }
}

这段代码会打印出”fieldArgClass = java.lang.String”。Type[]数组 fieldArgClass 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。

Java 动态类加载与重载

  • 类加载器
  • 类加载体系
  • 类加载
  • 动态类加载
  • 动态类重载
  • 自定义类重载
  • 类加载/重载示例
    Java 允许你在运行期动态加载和重载类,但是这个功能并没有像人们希望的那么简单直接。这篇文章将阐述在 Java 中如何加载以及重载类。 你可能会质疑为什么 Java 动态类加载特性是 Java 反射机制的一部分而不是 Java 核心平台的一部分。不管怎样,这篇文章被放到了 Java 反射系列里面而且也没有更好的系列来包含它了。

类加载器

所有 Java 应用中的类都是被 java.lang.ClassLoader 类的一系列子类加载的。因此要想动态加载类的话也必须使用 java.lang.ClassLoader 的子类。

一个类一旦被加载时,这个类引用的所有类也同时会被加载。类加载过程是一个递归的模式,所有相关的类都会被加载。但并不一定是一个应用里面所有类都会被加载,与这个被加载类的引用链无关的类是不会被加载的,直到有引用关系的时候它们才会被加载。

类加载体系

在 Java 中类加载是一个有序的体系。当你新创建一个标准的 Java 类加载器时你必须提供它的父加载器。当一个类加载器被调用来加载一个类的时候,首先会调用这个加载器的父加载器来加载。如果父加载器无法找到这个类,这时候这个加载器才会尝试去加载这个类。

类加载

类加载器加载类的顺序如下: 1、检查这个类是否已经被加载。 2、如果没有被加载,则首先调用父加载器加载。 3、如果父加载器不能加载这个类,则尝试加载这个类。

当你实现一个有重载类功能的类加载器,它的顺序与上述会有些不同。类重载不会请求的他的父加载器来进行加载。在后面的段落会进行讲解。

动态类加载

动态加载一个类十分简单。你要做的就是获取一个类加载器然后调用它的 loadClass()方法。下面是个例子:


  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.enoch.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

动态类重载

动态类重载有一点复杂。Java 内置的类加载器在加载一个类之前会检查它是否已经被加载。因此重载一个类是无法使用 Java 内置的类加载器的,如果想要重载一个类你需要手动继承 ClassLoader。

在你定制 ClassLoader 的子类之后,你还有一些事需要做。所有被加载的类都需要被链接。这个过程是通过 ClassLoader.resolve()方法来完成的。由于这是一个 final 方法,因此这个方法在 ClassLoader 的子类中是无法被重写的。resolve()方法是不会允许给定的 ClassLoader 实例链接一个类两次。所以每当你想要重载一个类的时候你都需要使用一个新的 ClassLoader 的子类。你在设计类重载功能的时候这是必要的条件。

自定义类重载

在前面已经说过你不能使用已经加载过类的类加载器来重载一个类。因此你需要其他的 ClassLoader 实例来重载这个类。但是这又带来了一些新的挑战。

所有被加载到 Java 应用中的类都以类的全名(包名 + 类名)作为一个唯一标识来让 ClassLoader 实例来加载。这意味着,类 MyObject 被类加载器 A 加载,如果类加载器 B 又加载了 MyObject 类,那么两个加载器加载出来的类是不同的。看看下面的代码:

MyClass object = (MyClass)
    myClassReloadingFactory.newInstance("com.enoch.MyClass");

MyObject 类在上面那段代码中被引用,它的变量名是 object。这就导致了 MyObject 这个类会被这段代码所在类的类加载器所加载。

如果 myClassReloadingFactory 工厂对象使用不同的类加载器重载 MyClass 类,你不能把重载的 MyClass t类的实例转换(cast)到类型为 MyClass 的对象变量。一旦 MyClass 类分别被两个类加载器加载,那么它就会被认为是两个不同的类,尽管它们的类的全名是完全一样的。你如果尝试把这两个类的实例进行转换就会报 ClassCastException。 你可以解决这个限制,不过你需要从以下两个方面修改你的代码: 1、标记这个变量类型为一个接口,然后只重载这个接口的实现类。 2、标记这个变量类型为一个超类,然后只重载这个超类的子类。

请看下面这两个例子:

yObjectInterface object = (MyObjectInterface)
    myClassReloadingFactory.newInstance("com.enoch.MyClass");

MyObjectSuperclass object = (MyObjectSuperclass)
    myClassReloadingFactory.newInstance("com.enoch.MyClass");

只要保证变量的类型是超类或者接口,这两个方法就可以正常运行,当它们的子类或是实现类被重载的时候超类跟接口是不会被重载的。

为了保证这种方式可以运行你需要手动实现类加载器然后使得这些接口或超类可以被它的父加载器加载。当你的类加载器加载 MyObject 类时,超类 MyObjectSuperclass 或者接口 MyObjectSuperclass 也会被加载,因为它们是 MyObject 的依赖。你的类加载器必须要代理这些类的加载到同一个类加载器,这个类加载器加载这个包括接口或者超类的类。

Java 动态代理

  • 创建代理
  • InvocationHandler 接口

常见用例

  • 数据库连接以及事物管理
  • 单元测试中的动态 Mock 对象
  • 自定义工厂与依赖注入(DI)容器之间的适配器
  • 类似 AOP 的方法拦截器

利用Java反射机制你可以在运行期动态的创建接口的实现。 java.lang.reflect.Proxy 类就可以实现这一功能。这个类的名字(译者注:Proxy 意思为代理)就是为什么把动态接口实现叫做动态代理。动态的代理的用途十分广泛,比如数据库连接和事物管理(transaction management)还有单元测试时用到的动态 mock 对象以及 AOP 中的方法拦截功能等等都使用到了动态代理。

创建代理

你可以通过使用 Proxy.newProxyInstance()方法创建动态代理。 newProxyInstance()方法有三个参数: 1、类加载器(ClassLoader)用来加载动态代理类。 2、一个要实现的接口的数组。 3、一个 InvocationHandler 把所有方法的调用都转到代理上。 如下例:

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                            MyInterface.class.getClassLoader(),
                            new Class[] { MyInterface.class },
                            handler);

在执行完这段代码之后,变量 proxy 包含一个 MyInterface 接口的的动态实现。所有对 proxy 的调用都被转向到实现了 InvocationHandler 接口的 handler 上。有关 InvocationHandler 的内容会在下一段介绍。

InvocationHandler 接口

在前面提到了当你调用 Proxy.newProxyInstance()方法时,你必须要传入一个 InvocationHandler 接口的实现。所有对动态代理对象的方法调用都会被转向到 InvocationHandler 接口的实现上,下面是 InvocationHandler 接口的定义:

public interface InvocationHandler{
  Object invoke(Object proxy, Method method, Object[] args)
         throws Throwable;
}

下面是它的实现类的定义:

public class MyInvocationHandler implements InvocationHandler{

  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    //do something "dynamic"
  }
}

传入 invoke()方法中的 proxy 参数是实现要代理接口的动态代理对象。通常你是不需要他的。

invoke()方法中的 Method 对象参数代表了被动态代理的接口中要调用的方法,从这个 method 对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。关于这部分内容可以查阅之前有关 Method 的文章。

Object 数组参数包含了被动态代理的方法需要的方法参数。注意:原生数据类型(如int,long等等)方法参数传入等价的包装对象(如Integer, Long等等)。

数据库连接以及事物管理

Spring 框架中有一个事物代理可以让你提交/回滚一个事物。它的具体原理在 Advanced Connection and Transaction Demarcation and Propagation一文中有详细描述,所以在这里我就简短的描述一下,方法调用序列如下:

web controller --> proxy.execute(...);
  proxy --> connection.setAutoCommit(false);
  proxy --> realAction.execute();
    realAction does database work
  proxy --> connection.commit();

自定义工厂与依赖注入(DI)容器之间的适配器

依赖注入容器 Butterfly Container 有一个非常强大的特性可以让你把整个容器注入到这个容器生成的 bean 中。但是,如果你不想依赖这个容器的接口,这个容器可以适配你自己定义的工厂接口。你仅仅需要这个接口而不是接口的实现,这样这个工厂接口和你的类看起来就像这样:

public interface IMyFactory {
  Bean   bean1();
  Person person();
  ...
}

public class MyAction{

  protected IMyFactory myFactory= null;

  public MyAction(IMyFactory factory){
    this.myFactory = factory;
  }

  public void execute(){
    Bean bean = this.myFactory.bean();
    Person person = this.myFactory.person();
  }

}

当 MyAction 类调用通过容器注入到构造方法中的 IMyFactory 实例的方法时,这个方法调用实际先调用了 IContainer.instance()方法,这个方法可以让你从容器中获取实例。这样这个对象可以把 Butterfly Container 容器在运行期当成一个工厂使用,比起在创建这个类的时候进行注入,这种方式显然更好。而且这种方法没有依赖到 Butterfly Container 中的任何接口。

类似 AOP 的方法拦截器

Spring 框架可以拦截指定 bean 的方法调用,你只需提供这个 bean 继承的接口。Spring 使用动态代理来包装 bean。所有对 bean 中方法的调用都会被代理拦截。代理可以判断在调用实际方法之前是否需要调用其他方法或者调用其他对象的方法,还可以在 bean 的方法调用完毕之后再调用其他的代理方法。

你可能感兴趣的:(Java 反射教程)