Android插件化开发指南——基础之反射

1. 前言

Java最强大的技术:反射!为什么这么说,不妨再次来简单回忆一下Spring这个框架。

    我们知道Spring 是目前主流的 Java Web 开发框架,是 Java 世界最为成功的框架。该框架是一个轻量级的开源框架,具有很高的凝聚力和吸引力。在Spring框架中,以 IoCInverse of Control,控制反转)和 AOPAspect Oriented Programming,面向切面编程)为内核。

  • IoC 指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们使用 new 创建,而使用 Spring 之后,对象的创建都交给了 Spring 框架。IoC是一种编程思想,主要用于降低代码之间的耦合度。具体来说,就是在创建对象的时候,不需要在使用经典的new来进行对象的创建,而是利用反射,结合xml将工厂对象和生产对象相隔离开,提高了灵活性。
  • AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。

IoC通常又叫做IoC容器,也可以称为 Spring 容器。主要用来管理对象的实例化和初始化,以及对象从创建到销毁的整个生命周期。通过读取 XMLJava 注解中的信息来获取哪些对象需要实例化。而在IoC容器对对象进行实例化的时候,就是使用Java中的反射技术来实现的。所以说Java的反射技术,支撑起了整个底层的实现。

虽然在之前学习Spring的时候,简单使用了一些反射的技术,但是其实技术这种东西很快就忘记了。所以在这篇文章中将比较详细的介绍下Java的反射技术,并做一些简单的案例搭配学习。

2. 反射

Java的反射是指程序在运行期可以拿到一个类的所有属性和方法,并得到实例化对象。代码可以在运行时装配,无需在源代码中进行链接。进而降低了代码的耦合度。基本反射包括一下几个技术:

  • 根据一个字符串得到一个类的对象;
  • 获取一个类的所有公有或者私有、静态或者实例的字段、方法和属性;
  • 对泛型类的反射;

2.1 获得代表类的Class对象

2.1.1 getClass

即:通过一个类的对象,来获取代表这个类的Class对象。这个也是最简单的,比如:

String name = "张三";
Class<? extends String> aClass = name.getClass();

2.2.2 Class.forName

通过类的命名空间和类的名称组成。比如:

try {
    
    
    Class<?> name = Class.forName("java.lang.String");
    Constructor<?> constructor = name.getDeclaredConstructor(String.class);
    String o = (String) constructor.newInstance("123");
    System.out.println(o);
}catch (Exception e){
    
    
    e.printStackTrace();
}

2.2.3 类的class属性

每个类都有class属性,也就可以得到Class类型对象,比如前面使用过的String.class

2.2.4 基本类型的TYPE属性

对于基本数据类型的包装类,都有TYPE类型,通过这个属性也可以得到代表这个类的Class实例,比如:

public static void main(String[] args) throws ClassNotFoundException {
    
    
    Boolean flag = false;
    Class<? extends Boolean> name = flag.getClass();
    Class<?> name1 = Class.forName("java.lang.Boolean");
    Class<?> name2 = Boolean.class;
    // 多了一种方式
    Class<?> name3 = Boolean.TYPE;
}

2.2 获取类的成员

2.2.1 构造函数

对于反射来说,Java的访问修饰符没有任何意义。比如某个类定义为私有的构造方法,这里同样可以得到。比如说这里定义这样一个类Person,用来测试:

static class Person{
    
    
    private String name;
    private int age;
    private Person(){
    
    }

    public Person(String name, int age){
    
    
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
    private String getName() {
    
    
    	return name == null ? "未知姓名" : name;
    }
    
	public int getAge(int baseAge) {
    
    
       return age == 0 ? 0 : baseAge + age;
    }
}

为了测试类的有参和无参两种构造,这里做简单的测试:

public static void main(String[] args) throws Exception {
    
    
    // ------------私有无参构造函数----------------
    Class<?> clazz = Person.class;
    Constructor<?> constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true); // 设置可访问
    Person person = (Person) constructor.newInstance();
    System.out.println(person);

    // ------------公开有参构造函数----------------
    constructor = clazz.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true); // 设置可访问
    person = (Person) constructor.newInstance("战三", 123);
    System.out.println(person);
}

根据上面的案例我们知道,确实Java的访问修饰符在反射这里没有任何意义。且在得到构造器的方式有两种,分为有参数和无参数。有参数的这种需要传入的为方法中定义的参数的class类型。当然,在Java中还提供了另一种方式,即:

// ------------得到所有构造函数,不用处理参数类型----------------
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
    
    
    Constructor<?> temp = constructors[i];
    temp.setAccessible(true);
    Person person;
    if(i == 0){
    
    
        person = (Person) temp.newInstance();
        System.out.println(person);
    }else{
    
    
        person = (Person) temp.newInstance("李四", 123);
        System.out.println(person);
    }
}

2.2.2 普通方法

这里还是接着上面的案例来进行展开,比如这里我们需要执行getNamegetAge方法来得到用户的姓名/年龄:

