Android进阶学习(2)-- 反射及动态代理模式

什么是反射

“反射”之中包含了一个“反”字,我们先来看看什么是“正”;一般情况下,我们使用某个类会知道他是干什么的,里面有哪些属性和方法,在使用时对这个类实例化,之后用实例化的对象来操作。
反射,则是一开始我们不知道要使用的类是什么,所以不能使用new关键字来实例化,更不会知道类中包含了哪些方法、属性。这时候就需要使用JDK提供的“反射”API进行反射调用。
反射就是在运行时才知道要操作的类是什么,在运行时获取完整的构造,属性,并调用方法。
反射也是Java语言被视为动态语言的关键。

Class类

Class是一个类,封装了当前对象所对应的类的信息。我们写的所有的类,其实都是一个Class的对象,Class就是来描述所有的类。类的所有信息,会在编译的时候加在java类文件的末尾,包括它的构造,方法,属性。

获取Class类

获取Class类的方法有三种:

  1. 通过类名获取 —— 类名.Class
  2. 通过对象名获取 —— 对象名.Class
  3. 通过全类名获取 —— Class.forName(“com.xxx.xxx”)

通过new关键字创建的对象,和通过Class创建的对象是完全一样的:

Person p = new Person("shy",23);
p.getAge();
Log.e("通过new创建Person", "name = " + p.getName());
//通过下面 三种方式创建的Class对象是一样的
//Class class1 = Person.class;
//Class class2 = p.getClass();
try { //全类名可能找不到 android里需要try catch
    Class class3 = Class.forName("com.example.study.Person");
    Person person = (Person) class3.newInstance();
    person.setName("SHY");
    Log.e("通过Class创建Person", "name = " + person.getName());
} catch (Exception e) { 
    e.printStackTrace();
}

输出:
在这里插入图片描述
这里要说明一下,在Java中类的信息(构造,属性,方法)都有对应的类来表示(Field:属性,Constructor:构造,Method:方法)。通过这些类的对象,我们可以获取到类的信息。
在这里插入图片描述

通过反射获取构造方法

首先我们先创建一个Person类,里面包含一些属性和方法

public class Person {

    String name;
    private int age;

