Class类描述的是整个类的信息,在Class类中提供的forName()
方法,这个方法根据ClassPath
配置的路径进行类的 加载,如果说现在你的类的加载路径可能是网络、文件,这个时候就必须实现类加载器,也就是ClassLoader
类的 主要作用。
认识ClassLoader
首先通过Class类观察如下方法:
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*
* <p> If a security manager is present, and the caller's class loader is
* not null and the caller's class loader is not the same as or an ancestor of
* the class loader for the class whose class loader is requested, then
* this method calls the security manager's {@code checkPermission}
* method with a {@code RuntimePermission("getClassLoader")}
* permission to ensure it's ok to access the class loader for the class.
*
* <p>If this object
* represents a primitive type or void, null is returned.
*
* @return the class loader that loaded the class or interface
* represented by this object.
* @throws SecurityException
* if a security manager exists and its
* {@code checkPermission} method denies
* access to the class loader for the class.
* @see java.lang.ClassLoader
* @see SecurityManager#checkPermission
* @see java.lang.RuntimePermission
*/
@CallerSensitive
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
范例:编写一个简单的反射程序,来观察ClassLoader的存在
package www.bit.Fanshe;
public class TestClassLoader {
public static void main(String[] args) {
Class<?> cls = TestClassLoader.class;
System.out.println(cls.getClassLoader());
System.out.println(cls.getClassLoader().getParent());
System.out.println(cls.getClassLoader().getParent().getParent());
}
}
运行结果如下:
Files\Java\jdk1.8.0_181\jre\lib\rt.jar;E:\Java\code\out\production\code" www.bit.Fanshe.TestClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4554617c
null
Process finished with exit code 0
此时出现了两个类加载器:ExtClassLoader
(扩展类加载器)、AppClassLoader
(应用程序类加载器)。
那么,什么是类加载器?
JVM设计团队把类加载阶段中的"通过一个类的全名称来获取描述此类的二进制字节流"这个动作放在Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称之为"类加载器".
JDK中内置的三大类加载器
- Bootstrap(启动类加载器):
- 使用C++实现,是JVM的一部分。其他所有类加载器均使用Java实现
- 负责将存放于Java_HOME\lib目录下的能被JVM识别的类库(rt.jar—存放了Java所有基础类库,java.lang,java.util)加载到JVM中。
- 启动类加载器无法将Java程序直接引用
- ExtClassLoader(扩展类加载器):
- 使用Java实现,并且可以被Java程序直接引用
- 加载Java_HOME\lib\ext目录下能被识别的类库
- APPClassLoader(应用程序类加载器):
- 负责加载用户路径(classPath)上指定的类库
- 如果应用程序中没有自定义类加载器,则此加载器就是Java程序中默认的类加载器
类加载器双亲委派模型
我们的应用程序都是由这三种加载器互相配合进行加载的,如果有必要,还可以加入自定义的类加载器。这些类加载器的关系一般如下图所示:
JDK内置的三种类加载器与用户自定义类加载器之间的层次关系称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的父类加载器外,其余的类加载器都应有自己的父类加载器。
执行流程
如果一个类加载器收到了类加载请求,他首先不会自己去尝试加载此类,而是把类加载请求委托给父类加载器完成,每一个层次类加载器均是如此。因此,所有的加载请求都应当传送到顶层的 BootStrap
加载器中,只有当父类加载器反馈无法完成加载请求时(在自己搜索范围内没有找到此类),子加载器才会尝试自己去加载。
类加载器的双亲委派模型从JDK1.2引入后被广泛应用于之后几乎所有的Java程序中,但它并不是强制性约束,甚至 可以破坏双亲委派模型来进行类加载,最典型的就是OSGI
技术。
意义
双亲委派模型保证Java程序稳定运行。有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object
类,它存放在rt.jar
中,无论哪一个类加载器要加载这个类, 最终都是委派给处于顶端的启动类加载器进行加载。
因此,Java中基础类库一定由顶层BootStrap
类加载器加载,诸如Object等核心类在各种类加载器环境下都是同一个类。
范例:观察CLassLoader.loadClass()方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类加载器给用户提供最大的帮助为:可以通过动态的路径进行类的加载操作。
比较两个类相等的前提:必须是由同一个类加载器加载的前提下才有意义。否则,即使两个类来源于同一个Class 文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类注定不相等。
反射与代理设计模式
基础代理设计模式
代理设计模式的核心本质在于:一个接口有两个子类,一个负责真实业务,一个负责与真实业务有关的所有辅助性操作。按照这样的原则,一个基础的代理设计模式如下:
https://blog.youkuaiyun.com/sifanchao/article/details/83421269
现在的问题是:在开发中并不知道项目会有多少个接口,如果这些接口都需要使用到代理模式,那么就意味着每一个接口都需要编写两个子类,再假设这些接口的代理类的功能几乎都一样。
之前的这种代理设计只是一种最简单的代理设计,所以这种代理设计只能够代理一个接口的子类对象,无法代理更多的接口子类对象。要想真正使用代理设计模式,我们需要引入动态代理设计模式。
动态代理设计模式
动态代理模式的核心特点:一个代理类可以代理所有需要被代理的接口的子类对象。
/**
* 动态代理实现的标识接口,只有实现此接口才具备有动态代理的功能
*/
public interface InvocationHandler {
/**
* invoke表示的是调用执行的方法,但是所有的代理类返回给用户的接口对象都属于代理对象
* 当用户执行接口方法的时候所调用的实例化对象就是该代理主题动态创建的一个接口对象
* @param proxy 表示被代理的对象信息
* @param method 返回的是被调用的方法对象,取得了Method对象则意味着可以使用invoke()反射调用方法
* @param args 方法中接收的参数
* @return 方法的返回值
* @throws Throwable 可能产生的异常
* /
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
如果要想进行对象的绑定,那么就需要使用一个Proxy程序类,这个程序类的功能是可以绑定所有需要绑定的接口 子类对象,而且这些对象都是根据接口自动创建的,该类有一个动态创建绑定对象的方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
范例:动态代理设计实现:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface ISubject {
void eat(int count);
}
class RealSubject implements ISubject{
@Override
public void eat(int count) {
System.out.println("吃"+count+"元钱海底捞");
}
}
class ProxySubject implements InvocationHandler {
// 绑定任意接口的对象,使用Object描述
private Object realObject;
/**
* 绑顶真实主题类
* @param realObject
* @return 代理类
*/
public Object bind(Object realObject){
// 保存真实主题对象
this.realObject = realObject;
return Proxy.newProxyInstance(realObject.getClass().getClassLoader(),
realObject.getClass().getInterfaces(),this);
}
public void before(){
System.out.println("eat before");
}
public void after(){
System.out.println("eat after");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.before();
// 调用真实主题类
Object result = method.invoke(this.realObject,args);
this.after();
return result;
}
}
public class TestClassLoader {
public static void main(String[] args) {
ISubject iSubject = (ISubject) new ProxySubject().bind(new RealSubject());
iSubject.eat(200);
}
}
运行结果:
eat before
吃200元钱海底捞
eat after
Process finished with exit code 0