java 反射及代理模式初步学习
0. 什么是反射?
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。[摘自百度百科]
1. 反射的用途
反射通常由需要检查或修改Java虚拟机中运行的应用程序的运行时行为的程序使用。这是一个相对高级的功能,只应由对语言基础有很深了解的开发人员使用。考虑到这一警告,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。
2. 反射的优缺点
- 优点:
a. 可扩展性
应用程序可以通过使用其完全限定的名称创建可扩展性对象的实例来使用外部用户定义的类。
b. 类浏览器和可视化开发环境
类浏览器需要能够枚举类的成员。可视化开发环境可以受益于利用反射中可用的类型信息来帮助开发人员编写正确的代码。
c. 调试器和测试工具
调试器需要能够检查类的私有成员。测试工具可以利用反射来系统地调用在类上定义的可发现的集合API,以确保测试套件中的代码覆盖率很高。 - 缺点:
反射功能强大,但不应随意使用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时,应牢记以下注意事项。
a. 性能开销
由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能要比非反射操作慢,因此应避免在对性能敏感的应用程序中经常调用的代码段中。
b. 安全限制
反射需要运行时许可,而在安全管理器下运行时可能不存在。对于必须在受限的安全上下文(例如Applet)中运行的代码,这是一个重要的考虑因素。
c. 暴露内部细节
由于反射允许代码执行在非反射代码中是非法的操作,例如访问私有字段和方法,因此使用反射可能会导致意外的副作用,这可能会使代码无法正常工作并可能破坏可移植性。反射性代码破坏了抽象,因此可能会随着平台的升级而改变行为。
以上内容引用百度百科或翻译自java官方文档,使大家对于Java反射有一个基本的认识,接下来我们开始学习java反射的基本使用。
3. Java反射基本使用
1. 首先我们创建一个类,然后分别使用正常手段和反射方式获取类的实例
public class Person {
String name;
private int age;
// 无参构造
public Person ()
{
}
// 有参构造
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;
}
// public 方法
public void printName()
{
System.out.println("printName = " + name);
}
// private 方法
private void printAge()
{
System.out.println("printAge = " + age);
}
}
这个类共有两个属性,两个构造方法,set,get方法以及一个公共的printName方法,一个私有的printAge方法,下面我们就分别使用正常方法和反射方法获取该类的对象
一. 正常的方式
Person person = new Person(); // 这种方式就不多说了,大家都会的
二. 反射方式
// 使用反射获取类对象的步骤如下
// 1. 获取类
// 2. 调用newInstance方法实例化对象
// 获取类的方式有三种
// 1. 类名.class
// 2. 对象名.getClass();
// 3. Class.forName("类的全路径名");
// 以下例子讲解了反射的各种用法,其中包括:
// 1. 获取类
// 2. 获取类加载器(这个后面再说,先了解)
// 3. 获取方法列表(public、private)
// 4. 获取属性列表(public、private)
// 5. 获取构造器列表
// 6. 获取指定的方法,属性
具体请看以下代码:
public class main {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 1. 正常的手段
Person person = new Person();
// 2. 反射
// 第一种拿到类的方式
Class cls = Person.class;
// 第二种拿到类的方式
Class cls1 = person.getClass();
// 第三种拿到类的方式
Class cls2 = Class.forName("Person");
// 获取实例, 使用Class.newInstance方法来实例化对象
Person person1 = (Person) cls.newInstance();
person1.setName("jiangc");
person1.printName();
// 类加载器,获取类加载器的方式,Class.getClassLoader
ClassLoader classLoader = person1.getClass().getClassLoader();
System.out.println(classLoader);
System.out.println("------------------------获取公共方法,包括从父类继承 通过getMethods方法-----------------------------------------------------");
// 获取公共方法,包括从父类继承 通过getMethods方法
Method[] methods = cls.getMethods();
// 打印一下
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i].getName() + "()");
}
System.out.println("------------------------获取类的所有方法,包括私有方法,只能是本类的 通过getDeclareMethods方法----------------------------------------------------");
// 获取类的所有方法,包括私有方法,只能是本类的 通过getDeclareMethods方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + "()");
}
System.out.println("------------------------获取指定方法 通过getMethod方法, 第一个参数是方法名,后面是可变参数,是参数的类型-----------------------------------------------------");
// 获取指定方法 通过getMethod方法, 第一个参数是方法名,后面是可变参数,是参数的类型
Method setName = cls.getMethod("setName", String.class);
// 执行方法 通过invoke 第一个是对象,后面是方法的参数
setName.invoke(person1, "lalala");
person1.printName();
// 其中注意的一个点,类似于int类型的在传入类型的时候使用int.class 这种方式,如下所示
Method setAge = cls.getMethod("setAge", int.class);
setAge.invoke(person1, 18);
System.out.println("age = " + person1.getAge());
// 访问私有方法有一些不同, 如果直接invoke会报错,因为没有访问权限,在调用私有方法的时候需要先调用setAccessible方法,打开访问权限
Method printAge = cls.getDeclaredMethod("printAge");
printAge.setAccessible(true);
printAge.invoke(person1);
System.out.println("-------------------获取字段 使用getFields 方法----------------------------------------------------------");
// 获取字段 使用getFields 方法
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
System.out.println("-------------------这里只有一个public修饰的字段,使用getDeclareFields方法可以获取私有的字段----------------------------------------------------------");
// 这里只有一个public修饰的字段,使用getDeclareFields方法可以获取私有的字段
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
System.out.println("--------------------获取指定字段,和方法类似,这里使用的是getField方法, 传入字段名---------------------------------------------------------");
// 获取指定字段,和方法类似,这里使用的是getField方法, 传入字段名
Field name = cls.getField("name");
System.out.println("打印:");
System.out.println("字段的名字 :" + name.getName());
// 打印字段的值
System.out.println("字段的值:" + name.get(person1));
// 修改public字段的值
// 修改字段的值
name.set(person1, "yayaya");
System.out.println(name.get(person1));
Object o = name.get(person1);
// 修改私有字段的值
System.out.println("修改私有字段的值, 和私有方法一样,无论访问还是修改,都需要调用setAccessible方法来打开权限");
// 获取私有字段
Field age = cls.getDeclaredField("age");
age.setAccessible(true);
System.out.println("age原本的值:" + age.get(person1));
// 修改age
age.set(person1, 27);
System.out.println("修改之后的值:" + age.get(person1));
// 大家看到,前面通过反射拿到的对象都是调用的newInstance方法,
//这个方法我们没有传参数,Person有一个有参构造,那么我们怎么通过有参构造来实例化对象呢
// 使用Constructors()方法获取所有的构造方法
System.out.println("-----------------------------------------------------------------------------");
System.out.println("通过getConstructors方法获取构造方法的列表");
Constructor<Person>[] constructors = (Constructor<Person>[]) cls.getConstructors();
for (Constructor<Person> constructor : constructors) {
System.out.println(constructor);
}
// 获取指定名称的构造方法
System.out.println("获取指定构造器实例化对象");
Constructor<Person> constructor = (Constructor<Person>) cls.getConstructor(String.class, int.class);
Person xixixi = (Person) constructor.newInstance("xixixi", 23);
xixixi.printName();
// 其他获取私有的啊什么的大家自己去尝试
// 以上讲解了如何使用反射获取类的public和private的方法、属性,列表,获取public和private的指定方法,属性,以及调用方法,还有获取指定构造器实例化的使用方式。反射的知识就讲到这里。
}
}
4. 反射在代理模式中的使用
首先我们先看一下代理模式,什么是代理模式,举个例子:代购的公司,房产中介公司,都可以看作代理,例如说你要买海外的东西,有两种方式:1. 自己直接联系海外商家,直接买,2. 找代购,把自己的需求告诉代购,代购全权代理,你不需要和商家打交道,这就是代理
我们先来看一下代理模式的类的关系图:
说一下这个类的关系,Subject类主要定义了一些公共方法,然后真实类和代理类去实现这个类,同时代理类拥有真实类的引用,从而调用真实类的方法。
代理模式有两种:1. 静态代理 2. 动态代理
我们先看静态代理
首先,想象一个场景,有一个房产中介aProxy,他可以为客户提供租房服务,客户aClient找他租房,中介aProxy找房东,中介为客户提供全套服务,包括合同,打扫等服务,下面我们根据代理模式的类图来实现一下
- 公共类,提供接口
/**
* 公寓类,定义公共方法(对应抽象类:Subject)
*/
public interface Apartment {
// 租房接口,提供一个参数:租房面积
public void renting(int area);
}
3. 房东提供租房
/**
* 房产中介(对应上面类图中的真实类:Goodscompanies)
*/
public class Intermediary implements Apartment{
@Override
public void renting(int area) {
System.out.println("房屋出租面积为:" + area + "的房屋");
}
}
4. 中介代理房东
/**
* 代理人(对应上面的Proxy)
*/
public class ProxyPeople implements Apartment{
// 真实对象的引用
private Intermediary intermediary;
/**
* 代理自己的方法(增强方法)
*/
private void conclude_contract()
{
System.out.println("代理签合同");
}
/**
* 代理自己的方法(增强方法)
*/
private void sweep()
{
System.out.println("代理打扫房间");
}
@Override
public void renting(int area) {
conclude_contract();
// 调用真实的对象的方法
intermediary.renting(area);
sweep();
}
}
这时候,客户想买一个新房子,而这个租房子的代理人没有能力提供买房子的服务(大家这里不要抠细节,我们假设这个中介就只能处理租房的事情),而另外一个中介可以提供买房的信息,所以,客户买房子就只能换一个代理这里记为:ProxyRealty,提供新房的实体其实是房地产开发商,所以关系是,买房的代理通过开放商向客户提供买房服务,请看下面的代码:
/**
* 房产类接口
*/
public interface Brealty {
public void renting(int area);
}
/**
* 开发商
*/
public class realty implements Brealty{
@Override
public void renting(int area) {
System.out.println("客户买了:" + area + "面积的房子");
}
}
/**
* 提供买房服务代理类
*/
public class ProxyRealty implements Brealty{
// 提供房子的实体类
private realty re;
public ProxyRealty(realty re) {
this.re = re;
}
private void see_apartment()
{
System.out.println("带着顾客看房子...");
}
private void sign_contract ()
{
System.out.println("和客户签合同");
}
private void pay()
{
System.out.println("交首付");
}
private void handing_room()
{
System.out.println("交房");
}
@Override
public void renting(int area) {
see_apartment();
re.renting(200);
sign_contract();
pay();
handing_room();
}
}
大家想一下,假如这时候客户买完房子了,想装修,这时候通过网络又找到另外一个代理人,全权交给这个人帮自己装修,代码和上面类似,这里就不写了,大家有感觉了吗?这就是静态代理,大家可以对照类图和代码例子理解一下。