    public Person(){
        super();
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void printAll(){
        Log.e("printAll被调用","printAll被调用");
    }
}

获取构造方法的思路,根据上面的图片,我们可以得知Constructor对象包含在Class中,所以我们获取到类的Class对象,在获取Constructor即可

try {
    Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
    Log.e("获取全部构造函数","——————————");
    Constructor<Person>[] constructors = (Constructor<Person>[]) c.getConstructors();
    for (Constructor<Person> constructor : constructors){
        Log.e("构造函数:", "" + constructor);
    }
} catch (Exception e) {// 为了方便我就直接写Exception了
    e.printStackTrace();
}

输出结果:
在这里插入图片描述
我们通过getConstructors可以获取到类的全部构造函数,那如何获取指定构造?

//通过getDeclaredConstructor方法 传入想获取的构造函数对应的参数类型即可
//这里不再需要封装类型,基础类型直接 .class 即可
Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("获取指定构造函数", "——————————");
Log.e("构造函数:", "" + constructor);

输出结果:
在这里插入图片描述
现在已经可以获取构造方法,那么该如何调用呢?

Constructor<Person> constructor = c.getDeclaredConstructor(String.class, int.class);
Log.e("获取指定构造函数", "——————————");
Log.e("构造函数:", "" + constructor);
Person person = constructor.newInstance("Shy", 23);
Log.e("调用构造函数之后","name = " + person.getName());
Log.e("调用构造函数之后","age = " + person.getAge());

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

通过反射获取方法

获取方法和获取构造方法大体上思路是一样的,就是个别方法会有所不同,同样需要先获取Class对象,通过Class对象获取Method

Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("获取全部方法","不包括私有方法!!!");
Method[] methods = c.getMethods();
for (Method method : methods){
    Log.e("方法:", "" + method);
}

输出结果:
在这里插入图片描述
通过上面的代码,会发现Person类中printAll()方法并没有被打印出来,这就说明getMethods()能获取除私有方法外的所有方法,那么如何获取私有方法呢?

Class<Person> c = (Class<Person>) Class.forName("com.example.study.Person");
Log.e("获取当前类的方法","不包括父类方法!!!");
Method[] methods = c.getDeclaredMethods();
for (Method method : methods){
    Log.e("方法:", "" + method);
}

输出结果:
在这里插入图片描述
通过getDeclaredMethods()方法能够获取当前类的所有方法,包括私有方法,但是父类的方法无法获取
当然,和获取构造方法同样,也可以根据参数获取指定方法,调用getDeclaredMethod(name, paramType)方法可以获取指定方法。

//需要传入 参数名 参数类型, 如果没有参数可以省去
Method method = c.getDeclaredMethod("printAll");
Log.e("获取指定方法", "如果有参数,后面需要传入对应的参数类型");
Log.e("指定方法:", "" + method);

在这里插入图片描述
最后来说一下如何调用获取到的方法,再Method类中,有一个invoke方法,即为调用方法。

Method method = c.getDeclaredMethod("printAll");
Log.e("获取指定方法", "如果有参数,后面需要传入对应的参数类型");
Log.e("指定方法:", "" + method);
//要执行方法的对象
Object obj = c.newInstance();
//这里要注意下,如果被调用方法是私有方法必须调用setAccessible设为true!!!
method.setAccessible(true);
//如果调用的方法有参数,后面要跟上参数值
method.invoke(obj);

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

通过反射获取属性

属性的获取、调用和方法的很相似就不过多赘述了直接上代码
通过getDeclaredFields获得当前类的全部属性,包括私有属性,但不包括父类属性

Log.e("获取当前类属性","不包括父类的属性!!!");
Field[] fields = c.getDeclaredFields();
for(Field field : fields){
    Log.e("属性:", "" + field);
}

输出结果:
在这里插入图片描述
获取指定属性:

Log.e("获取指定属性","获取指定属性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("获取指定属性name:","" + field.get(person));

输出结果:
在这里插入图片描述
修改指定属性:

Log.e("获取指定属性","获取指定属性");
Field field = c.getDeclaredField("name");
Person person = new Person("Sun", 23);
Log.e("获取指定属性name:","" + field.get(person));
Log.e("修改指定属性","私有属性需要调用setAccessible并且设为true");
//如果属性是私有属性 也需要调用setAccessible(true)
//field.setAccessible(true);
field.set(person, "Shy");
Log.e("修改指定属性name:","" + field.get(person));

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

静态代理模式

想象一个场景:有一家男装工厂ManFactory生产男装,一家男装商城ManShop售卖男装;张三想从这家男装商城购买一件黑色的男装,但是距离太远他没法去,只能通过代购Person去买,代购还可以给他提供售前售后服务。
根据这样的场景,我们编写一下基础代码:
男装工厂 ManFactory

//男装工厂  生产男装
public interface ManFactory {
    public void buyManClothes(String color);
}

男装商城 ManShop

//男装商城售卖男装
public class ManShop implements ManFactory {
	//商场买衣服事件
    @Override
    public void buyManClothes(String color) { 
        Log.e("男装商城ManShop","买了一件 "+ color +" 男装");
    }
}

代购人Person

//代购Person 买衣服可以提供售前 售后服务
public class Person implements ManFactory {

    ManFactory manFactory; //必须持有真实对象

    public Person(ManFactory manFactory){
        this.manFactory = manFactory;
    }

    /** 售前服务 前置处理器 **/
    public void before(){
        Log.e("代购Person","售前服务");
    }

    /** 售后服务 后置处理器 **/
    public void after(){
        Log.e("代购Person","售后服务");
    }

	//代购买衣服事件
    @Override
    public void buyManClothes(String color) {
    	//售前服务
        before();
        //代购去商场买衣服
        manFactory.buyManClothes(color);
        //售后服务
        after();
    }
}

当张三找代购去购买男装商场的男装时

//张三告诉代购 去哪个商场买
ManFactory manFactory = new ManShop();
Person person = new Person(manFactory);
//代购根据张三的要求 去买 并且提供 售前售后服务
person.buyManClothes("黑色");

输出结果:
在这里插入图片描述
以上的代码就完成了简单的静态代理,静态代理的缺点在于,如果此时,李四想要从女装商场买一件女装,他也去找Person代购,这样一来,我们还得去修改Person,让Person去实现WomanFactory,如果男装商场购买条件不是颜色了,改为Int类型的码数了,那改动量就大了。所以静态代理的缺点显而易见,维护性差,可拓展性差。

动态代理模式

为了解决静态代理的种种缺点,动态代理出现了。接着上面的需求,李四又要买女装;我们先创建女装对应的工厂和商场
女装工厂 WomanFactory

//女装工厂 
public interface WomanFactory {
    public void buyWomanClothes(String color);
}

女装商场 WomanShop

public class WomanShop implements WomanFactory {
    @Override
    public void buyWomanClothes(String color) {
        Log.e("女装商城WomanShop","买了一件 "+ color +" 女装");
    }
}

显而易见,如果还去找Person代购,是满足不了需求的,这时就出现了一家代购公司,可以代购多个商场的衣服。
代购公司 Company

public class Company implements InvocationHandler {

    Object shop; //持有的真实对象

    public Object getShop() {
        return shop;
    }

    public void setShop(Object shop) {
        this.shop = shop    ;
    }

    public Object getPoxyInstance(){
    	//newProxyInstance接受三个参数
    	//1. 要代理对象的类加载器
    	//2. 要代理对象的类实现的接口(根据反射得到,反射中没有说获取接口的方式,根据反射获取方法举一反三)
    	//3. 被代理对象要进行的操作(重写的invoke方法)
        return Proxy.newProxyInstance(shop.getClass().getClassLoader(),
                shop.getClass().getInterfaces(),
                this);
    }

    /** 售前服务 前置处理器 **/
    public void before(){
        Log.e("代购公司","售前服务");
    }

    /** 售后服务 后置处理器 **/
    public void after(){
        Log.e("代购公司","售后服务");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //售前服务
        before();
        //去代购 去商场买衣服
        Object proxyResult = method.invoke(shop,args);
        //售后服务
        after();
        return proxyResult;
    }
}

那么此时,张三李四同时去找代购公司去买衣服用如下代码表示:

//张三要买黑色男装
//告诉代购公司 买哪个商场的衣服
ManFactory manFactory = new ManShop();
Company company = new Company();
company.setShop(manFactory);
//代购公司 派员工 person1 去代购
ManFactory person1 = (ManFactory) company.getPoxyInstance();
//员工 person1 根据张三的要求去买男装 并且完成售前售后服务
person1.buyManClothes("黑色");
//李四 要买红色女装
//告诉代购公司 买哪个商场的衣服
WomanFactory womanFactory = new WomanShop();
company.setShop(womanFactory);
//代购公司 派员工 person2 去代购
WomanFactory person2 = (WomanFactory) company.getPoxyInstance();
//员工 person2 根据李四的要求去买女装 并且完成售前售后服务
person2.buyWomanClothes("红色");

输出结果:
在这里插入图片描述
总结一下:学习反射和动态代理模式,更多的在于能够读懂一些主流的框架,很多框架都用动态代理封装了一些操作供我们使用,比如:Retrofit,Retorfit就是将我们定义的网络请求接口通过动态代理翻译成网络请求,再通过okhttp去请求。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值