JAVAEE第十篇 反射和动态代理完美解析

1-概念

何为反射:在程序运行期间可以拿到一个对象的所有信息,如对象的名称-参数-方法-构造方法等.
除了int等基本类型外,Java的其他类型全部都是class.
拿到的流程是怎么样的呢?
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后为String类创建一个Class实例并关联起来,我们可以暂且理解为我们创建的每个类JVM运行时都创建了一个隐形的静态变量class,此隐形变量的类型是Class,我们通过隐形变量和方法能够获取到当前对象的所有信息.

class Person{
	String name = "Hello";
	//类型是Class的隐形静态变量class
	static Class class;
	//返回类型是Class的隐形方法getClass
	Class getClass(){
	  	.......
   }
}

JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,获取对应的Class实例方法有三种

	Class<String> class1 =	String.class;//根据类名获取Class实例对象
	String str2 = new String("Hello");
	Class class2 = str2.getClass();//根据实例对象获取Class实例对象
	Class class3 = Class.forName("java.lang.String");//根据完整类名获取Class对象

上面我们知道了怎么获取某一个类对应的Class实例对象后,就可以根据此Class实例对象获取到对应类的类名、包名、方法、字段等所有属性

public class fanshe {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		example example = new example();
		example.getExample();
	}
}
class example{
	@SuppressWarnings({ "unused", "rawtypes" })
	void getExample() throws ClassNotFoundException {
	Class<String> class1 =	String.class;//根据类名获取Class实例对象
	String str = new String("Hello");
	Class class2 = str.getClass();//根据实例对象获取Class实例对象
	Class class3 = Class.forName("java.lang.String");//根据完整类名获取Class对象

	System.out.println("Class name:"+class1.getName());//获取类名 java.lang.String
	System.out.println("Simple name:" +class1.getSimpleName());//简化的类名 String
	System.out.println("Package name:" +class1.getPackage().getName());//包名 java.lang
	}
}

动态加载:上述代码中当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Example.class,除非程序执行到getExample()方法,JVM发现需要加载Example类时,才会首次加载Example.class。如果没有执行getExample()方法,那么Example.class根本就不会被加载。

2-访问字段

Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
通过上述方法先获取到Field对象,再通过Filed对象获取字段名、类型、字段的值、修改字段的值
注意:如果一个字段在类中用的private声明的,需调用Field.setAccessible(true),意思是别管这个字段是不是public,一律允许访问

public class fanshe {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException,
			ClassNotFoundException, NoSuchFieldException, SecurityException {
		fanshe fanshe = new fanshe();
		fanshe.getName(new Student2());
	}
	void getName(Object obj)
			throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException {
		// 先获取对应的Class对象
		Class classObj = obj.getClass();
		// 实例化对象用getClass().newInstance()
		Object student = obj.getClass().newInstance();
		// 根据字段名获取某个public的field(包括父类)
		Field field1 = classObj.getField("name");
		// 结果:字段名name:public java.lang.String com.Sinokj.miaosha.controller.Persion2.name
		System.out.println("字段名修改后的name:" + field1);
		// 设置name的值
		field1.set(student, "Hello");
		// 根据字段名获取当前类的某个field(不包括父类)
		Field field2 = classObj.getDeclaredField("score");// 结果:字段名name:public int
															// com.Sinokj.miaosha.controller.Student2.score
		System.out.println("字段名name:" + field2);
		// 获取所有public的filed(包括父类)
		Field[] fields3 = classObj.getFields();
		for (Field field : fields3) {
			System.out.println(field.getName());// 字段名
			System.out.println(field.getType());// 字段类型
			// 获取每个字段的值 field.get(classObj)
			System.out.println(field.get(student));
		}
		// 获取当前类的所有field(不包括父类)
		Field[] fields4 = classObj.getDeclaredFields();
		// 获取实例对象 getClass().newInstance()
		Object object = obj.getClass().newInstance();
		// 用instanceof不但匹配指定类型,还匹配指定类型的子类
		if (object instanceof Persion2) {
			// getClass().newInstance()用来实例化对象
			Persion2 perison = (Persion2) object;
			System.out.println(perison.getName());
		}
	}
}

class Persion2 {
	public String name = "Persion2";

	void setName(String name) {
		this.name = name == null ? null : name.trim();
	}

	String getName() {
		return this.name;
	}
}

class Student2 extends Persion2 {
	public int score;
	public int grade;

}

调用方法

我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:
Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)

public class fansheMethod {
	public static void main(String[] args) throws Exception {
		Class stdClass = Student5.class;
		Method method1 = stdClass.getMethod("getScore", String.class);
		// 获取public方法getScore,参数为String:
		// 结果:public int com.Student5.getScore(java.lang.String)
		System.out.println(method1);
		// 获取继承的public方法getName,无参数:
		Method method2 = stdClass.getMethod("getName");
		System.out.println(method2);
		// 获取private方法getGrade,参数为int:
		Method method3 = stdClass.getDeclaredMethod("getGrade", int.class);
		System.out.println(method3);

		// 返回方法名称,例如:"getScore";
		String name = method1.getName();
		// 返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
		TypeVariable<Method>[] typeParameters = method1.getTypeParameters();
		// 返回方法返回值类型,也是一个Class实例,例如:String.class
		Class returnType = method1.getReturnType();
		// 返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
		int modifiers = method1.getModifiers();

	}
}

class Student5 extends Person5 {
	public int getScore(String type) {
		return 99;
	}
	private int getGrade(int year) {
		return 1;
	}
}
class Person5 {
	public String getName() {
		return "Person";
	}
}

调用构造方法

我们通常使用new操作符创建新的实例:
Person p = new Person();
如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

public class fansheConstruction {
	@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
	public static void main(String[] args) throws InstantiationException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		//获取Person6对应的class实例对象
		Class person = Person6.class;
		//class实例对象的getConstructor方法,得到Constructor对象,其中String.class为构造传入类型,
		Constructor constructors = person.getConstructor(String.class);
		//通过Constructor对象生成此构造函数对象
		Person6 newInstance = (Person6) constructors.newInstance("hello");
		System.out.println(newInstance.getName());
	}
}

class Person6 {
	private String name;
	public Person6(String name) {
		this.name = name;
	}
	String getName() {
		return this.name;
	}
}

通过Class实例获取Constructor的方法如下:
getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

动态代理

我们先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

public class ProxyExample {
	    public static void main(String[] args) {
	        InvocationHandler handler = new InvocationHandler() {
	            @Override
	            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	                System.out.println(method);
	                if (method.getName().equals("morning")) {
	                    System.out.println("Good morning, " + args[0]);
	                }
	                return null;
	            }
	        };
	        Hello hello = (Hello) Proxy.newProxyInstance(
	            Hello.class.getClassLoader(), // 传入ClassLoader
	            new Class[] { Hello.class }, // 传入要实现的接口
	            handler); // 传入处理调用方法的InvocationHandler
	        hello.morning("Bob");
	    }
	}

	interface Hello {
	    void morning(String name);
	}

在运行期动态创建一个interface实例的方法如下:
1-定义一个InvocationHandler实例,它负责实现接口的方法调用;
2-通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
(1)使用的ClassLoader,通常就是接口类的ClassLoader;
(2)需要实现的接口数组,至少需要传入一个接口进去;
(3)用来处理接口方法调用的InvocationHandler实例。
3-将返回的Object强制转型为接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值