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.csdn.net/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