Java Reflection

记录一下反射的基本用法。反射其实很简单,就是能够在让代码在运行时候使用其他类。本篇内容基本来自这里

Class对象

如果需要对一个类使用反射,那么首先需要获得这个类的class对象。如一个MyClass的类:

Class classObj = MyClass.class;

就可以获得这个类的class对象。另外一种来获得Class对象的方式是使用Class.forName(String classsname),往这函数中传入的是一个类的全名,包括前面的包名。如上面的MyClass如果在com.foo.test.Myclass。代码如下:

Class classobj = Class.forName("com.foo.test.Myclass");

如果想获取一个Class的全名。可以使用如下代码:

Class aClass = ... //obtain Class object. See prev. section
String className = aClass.getName();

PS:Java中的任何类型都有class对象,array,primitive type都有。

修饰符(Modifier)

修饰符就是一些关键词。比如说常见的,private,public,static,final等等。通过以下代码来获得修饰符:

Class  aClass = ... //obtain Class object. See prev. section
int modifiers = aClass.getModifiers();

int中的各个bit保存着各个修饰符的信息。假设,第一个bit是保存是否为public,1表示是public,0表示不是。

Class foo = MyTestClass.class;
int modifiers = foo.getModifiers();
boolean result = Modifier.isPrivate(modifiers);
System.out.println(result);

还有其他很多比较常见的api。下面主要以代码举例。

父类:

Class superclass = aClass.getSuperclass();

因为在java中只单继承,所以getSuperclass()

所实现的接口:

Class  aClass = ... //obtain Class object. See prev. section
Class[] interfaces = aClass.getInterfaces();

PS:

Class[]中放着的是当前类直接实现的接口。如果当前类继承了某个类,然后父类实现了某个接口。这个接口信息并不会返回到Class[]中。如下代码来说明这个问题。

interface  Hello{
    void sayHello();
}
class Foo implements Hello{
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}
class Foo1 extends Foo {
}
public class Test1 {
    public static void main(String[] args) {
        Class<Foo1> foo1 = Foo1.class;
        Class[] bar = foo1.getInterfaces();
        System.out.println(bar.length); //输出为0
    }
}

虽然在功能上Foo1也实现了Hello接口,但是getInterfaces()中并没有这个信息。所以如果要知道某个类是否实现了某个接口,就需要查看父类。

Constructors

一个类往往有多个构造方法,所以这里返回的也是一个数组。

Constructor[] constructors = aClass.getConstructors();

Methods

Method[] method = aClass.getMethods();

Fields

Field[] method = aClass.getFields();

Annotations

 Annotation[] annotations = aClass.getAnnotations();

Constructors

前面说过可以通过getConstructors()来获得有所有的构造函数。如果知道某个构造函数的参数列表,也可以通过如下这种方式来获得该构造函数:

Class aClass = ...//obtain class object
Constructor constructor =
        aClass.getConstructor(new Class[]{String.class});

上述代码的意思是:获得参数为String的构造函数

获得构造函数的参数列表

构造函数往往具有多个函数,所以很自然地,getParameterTypes()返回的是一个Class的数组。

Constructor constructor = ... // obtain constructor - see above
Class[] parameterTypes = constructor.getParameterTypes();

初始化一个对象

通过调用newInstance()来初始化一个对象。

Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject)
        constructor.newInstance("constructor-arg1");

Fields

通过getFields()获得该类的所有成员变量。类当中都有多个成员变量,所以getFields()返回的的也是一个数组。

Class aClass = ...//obtain class object
Field[] fields = aClass.getFields();

Field[]中只有声明为public的成员变量。如下代码:

class Foo {
    public String name;
    private int age;
    public static final int size = 12;
}

public class Test1 {
    public static void main(String[] args) {
        Class foo = Foo.class;
        Field[] fields = foo.getFields();
        for(Field field:fields) {
            System.out.println(field.getName());
        }
    }
}
//只能输出name和size,age为private所以没有输出。

如果知道某个Field的名字,可以通过getField()来获得该Field。

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

Getting and setting field values

class Foo {
    public String name;

    public Foo(String name) {
        this.name = name;
    }
}

public class Test1 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Foo foo = new Foo("jack");
        Class bar = foo.getClass();
        Field name = bar.getField("name");
        Object value = name.get(foo);
        System.out.println((String)value);
        name.set(foo,"allen");
        System.out.println(foo.name);

    }
}

