Java反射和动态代理

1.Class类

Class类是一个用于描述类的类,其实例代表了一个运行中程序的类或对象,可以通过Class类实例来获取一个类的相关信息,比如Field,Method,Constructor等信息。

Class没有公有构造方法,一个Class对象是被JVM自动实例化的。

获取一个Class实例的三种方式:

  1. 类名.class
  2. 类实例.getClass()
  3. Class.forName("类全限定名")
public void getClazz() throws ClassNotFoundException {
	//类名.class	
	Class clazz1 = Person.class;
	//Class.forName("类全限定名")	
	Class clazz2 = Class.forName("org.test.reflect.Person");
	//类实例.getClass()
	Person per = new Person();
	Class clazz3 = per.getClass();
}

newInstance()方法:

如果一个Class对象clazz,clazz可以获取clazz本身所代表的类的方法、属性、注解等,而clazz.newInstance()将会返回真正的对象。看代码:

public class TestNewInstance {
	public static void main(String[] args)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class<Person> clazz = (Class<Person>) Class.forName("org.test.reflect.Person");
		Person per = clazz.newInstance();
		System.out.println(per);
	}
}
class Person {
	public String name;
	public int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
        public Person(){
        }
}

上面的代码有两个构造器,而newInstance()调用的是无参构造,如果注释无参构造,运行程序会报错:

所以一个类若声明一个带参的构造器,同时要声明一个无参数的构造器

反射相关代码:

public class TestReflect {

	// 获取Class实例的三种方式
	public Class getClazz() throws ClassNotFoundException {
		Class clazz1 = Person.class;
		Class clazz2 = Class.forName("org.test.reflect.Person");
		Person per = new Person();
		Class clazz3 = per.getClass();
		return clazz2;
	}

	// Class获取方法
	public void getMethod() throws ClassNotFoundException, NoSuchMethodException, SecurityException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
		Class clazz = getClazz();
		// method对象代表了test方法
		Method method = clazz.getMethod("test");
		Person per = (Person) clazz.newInstance();
		// method.invoke()执行per对象的test()方法
		method.invoke(per);
		// 获取所有方法
		Method[] methods = clazz.getDeclaredMethods();
		System.out.println("获取到了" + methods.length + "个声明方法");
	}

	// Class获取属性
	public void getField() throws ClassNotFoundException {
		Class clazz = getClazz();
		// 获取所有属性
		Field[] fields = clazz.getDeclaredFields();
		System.out.println("获取到了" + fields.length + "个声明的属性");
	}

	// Class获取接口
	public void getInterfaces() throws ClassNotFoundException {
		Class clazz = getClazz();
		// 获取所有接口
		Class[] interfaces = clazz.getInterfaces();
		System.out.println("获取到了" + interfaces.length + "个接口");
	}

	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException,
			IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
		TestReflect tr = new TestReflect();
		tr.getMethod();
		tr.getField();
		tr.getInterfaces();
	}
}

需要特别说明的是Method类中的invoke()方法:

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

Method对象的一个实例就代表了一个方法,method.invoke()是指调用这个方法,传入的参数obj是指定调用方法的对象,args是所需要的参数。

2.ClassLoader

在Class类的Outline中看到了getClassLoader(),在此对ClassLoader类稍作一些总结。

2.1文档整理

ClassLoader是用作加载类(具体来说是class文件,包括一些jar包)的,它的作用是将类名转换为文件名,并通过文件名找到相应的class文件

对于数组(array)而言,如果存放的元素为原型(int,double,char),数组没有ClassLoader,如果存放的是对象或包装类,数组的ClassLoader为元素的ClassLoader。

上面这一段很重要:ClassLoader使用的是委派模型(delegation model)来加载类和资源,每个ClassLoader的实例都有一个相关的父class locader,当尝试去加载类时,ClassLoader的实例会将加载委派为它的父class locader来执行。JVM内置的class loader为“bootstrap class loader”,并没有父class loader,所以可以知道bootstrap class loader是顶级的,或者说是root class loader。

上面明确声明了,JVM加载class文件(注意不是.java文件)是从定义的环境变量CLASSPATH中加载。

2.2类加载器

Java默认提供的三种类加载器:

  1. BootStrap ClassLoader
  2. ExtClassLoader
  3. AppClassLoader

-BootStrap ClassLoader

BootStrap 意为启动、引导,BootStrap ClassLoader是最顶级的加载器,没有父加载器,负责加载核心类库,诸如rt.jar、resources.jar、charsets.jar等,也就是JRE所在目录的lib文件夹下的内容。

-ExtClassLoader

Extended,扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。

这里额外提一嘴:JRE System Libraray,包括核心类库和扩展类库的所有jar包。

-AppClassLoader

Application,系统类加载器,用于加载应用程序classpath目录下的class文件。Java程序一般都使用AppClassLoader来加载类。


来看Class类中的getClassLoader()方法,可以获取当前class的类加载器。

先看一下这个方法的说明:

如果方法返回null,可能代表的是bootstrap class loader,也就是JVM内置的class loader。看代码:

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		Class<Person> clazz = (Class<Person>) Class.forName("org.test.reflect.Person");
		System.out.println(clazz.getClassLoader());
		//获取父class loader
		System.out.println(clazz.getClassLoader().getParent());
		System.out.println(clazz.getClassLoader().getParent().getParent());
	}

输出结果:

可以看到自定类Person的类加载器是AppClassLoader,AppClassLoader的父加载器是ExtClassLoader,再后面返回null,则可能是bootstrap class loader。

2.3委派模型

