Java的反射机制与动态代理学习笔记

最近在补Java基础,看到了类型信息这块知识,记起某些Android api和一些库里都有用到反射或动态代理的知识,特此探究一番。

一、反射

首先百度到的定义:JAVA反射是在运行状态中,对于任意一个类,都能获取这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

看定义能够知道反射就是在程序运行的时候,通过获得某个未知类型对应的Class引用,来获得类型的所有信息,并可以动态地执行构造这种类型的对象,调用方法,访问域等操作。这里的未知类型是指不在本程序空间中定义的类,即在程序编译后才从网络或从其他地方加载进来的类,因此利用反射机制可以完成一段通用的代码以动态适应各种情况。

   复习一下获得某类型的Class引用的方法:

       1、获得了某个具体的对象引用如object,可以通过Class<?> c = object.getClass();
       2、获知某类的完整包名+类名,可以Class<?> c = Class.forName("path");
       3、通过类字面常量,如某类名“Test”,Class<?> c = Test.class.
(关于Class引用,java程序在运行的时候并不是一次把所有的类都加载进来,当第一次使用某类时才会把类型对应的.Class文件加载进来,同时创建一个Class对象,其保存了这种类型的所有信息。)

   一个模拟运行时使用反射的例子:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

public class Unknownclass {
	private static String mClassName;
	
	public static void main(String[] args) throws ClassNotFoundException{
		Scanner in = new Scanner(System.in);
		mClassName = in.nextLine();
		Class c = Class.forName(mClassName); //mClassName 模拟动态获取的类
		
		Method[] methods = c.getDeclaredMethods();
		System.out.println("类"+mClassName+"的所有方法:");
		for(Method method : methods){
			System.out.println(method+"");
		}
		
		Constructor[] constructors = c.getConstructors();
		System.out.println("类"+mClassName+"的所有构造方法:");
		for(Constructor construct : constructors){
			System.out.println(construct+"");
		}
	}
	
	public void method1(String s1){
	}
	
	public void method2(){
	}
}

打印结果:

Unknownclass   //输入
类Unknownclass的所有方法:
public static void Unknownclass.main(java.lang.String[]) throws java.lang.ClassNotFoundException
public void Unknownclass.method2()
public void Unknownclass.method1(java.lang.String)
类Unknownclass的所有构造方法:
public Unknownclass()

获取到class引用后可以动态地newInstance出该类型的对象,并通过invoke方法调用其成员方法等等。

其实花费很多功夫,最后完成的操作可能也就是构造一个对象,访问下域调用下方法,利用反射机制无疑降低了程序的性能,增加了程序的复杂度,但前面也说了,它可以加载、探知、使用编译期间完全未知的classe,使程序可以有一定的动态性和通用性,所以用得好的话很犀利,用不好就弄巧成拙了。

总结:反射机制用于在程序运行时获得任意对象的类型的所有信息,构造任意类型的对象并可调用其方法访问其域。动态代理就是基于反射机制实现的。


二、动态代理

首先复习一下代理的知识,代理是介于组合与继承之间的做法,使用一个代理类,内部保持一个实际使用的对象的引用,代理类实现所有和内部对象一样的方法(如实现相同的接口),并在方法中仅仅调用内部对象的对应方法,即代理做中间人的角色。
通过调用静态Proxy.newProxyInstance()创建动态代理,参数需要:一个类加载器、一个希望该代理实现的接口列表(不能是类或抽象类,这也导致动态代理仅适用于接口的类型)、以及InvocationHandler()接口的一个实现(调用处理器),需要知道对动态代理的所有方法调用都会重定位到调用处理器中。
通过一个例子来理解:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy {
	public static void main(String[] args){
		AnInterface proxy = (AnInterface)Proxy.newProxyInstance(AnInterface.class.getClassLoader(),    
				new Class[]{AnInterface.class}, new DynamicProxyHandler(new ActualObject()));
		//构造动态代理对象并转换为某接口类型以调用接口声明的方法
		proxy.method1("haha");
		proxy.method2();
	}
}

interface AnInterface{
	void method1(String s1);
	void method2();
}

class ActualObject implements AnInterface{
	public void method1(String s1) {
		System.out.println("调用了method2, 参数: " + s1);
	}

	public void method2() {
		System.out.println("调用了method2");
	}
}

class DynamicProxyHandler implements InvocationHandler{
	private Object proxied;    //实际使用的对象
	
	public DynamicProxyHandler(Object proxied){
		this.proxied = proxied;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("准备调用方法: " + method + " 参数:" + args);
		return method.invoke(proxied, args);     //动态调用某方法
	}
}
打印结果:
准备调用方法: public abstract void AnInterface.method1(java.lang.String) 参数:[Ljava.lang.Object;@75b84c92
调用了method2, 参数: haha
准备调用方法: public abstract void AnInterface.method2() 参数:null
调用了method2
分析可知,proxy作为一个代理,相当于保持了一个对实际对象的引用,同样通过代理这个中间人来操作实际对象。
那么这是怎么实现的呢?从Proxy.newInstance()方法起看看:
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);   //检查传入的调用处理器是否为空

        final Class<?>[] intfs = interfaces.clone();
        ......
        Class<?> cl = getProxyClass0(loader, intfs);

        
        ......

        final Constructor<?> cons = cl.getConstructor(constructorParams);  //利用反射机制
        return cons.newInstance(new Object[]{h});  //返回传入调用处理器构造的某个接口类型的对象,并向上转为Object
        ......
    }
然后回到调用Proxy.newInstance()方法的地方:
AnInterface proxy = (AnInterface)Proxy.newProxyInstance(AnInterface.class.getClassLoader(),    
				new Class[]{AnInterface.class}, new DynamicProxyHandler(new ActualObject()));
返回的Object对象向下转型成对应的接口类型,因为返回对象原本就是AnInterface类型的,所以没有任何问题,直接调用接口声明的方法,就能将方法调用重定位至调用处理器的Invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("准备调用方法: " + method + " 参数:" + args);
		return method.invoke(proxied, args);     //动态调用某方法
	}
传入代理引用、调用的方法信息跟参数列表作为参数,而方法体的实现则是让实际对象proxied根据传入的参数,调用相应的方法。

关于反射和动态代理其他更深层的剖析和应用等等等等,有空再研究~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值