public static void main(String[] args) throws Exception {
    
    
    // ------------有参构造函数----------------
    Class<?> clazz = Person.class;
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true); // 设置可访问
    Object person = constructor.newInstance("张三", 23);

    // => 无参数普通方法
    Method getName = clazz.getDeclaredMethod("getName");
    getName.setAccessible(true);
    String name = (String) getName.invoke(person, null);
    System.out.println(name);
    // => 有参数普通方法
    Method getAge = clazz.getDeclaredMethod("getAge", int.class);
    getAge.setAccessible(true);
    int age = (int) getAge.invoke(person, 2);
    System.out.println(age);
}

结果:
在这里插入图片描述

2.2.3 静态方法

同样的,这里在Person类中新增一个静态方法用于测试,比如:

public static void printUserInfo(String parameter){
    
    
    System.out.println("测试静态方法!,传入参数:" + parameter);
}

然后我们进行测试:

public static void main(String[] args) throws Exception {
    
    
    // ------------静态方法----------------
    Class<?> clazz = Person.class;
    Method printUserInfo = clazz.getDeclaredMethod("printUserInfo", String.class);
    printUserInfo.setAccessible(true);
    printUserInfo.invoke(null, "User");
}

注意到,这里因为静态方法并不需要类的实例对象,所以这里在invake中传入的是一个null对象。故而这里也就不再需要获取到构造器对象。运行结果:
在这里插入图片描述

2.2.4 私有非静态属性

需要注意的是,在前面的案例中。我定义Person这个类为我这里测试的一个静态内部类,所以在外部类中可以直接访问内部类的私有属性。这里为了测试私有属性,显然就不能通过反射得到类实例对象,然后直接方位私有属性。所以这里将之前定义的Person单独放置在一个文件中,即:Person.java。然后开始本次测试:

public static void main(String[] args) throws Exception {
    
    
    // ------------有参构造方法----------------
    Class<?> clazz = Person.class;
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
    constructor.setAccessible(true);
    Object person = constructor.newInstance("张三", 25);

    // --> 获取private类型的name字段
    Field name = clazz.getDeclaredField("name");
    name.setAccessible(true);
    System.out.println("用户初始化的姓名为:" + (String) name.get(person));

    // --> 修改字段
    name.set(person, "李四");
    System.out.println("修改后的用户姓名为:" + (String) name.get(person));
}

测试结果:

在这里插入图片描述

2.2.5 私有静态属性

和前面操作静态方法类似的操作,我们还是先定义一个静态的属性字段在Person类中,比如:

private static final String TAG = "Person";

按照类似的处理操作,我们只需要在第一个参数中传入null即可:

public static void main(String[] args) throws Exception {
    
    
    // ------------有参构造方法----------------
    Class<?> clazz = Person.class;

    // --> 获取private类型的name字段
    Field tag = clazz.getDeclaredField("TAG");
    tag.setAccessible(true);
    System.out.println("静态常量参数TAG的值为:" + (String) tag.get(null));
}

结果为:
在这里插入图片描述
因为这里我定义为常量字符串,所以这里就不测试修改语法了。

2.3 对泛型类的反射

和前面一样,这里为了测试案例首先定义一个泛型类:

/**
 * DCL——懒汉式
 * @param <T>
 */
public abstract class Singleton<T extends Person> {
    
    

    public Singleton(){
    
    }

    private volatile T mInstance;

    /**
     * 真正的创建对象工作,交给子类去完成
     * @return
     */
    protected abstract T create();

    public T getInstance(){
    
    
        if(mInstance == null){
    
    
            synchronized (this){
    
    
                if(mInstance == null){
    
    
                    mInstance = create();
                }
            }
        }
        return mInstance;
    }
}

上面的Singleton类是一个泛型类,同时注意到也是一个抽象类。所以在实例化Singleton的时候需要实现这个类的抽象方法create。首先来看下正常的调用是如何做的:

public class Man{
    
    

    // 正常使用抽象的泛型单例
    private static final Singleton<Person> userBean = new Singleton<Person>() {
    
    
        @Override
        protected Person create() {
    
    
            return new Person("未知", 23);
        }
    };

    public static void main(String[] args) throws Exception {
    
    
        Person instance = userBean.getInstance();
        System.out.println(instance);
    }
}

对应的,写法为:

public class Man{
    
    

    // 正常使用抽象的泛型单例
    private static final Singleton<Person> userBean = new Singleton<Person>() {
    
    
        @Override
        protected Person create() {
    
    
            return new Person("未知", 23);
        }
    };

    public static void main(String[] args) throws Exception {
    
    

        // 反射
        Class<?> clazz = Man.class;
        Constructor<?> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Object obj = constructor.newInstance();
        Field userBean = clazz.getDeclaredField("userBean");
        userBean.setAccessible(true);
        Singleton<Person> o = (Singleton<Person>) userBean.get(obj);
        Person person = o.getInstance();
        System.out.println(person);
    }
}

注意到这里使用的是测试类的Class对象,因为抽象方法需要被实现。所以这里使用的是userBean

3. 后记

当然上面的只是一些比较原始的反射用法。后续将继续学习jOOR


References

猜你喜欢

转载自blog.csdn.net/qq_26460841/article/details/121290626
今日推荐