Delegation Model,也就是JDK文档中所说的,在ClassLoader的实例自己加载类之前,尝试将加载交给父class loader去执行,这种委派模型,决定了类加载的整个过程是由上到下的,而最终导致的加载顺序是:bootstrap class loader先进行加载,加载不到交给ExtClassLoader,然后是AppClassLoader,最后才是自定义的类加载器。

图示:(图片来源:https://img-blog.csdn.net/20180805222513969?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ2MzQzMzg=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

3.动态代理

代理的意思就是只作为一个门面,而实际干活的另有其人。使用动态代理,可以在核心业务执行前后加入自定义的逻辑。

Java动态代理可以分为JDK动态代理,也就java.lang.reflect包提供的代理,还有cglib动态代理。

SpringAOP底层是JDK动态代理,而JDK动态代理是基于接口的。SpringAOP典型的应用是数据库事务管理,在某些情况下会出现@Transaction失效的问题,一个排错点就是看拦截的是否为接口。

-Proxy类

提供了为接口创建代理对象的方法,比如:newProxyInstance(),对于此方法JDK文档明确指出了:To create a proxy for some interface。一定要注意是接口。

每个代理对象都有一个相应的invocation handler,invocation handler实现了InvocationHandler接口,此接口中只有一个invoke()方法,当代理对象调用代理接口方法时,实际是把对方法的执行交给invoke()方法来执行,同时向invoke()方法传递三个参数:代理对象、要执行的方法和方法参数。

JDK文档中的proxy class , proxy interface , proxy instance,再加上newProxyInstance()方法,让我对这些概念感到混乱,理清后还是总结一下:

  • proxy class:代理类,HelloWorldImpl实现了HelloWorld接口,就是一个代理类
  • proxy interface:代理接口,HelloWorld接口就是一个代理接口
  • proxy instance代理实例,代理类的实例

现在再来看newProxyInstance()方法:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

方法参数说明:

  • loader:proxy class的classLoader
  • interfaces:proxy class实现的所有interface
  • h:相应的InvocationHandler实例

再看文档方法返回值的说明:

可见返回的是proxy class的proxy instance,返回类型是Object,再使用此方法时,应该将返回结果强转为相应的代理类对象(proxy instance)。另外还提到了class loader,这个class loader是代理类的class loader。

看文档中给出的newProxyInstance()方法示例:(注意Foo是一个接口,接口啊)

-InvocationHandler接口

这是一个很重要的接口,看文档中的说明:

每个代理实例都会有一个相应的invocation handler,创建invocation handler只要实现此接口即可。

只有一个invoke()方法:

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

-case实例

下面来根据一个实例来对动态代理进行说明:

首先是HelloWorld接口和继承类HelloWorldImpl,这两个并没啥好说的。

HelloWorld.java:

public interface HelloWorld {
	public void say();
}

HelloWorldImpl.java:

public class HelloWorldImpl implements HelloWorld {
	@Override
	public void say() {
		System.out.println("hello world !!");
	}
}

ProxyInstance.java:

public class ProxyInstance {

        //目标对象,也就是被代理的类
	private HelloWorld target;
	
	public ProxyInstance(HelloWorld target) {
		this.target=target;
	}
	
	//生成代理对象,这个对象类型应该是HelloWorld,即接口
	public HelloWorld getProxy(InvocationHandlerInstance handler) {
		return (HelloWorld) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
	}
}

ProxyInstance类用于生成代理对象(proxy instance),按照上面叙述的必须使用接口原则,在getProxy()方法中进行强转,就得到一个HelloWorld(接口)类型的代理对象。

注意:在ProxyInstance类中,传入目标对象target,是因为newProxyInstance()需要用到target相关的classLoader和其实现的interfaces。

InvocationHandlerInstance.java:

public class InvocationHandlerInstance implements InvocationHandler{

	private HelloWorld target;
	public InvocationHandlerInstance(HelloWorld target) {
		this.target =target;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("-----say()之前的逻辑------");
                //调用say()方法
		Object obj = method.invoke(target, args);
		System.out.println("-----say()之后的逻辑------");
		return obj;
	}
}

在InvocationHandlerInstance类中传入target对象,是因为method.invoke(target,args)方法其实就是say()方法的调用,但需要指定是哪个对象的say()方法,所以传入target对象。

要保证proxy的target对象和invocation handler的target对象是同一个,因为就像之前所说的,proxy只是一个门面,把任务(也就是target)交给invocation handler来执行,如果二者的target不一致肯定不行,会报异常:UndeclaredThrowableException,从名字看可以知道是定义/叙述不清晰导致的异常。

许多框架底层的实现都使用了动态代理,在框架中可能会经常遇到这个异常。

程序入口:TestDemo.java

public class TestDemo {
	public static void main(String[] args) {
		
		//创建目标对象target
		HelloWorldImpl hw =new HelloWorldImpl();
		
		//拿到一个handler,传入目标对象为hw
		InvocationHandlerInstance handler = new InvocationHandlerInstance(hw);

		//拿到一个代理对象,传入目标对象为hw
    		ProxyInstance proxyInstance = new ProxyInstance(hw);
		HelloWorld proxy = proxyInstance.getProxy(handler);

		//proxy.say()并不是真的调用say()方法,而是把say()方法的调用交给invoke()方法
		proxy.say();
	}
}

在TestDemo中应该注意到,为InvocationHandler和Proxy传入的是相同的target对象,即hw,一定要注意这一点。

在上面的实例中是将Proxy和InvocationHandler分两个类来写,这样逻辑比较清楚。实际可以写在一个类中,这样只要定义一个target,就能将其传入Proxy和InvocationHandler了。

参考博客:

https://www.cnblogs.com/tech-bird/p/3525336.html

https://blog.csdn.net/u014634338/article/details/81434327

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值