1.Class类
Class类是一个用于描述类的类,其实例代表了一个运行中程序的类或对象,可以通过Class类实例来获取一个类的相关信息,比如Field,Method,Constructor等信息。
Class没有公有构造方法,一个Class对象是被JVM自动实例化的。
获取一个Class实例的三种方式:
- 类名.class
- 类实例.getClass()
- 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默认提供的三种类加载器:
- BootStrap ClassLoader
- ExtClassLoader
- 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,最后才是自定义的类加载器。
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.youkuaiyun.com/u014634338/article/details/81434327