通过get()来获得Field的值,通过set()来设置值。这两个函数中和都需要传入,持有着该Field的对象。

PS:对于静态(static)成员变量来说,直接传入null。如name.get(null);

Methods

与前面的内容相类似,通过如下代码来获得一个类中的Methods。

Class aClass = ...//obtain class object
Method[] methods = aClass.getMethods();

和Field的一样,getMethods()只能获得声明为public的函数。

如果知道某个method的参数列表以及函数名字。可以直接通过getMethod()来获得该函数的Method对象,代码如下:

Class  aClass = ...//obtain class object
Method method =
    aClass.getMethod("doSomething", new Class[]{String.class});

因为Java支持函数的重载,所以我们不能简单的使用函数名来获得method,还需要加入参数列表信息来区分不同的method。

参数列表以及返回值

获得method的参数列表

Method method = ... // obtain method - see above
Class[] parameterTypes = method.getParameterTypes();

获得method的返回值类型。

Method method = ... // obtain method - see above
Class returnType = method.getReturnType();

通过method来调用函数

我们将反射看作是正常代码的逆过程。那么这些代码就更容易理解一些。如果我们要调用某个类对象的函数。就需要往invoke函数中传入该对象。以及与其相关的参数列表。如果调用的是静态函数,那么invoke的第一个参数就是为null。如下所示:

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

访问Private

前面说的一些getMethod()和getField()函数,只能返回public的。但是访问private的也是可以的

访问private的成员变量:

To access a private field you will need to call the Class.getDeclaredField(String name) or Class.getDeclaredFields() method

访问private的函数:

To access a private method you will need to call the Class.getDeclaredMethod(String name, Class[] parameterTypes) or Class.getDeclaredMethods() method.

Annotation

本篇并不是介绍注解的文章,入门文章可以看Oracle的官方文档:官方文档。在此,只是稍微的过一下注解有关的内容。

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

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

定义一个注解和定义一个接口差不多。@Retention和@Target是用到其他注解的。上述代码中,我们使用@Retention和@Target来修饰了@MyAnnotation这个注解。下面是来自官方文档中关于这种注解的解释:

@Retention: 注解的作用类型,只有注解被声明为RUNTIME的时候,才可以在运行的时候使用

  • RetentionPolicy.SOURCE – The marked annotation is retained only in the source level and is ignored by the compiler.
  • RetentionPolicy.CLASS – The marked annotation is retained by the compiler at compile time, but is ignored by the Java Virtual Machine (JVM).
  • RetentionPolicy.RUNTIME – The marked annotation is retained by the JVM so it can be used by the runtime environment.

@Target: 指明了注解可以使用在何种对象上。

  • ElementType.ANNOTATION_TYPE can be applied to an annotation type.
  • ElementType.CONSTRUCTOR can be applied to a constructor.
  • ElementType.FIELD can be applied to a field or property.
  • ElementType.LOCAL_VARIABLE can be applied to a local variable.
  • ElementType.METHOD can be applied to a method-level annotation.
  • ElementType.PACKAGE can be applied to a package declaration.
  • ElementType.PARAMETER can be applied to the parameters of a method.
  • ElementType.TYPE can be applied to any element of a class.

获取一个类的注解的内容

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对象的某一种注解。

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

其他的与之类似,都可以使用上述两种方法来获得修饰的注解。具体见这里

Dynamic Proxy

在我的观点看来,动态代理比较直观的一个好处就是。在不修改原代码的基础上,或者说也不需要加入很多的代码,可以拓展原来的类或者函数的功能。

按照官方文档的解释,动态代理类的是一个在运行时实现指定接口的类,任何调用接口函数的代理类对象的函数调用都会被以一种统一的形式分发到另外一个对象。代理对象的函数调用会被分发到代理对象的invocation handler去。官网说的有点绕,接下来结合代码来解释应该就可以理解这段话的意思。

一段实例的代码:

interface Foo {
     void sayHello();
}

class FooImpl implements Foo{
    @Override
    public void sayHello() {
        System.out.println("Hello");
    }
}

如果我们想在FooImpl中加入一点功能,在输出Hello前后都输出代码的运行时间。很自然地想法,我们会在sayHello()中直接插入代码。如下:

public void sayHello() {
    System.out.println(System.currentTimeMillis());
    System.out.println("Hello");
    System.out.println(System.currentTimeMillis());
}

所以呢,这里有一个问题就是,我们修改了原来的代码。于是,我们希望有一种不修改源代码的方式,来拓展一下原来函数的功能。动态代理就出现了。

使用动态代理的代码如下:

interface Foo {
     void sayHello();
}
class FooImpl {
        @Override
    public void sayHello() {
        System.out.println("Hello");
    }
}

class MyHandler implements InvocationHandler {
    Object target;

    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(target,args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}    

public class Test1 {
    public static void main(String[] args)  {
        Foo prox = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                new Class[]{Foo.class},new MyHandler(new FooImpl()) );
        prox.sayHello();

    }
}

理解这一段代码的需要记住前面说过的一句话:对于代理对象的函数调用都会被转发到代理对象的Invocation handler去。所以类MyHanlder就是这个作用。接下来解释一下其他代码的意思:

Foo prox = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                        new Class[]{Foo.class},
                                        new MyHandler(new FooImpl()));

这里代码的意思是,调用`Proxy.newProxyInstance`返回一个代理对象(proxy)。该代理对象会在运行时来实现一个或者多个接口,这些接口都在第二个中参数中传入,我们的例子中就是new class[] {Foo.class}。因为一个类可以实现多个接口,所以在这里传入的是一个数组。对于接口中这些函数的调用,都会转发到一个Handler当中,也就是第三个参数。new MyHandler(new FooImpl())。当调用接口Foo当中的函数的时候,会转发到Handler中的invoke函数。所以,我们在invoke中添加一些代码,就相当于在原来的函数上拓展了函数的功能

 public Object invoke(Object proxy, Method method, Object[] args)

接下来解释一下这个函数中几个参数的意思。proxy指的是代理对象,如果使用proxy.getClass().getName()来获得这个类的名称,输出的结果并不是Foo,而是一个$Proxy这样的名字,因为代理类是来自java.lang.reflect.Proxy.的。method指向的是被调用的函数。在这里是sayHello()函数。args值得就是method需要的参数了。还有一个需要说明的是返回值:如果接口中定义的返回值是int(primitive type),那么在invoke的返回值,就会返回的是包装类(wrapper class)。如果不是,那么就需要和接口定义中的类型相匹配。如果返回值为null,但是接口中定义的是primitive type,那么会抛出NullPointerException。(因为假如返回值并不是一个primitive type,那么返回null是可以的)。如果返回值类型和接口中定义的类型不相匹配,那么会返回ClassCastException。文档中说明如下:

If the declared return value of the interface method is a primitive type, then the value returned by invoke must be an instance of the corresponding primitive wrapper class; otherwise, it must be a type assignable to the declared return type. If the value returned by invoke is null and the interface method’s return type is primitive, then a NullPointerException will be thrown by the method invocation on the proxy instance. If the value returned by invoke is otherwise not compatible with the method’s declared return type as described above, a ClassCastException will be thrown by the proxy instance.

在上述的代码之上,可以做一些修改。毕竟我们前面说了代理类可以在运行时实现多个类

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

interface Foo {
     void sayHello();
}

interface Bar {
    void sayGoodBye();
}

class FooImpl implements Foo,Bar{
    @Override
    public void sayHello() {
        System.out.println("Hello");
    }

    @Override
    public void sayGoodBye() {
        System.out.println("GoodBye");
    }
}

class MyHandler implements InvocationHandler {
    Object target;

    public MyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(System.currentTimeMillis());
        Object result = method.invoke(target,args);
        System.out.println(System.currentTimeMillis());
        return result;
    }
}
public class Test1 {
    public static void main(String[] args)  {
        Foo prox = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                new Class[]{Foo.class,Bar.class},new MyHandler(new FooImpl()) );
        prox.sayHello();
        Bar bar = (Bar)prox;
        bar.sayGoodBye();
    }
}

这样一来,代理对象可以随便的转换为Bar或者Foo接口。同时也拓展了函数的功能。一些关于动态代理比较好的文档:

官方文档有些不好懂,理解之后还是可以的。

Proxy类和Invocation Handler的代码注释。直接搜索Proxy和Invocation handler就可以了,或者直接看源码中的注释。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