總結與回顧(接上篇)
1、上一篇講到我們自己實現動態代理。
1.1 自己模擬的動態代理
不需要手動創建類文件(因爲一旦手動創建類文件,就會產生類爆炸),通過接口反射生成一個類文件,然後調用第三方的編譯技術,動態編譯這個產生的類文件成class文件,繼而利用UrlclassLoader(因爲這個動態產生的class不在工程當中所以需要使用UrlclassLoader)把這個動態編譯的類加載到jvm當中,最後通過反射把這個類實例化。
那麼JDK實現的動態代理,又是怎樣實現的呢?
上一篇有一個問題,我們的邏輯都是打印語句。那我們的邏輯如何動態?怎麼代理任何邏輯?
帶着疑問我們來看一下 jdk的動態代理。如下圖是jdk代理源碼的核心方法,jdk爲什麼需要一個classloader?
jvm加載圖,如下。jvm先回加載所有預編譯好的class文件,但是動態產生的類怎麼辦呢?就需要我們傳入classloader 然後再load一遍到jvm中。
1.2jdk動態代理是怎麼回事?
如下代碼片段,我們要調用JDK動態代理。只需要傳入三個參數。類加載器,目標類實現的接口,與InvocationHandler對象。
//第一個參數,傳入類加載器。怎麼證明一個類在項目中的唯一呢?很多人會說hashcode,
//其實是根據類加載器來判斷。因爲一個類只會加載一次
//第二個參數,傳入被代理類(目標對象的類)的接口數組。因爲類是可以多實現,所以是一個數據
//第三個參數,傳入InvocationHandler對象,但是InvocationHandler是一個接口。
//接口中只有一個方法,invoke(代理對象,目標對象,參數) 目標對象.invoke()調用反射傳入目標對象與方法實現動態代理
UserDao dao = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(),
new Class[]{UserDao.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("假裝增強,打印sql語句");
return method.invoke(new UserDaoImpl(),args);
}
});
dao.query();
System.out.println("------------------華麗分分割線--------------------");
System.out.println(dao.query("JDK動態代理。。。。"));
}
-------------------------控制檯輸出---------------------------------------------
假裝增強,打印sql語句
假裝查詢數據庫
------------------華麗分分割線--------------------
假裝增強,打印sql語句
JDK動態代理。。。。
1.3如上代碼片段,只需要傳入三個參數就可以對代理對象實現動態的業務與增強?那麼jdk底層是怎麼做的呢?
·我們先猜想,來改進一下上一篇的ProxyUtile工具類。
改造後的山寨版動態代理類核心如下。
public static Object newInstance(Class[] targetIn, CoustomInvocationHandler h){
Object proxy=null;
Class targetInf = targetIn[0];
Method methods[] =targetInf.getMethods();
//定義換行
String line="\n";
//定義tab空格
String tab ="\t";
String infName = targetInf.getSimpleName();
String content ="";
String packageContent = "package com.google;"+line;
String importContent = "import "+targetInf.getName()+";"+line
+"import com.bing.li.util.CoustomInvocationHandler;"+line
+"import java.lang.*;"+line
+"import java.lang.reflect.Method;"+line;
String clazzFirstLineContent = "public class $Proxy implements "+infName+"{"+line;
String filedContent =tab+"private CoustomInvocationHandler h;"+line;
String constructorContent =tab+"public $Proxy (CoustomInvocationHandler h){" +line
+tab+tab+"this.h =h;"
+line+tab+"}"+line;
String methodContent = "";
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName();
String methodName =method.getName();
String argsContent = "";
String paramsContent="";
int flag =0;
methodContent+=tab+"public "+returnTypeName+" "+methodName+"(";
//獲取方法的所有參數
Class parameters[] = method.getParameterTypes();
//參數字符串
StringBuilder args = new StringBuilder();
for (int i = 0; i < parameters.length; i++) {
//參數的類型,形參 String p0,Sting p1,
methodContent+=parameters[i].getSimpleName() + " p" +i;
args.append("p" +i);
if (i != parameters.length - 1) {
methodContent+=",";
args.append(",");
}
}
methodContent+="){"+line
+tab+tab+"try {"+line
+tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line;
if (!returnTypeName.equals("void")) {
// methodContent+=tab + tab +"Class[] args = new Class[]{" + args + "};"+line;
methodContent+= tab + tab + "return (" + returnTypeName + ")h.invoke(method, new Object[]{"+ args +"});" + line;
}else {
methodContent+= tab + tab + " h.invoke(method,new Object[]{"+paramsContent+"});" + line;
}
methodContent+= tab+tab+"}catch (Exception e){"+line+tab+tab+"}"+line;
if (!returnTypeName.equals("void")) {
methodContent+= tab+tab+tab+"return "+ null+ ";"+line;
}
methodContent+= tab+"}"+line;
}
content=packageContent+importContent+clazzFirstLineContent+filedContent+constructorContent+methodContent+"}";
File file1 =new File("G:\\com\\google");
File file =new File("G:\\com\\google\\$Proxy.java");
try {
//判斷是否是一個目錄
if (!file1.isDirectory()){
//創建目錄
file1.mkdirs();
}
if (!file.exists()) {
//創建文件
file.createNewFile();
}
//將字符串 寫出到磁盤
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//創建需要加載.class的地址
URL[] urls = new URL[]{new URL("file:G:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
//加載class對象
Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
Constructor constructor = clazz.getConstructor(CoustomInvocationHandler.class);
proxy = constructor.newInstance(h);
//clazz.newInstance();
//Class.forName()
}catch (Exception e){
e.printStackTrace();
}
return proxy;
}
調用工具類,控制檯輸出如下,我們也能動態實現業務邏輯部分了。
public static void main(final String[] args) {
UserDao proxy = (UserDao) ProxyUti2.newInstance(new Class[]{UserDao.class}, new CoustomInvocationHandler() {
@Override
public Object invoke(Method method, Object[] arg){
System.out.println("山寨版JDK動態代理增強打印sql");
try {
Object invoke = method.invoke(new UserDaoImpl(), arg);
return invoke;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
});
proxy.query();
------------------------------控制檯輸出----------------------------
山寨版JDK動態代理增強打印sql
假裝查詢數據庫
我們自己實現的動態代理缺點有哪些呢?
缺點:首先要生成文件
缺點:動態編譯文件 class
編譯會產生IO操作,軟件性能的最終體現在IO操作。性能會降低。
缺點:需要一個URLclassloader
JDK 動態代理源碼分析:
如下是JDK 動態代理newProxyInstance方法 如下,最重要的一行代碼就是 Class<?> cl = getProxyClass0(loader, intfs); 通過這行代碼 其實就得到了代理類,然後通過代理類的構造方法,就實例化了一個代理對象。
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 克隆一下我們傳進來的 interface
final Class<?>[] intfs = interfaces.clone();
//安全驗證
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
* 找到我們的代理類。
*/
//這段方法執行完成,就得到我們的代理類
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//通過上面得到的代理對象,得到一個構造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//通過構造方法 new出我們的要得代理對象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
getProxyClass0(ClassLoader loader,Class<?>… interfaces) 方法。其實就一行代碼proxyClassCache.get(loader, interfaces);通過名字看出,緩存中拿到代理對象。那我們 繼續往下看,看get(loader, interfaces)方法它又做了什麼呢?
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
get(loader, interfaces)方法如下。
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
//JDK去 cache中拿緩存,如果緩存中存在。就不在生成,直接拿。
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
//通過這句代碼得到了我們的value
V value = supplier.get();
if (value != null) {
//value就是我們要的這個代理類。
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
以上源碼部分可以看出通過V value = supplier.get();這一行代碼得到了我們要的類。接着,我們繼續分析這一行代碼。到底做了什麼?通過斷點的方式,得到如下代碼。
@Override
public synchronized V get() { // serialize access
// re-check
//從jdkmap衆去獲取。。
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try {
//獲取到value 說白了,這個方法最後返回 回去的value取值就是這一行代碼。
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
CacheValue<V> cacheValue = new CacheValue<>(value);
// try replacing us with CacheValue (this should always succeed)
if (valuesMap.replace(subKey, this, cacheValue)) {
// put also in reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
//獲取到我們要的類文件
return value;
}
}
通過如上代碼,得到value = Objects.requireNonNull(valueFactory.apply(key, parameter));value取值的這段代碼就是這一句。接口我們繼續分析,這一句代碼到底做了些什麼。這是一個類部類,外部類的名稱叫ProxyClassFactory 的apply方法。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
//得到我們傳進來的接口然後循環
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
-- 這裏就非常重要。這裏我們傳進來的已經是一個class,然後它用反射再load一次?爲什麼要再load一次呢?
-- 這裏不是重複了一次嘛?我們傳進來本身就是一個class
-- 其實JDK這裏做了一個相對高級的判斷,這裏重複的load一遍。是爲了判斷這兩個類是不是相等。
-- 上一篇說到,兩個類相等有一個前提條件,同一個類加載器。所以它用傳進來的loader
-- 再生了了一遍整個類。
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
--這裏對兩個類 做判斷、如果不相等。則拋異常。
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
--如果相等,則就往下面走。 這個就是代理類的包名。
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
--循環所有的interface
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
-- 判斷接口是不是 public 如果不是公共的 則不能實現它。
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
-- 跑完以上代碼,就得到了 包名+類名+一個類標識。
-- 包名是JDK 內部這一段大嘛寫死的public static final String PROXY_PACKAGE = "com.sun.proxy";
-- proxyClassNamePrefix 是固定的成員靜態變量$Proxy
-- 然後再生成一個隨機數。防止多線程重複的情況。
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
-- 這一段很重要 ,這一句代碼直接生成一個代理類的class byte數組。
-- 那麼byte怎麼變成這個方法所需要的class對象呢?
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
-- 通過這一段代碼就返回了。我們要的class類。
-- 這裏調用的native方法,JDK底層的方法。就不在深入研究了。
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags) 講一下這個方法,這個其實是JDK 的一個靜態方法。。
調用這個方法,生成的Class文件如下,其實就跟我們自己上面寫的山寨版的動態代理文件差不多。只不過我們先生成.java文件,在調用URLclassload 生成.class字節碼。然後讀取到內存中,產生對象。這裏一步到位了。這裏就不做多講解。有興趣的可以自己去品。
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});
FileOutputStream fileOutputStream = new FileOutputStream("G:\\$Proxy18.class");
fileOutputStream.write(bytes);
fileOutputStream.flush();
項目源碼地址
總結:
JDK動態代理底層:
通過接口反射,得到字節碼(就是那個byte[])然後通過native defineClass0
方法把字節碼轉成calss
JDK動態代碼基於接口實現反射,
Cglib 基於繼承
cglib 是基於asm框架實現。ASM框架是一個操作字節碼的一個框架。
JDK 與cglib 性能呢?
性能基本上可以忽略,看應用場景。沒有接口的情況下就用cglib