反射:运行时的类信息

Posted by AaronYang on May 12, 2020

反射:运行时的类信息

Java反射机制是在运行时,动态地提取某个类的信息,调用这个类的所有属性和方法,在编译时无需知道任何事情。

Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含了Field、Method和Constructor类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。可以使用Constructor来创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。

package com.lyricyang.knowledge.designpattern.reflect;

public interface Behavior {

    public abstract void run();

    public abstract void eat();
}

package com.lyricyang.knowledge.designpattern.reflect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Lyric {
}
package com.lyricyang.knowledge.designpattern.reflect;

public class Person {
    static{
        System.out.println("Loading Person ...");
    }

    private String organ;
    public String name;

    public void getName(){
        System.out.println("Name:" + name);
    }
}
package com.lyricyang.knowledge.designpattern.reflect;

@Lyric
public class Man extends Person implements Behavior {

    static{
        System.out.println("Loading Man ...");
    }

    public String hairStyle;

    private String estate;

    public Man(){

    }

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

    @Override
    public void run() {
        System.out.println("Running ...");
    }

    @Override
    public void eat() {
        System.out.println("Eating ...");
    }

    @Lyric
    private Integer search(String password){
        System.out.println("Password:"+password);
        return 250;
    }
}

Class类

无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。

// 获取Class对象的引用,加载类
Class<?> clazz = Class.forName("com.lyricyang.knowledge.designpattern.reflect.Man");
// 类的字面量
Class clazz2 = Person.class;
// 获取一个对象的类信息
Man man = new Man();
Class clazz3 = man.getClass();

Class.forName()不需要为了获取Class引用而持有该类型的对象。但是,如果你已经拥有了一个感兴趣的类型对象,就可以通过getClass()方法来获取Class引用了。.class来创建Class对象的引用时,不会自动自动地初始化该Class对象,即不会自动执行静态初始化器和静态初始化块。

Class.forName()除了将类的.class文件加载到jvm中以外,还会对类进行解释,执行类的static块。而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance()才会去执行static块。

//获取类的加载器
ClassLoader clazz1ClassLoader= clazz.getClassLoader();
System.out.println(clazz1ClassLoader);
//获取类的全路径名字
String classPathName = clazz.getName();
System.out.println(classPathName);
//获取类的名字
String className = clazz.getSimpleName();
System.out.println(className);
//获取父类
System.out.println(clazz.getSuperclass().getName());
//获取当前类实现的接口
Class[] classes = clazz.getInterfaces();
for(Class c : classes){
    System.out.println(c.getName());
}

Constructor类

Class的getConstructors()方法可以获取该类的所有公有构造方法而getDeclaredConstructors()方法则返回所有构造方法;此外获取指定的构造器可以使用getConstructor(Class...<?> parameterTypes) getDeclaredConstructor(Class...<?> parameterTypes) 方法

Constructor<?>[] constructors = clazz.getConstructors();
for(Constructor<?> c : constructors){
    System.out.println(c);
}
// 获取某个构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
System.out.println(constructor);
Man man = (Man) constructor.newInstance("LyricYang");
man.getName();

Field类

Class对象获取属性后,可以使用 set(Object obj, Object value) 方法给实际对象obj设置对应属性的值,使用 get(Object obj) 方法来获取对象属性的值。

//获取所有公有的属性对象(包括父类)
Field[] fields = clazz.getFields();
for(Field f : fields){
    System.out.println(f.getName());
}
//获取某个公有的属性对象(包括父类)
Field field = clazz.getField("name");
System.out.println(field);
//获取所有属性对象(本身)
Field[] allFields = clazz.getDeclaredFields();
for(Field f : allFields){
    System.out.println(f.getName());
}
//获取某个属性对象(本身)
Field oneField = clazz.getDeclaredField("estate");
System.out.println(oneField);
Field field = clazz.getDeclaredField("hairStyle");
field.set(man,"Green Hair");
System.out.println(field.get(man));

Method类

与获取Field类一样,可以通过以下方法获取类的方法,之后可以使用 invoke(Object obj, Object... args) 方法来调用实际对象的方法。如果想使用私有方法需设置setAccessible(bool acc)方法,参数为true

方法 用途
getMethod(String name, Class…<?> parameterTypes) 获得该类某个公有的方法
getMethods() 获得该类所有公有的方法,包括父类的方法
getDeclaredMethod(String name, Class…<?> parameterTypes) 获得该类某个方法
getDeclaredMethods() 获得该类所有方法,只有本身的方法
// 获得该类所有公有的方法,包括父类的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
    System.out.println(m);
}
// 获得该类所有方法
Method[] allMethods = clazz.getDeclaredMethods();
for(Method m : allMethods){
    System.out.println(m);
}
// 获取该类的某个方法
Method run = clazz.getDeclaredMethod("run");
run.invoke(man);
Method search = clazz.getDeclaredMethod("search", String.class);
search.setAccessible(true);
Integer integer = (Integer) search.invoke(man,"LyricYang");
System.out.println("Balance: "+integer);

Annotation类

获取注解时,该注解必须是RetentionPolicy.RUNTIME才能在运行时获得

方法 用途
getAnnotation(Class annotationClass) 返回该类中与参数类型匹配的公有注解对象
getAnnotations() 返回该类所有的公有注解对象
getDeclaredAnnotation(Class annotationClass) 返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations() 返回该类所有的注解对象
Annotation annotation = clazz.getAnnotation(Lyric.class);
System.out.println(annotation);
System.out.println(annotation.annotationType());
Annotation[] annotations = search.getAnnotations();
for(Annotation a : annotations){
    System.out.println(a);
}

Java动态代理

代理模式: 为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托类(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。

静态代理

创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

package com.lyricyang.knowledge.designpattern.reflect;

public class ManProxy implements Behavior {

    Behavior man;

    public ManProxy(Behavior man){
        this.man=man;
    }

    @Override
    public void run() {
        System.out.println("Proxy ...");
        man.run();
    }

    @Override
    public void eat() {
        System.out.println("Proxy ...");
        man.eat();
    }
}

代理类维护一个实际的被代理对象,代理类在执行具体方法时通过所持有的被代理对象来完成方法调用。 使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。

public static void main(String[] args) throws Exception{
    Class<?> clazz = Man.class;
    Constructor<?> constructor = clazz.getConstructor();
    Man man = (Man) constructor.newInstance();
    ManProxy manProxy = new ManProxy(man);
    manProxy.run();
    manProxy.eat();
}

动态代理

利用反射机制在运行时创建代理类。接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。

  1. 通过实现接口的方式 -> JDK动态代理
  2. 通过继承类的方式 -> CGLIB动态代理
package com.lyricyang.knowledge.designpattern.reflect;

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

public class ProxyHandler implements InvocationHandler {

    private Object object;

    public ProxyHandler(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Proxy ...");
        method.invoke(object,args);
        return null;
    }
}
public static void main(String[] args) throws Exception{
    Class<?> clazz = Man.class;
    Constructor<?> constructor = clazz.getConstructor();
    Man man = (Man) constructor.newInstance();
    ProxyHandler proxyHandler = new ProxyHandler(man);
    Behavior proxy = (Behavior) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),proxyHandler);
    proxy.run();
}

CGLIB动态代理

CGLIB动态代理优势在于不需要提供接口,只要一个非抽象类就能实现动态代理。