什麼是代理
# 增強一個對象的功能
# 買火車票,App 就是一個代理,他代理了火車站,小區當中的代售窗口
# Java 當中如何實現代理
Java 實現的代理的兩種辦法
代理的名詞
代理對象 ===> 增強後的對象
目標對象 ===> 被增強的對象
他們不是絕對的,會根據情況發生變化
靜態代理
1、繼承
代理對象繼承目標對象,重寫需要增強的方法;
缺點:會代理類過多,非常複雜
2、聚合
目標對象和代理對象實現同一個接口,代理對象當中要包含目標對象。
缺點:也會產生類爆炸,只不過比繼承少一點點
# 總結:如果在不確定的情況下,儘量不要去使用靜態代理。因爲一旦你寫代碼,就會產生類,一旦產生類就爆炸。
動態代理
自己模擬 JDK 動態代理
# 不需要訂閱專欄手動創建類文件(因爲一旦手動創建類文件,就會產生類爆炸),通過接口反射生成一個類文件,然後調用第三方的編譯技術,動態編譯這個產生的類文件成class文件,繼而利用UrlclassLoader(因爲這個動態產生的class不在工程當中所以需要使用UrlclassLoader)把這個動態編譯的類加載到jvm當中,最後通過反射把這個類實例化。
缺點:首先要生成文件
缺點:動態編譯class文件
缺點:需要一個 URLclassloader
軟件性能的最終體現在 IO 操作
接口:FutureDao
/**
* @description: 接口
* @author: Mr.Li
* @date: Created in 2020/7/1 15:42
* @version: 1.0
* @modified By:
*/
public interface FutureDao {
public void query();
public String returnString();
}
實現類:FutureDaoImpl
/**
* @description: 實現類
* @author: Mr.Li
* @date: Created in 2020/7/1 15:43
* @version: 1.0
* @modified By:
*/
public class FutureDaoImpl implements FutureDao {
@Override
public void query(){
System.out.println("FutureLL");
}
@Override
public String returnString() {
return "Return String";
}
}
自己實現動態代理:ProxyUtil
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* @description: 自己實現動態代理
* @author: Mr.Li
* @date: Created in 2020/7/1 13:19
* @version: 1.0
* @modified By:
*/
public class ProxyUtil {
/**
* content --->string
* .java io
* .class
* <p>
* .new 反射----》class
*
* @return
*/
public static Object newInstance(Object target) {
// 定義代理對象
Object proxy = null;
// getClass(): Returns the runtime class of this {@code Object}.
// getInterfaces(): Determines the interfaces implemented by the class or interface represented by this object.
Class targetInf = target.getClass().getInterfaces()[0];
Method methods[] = targetInf.getDeclaredMethods();
String line = "\n";
String tab = "\t";
// getSimpleName(): Returns the simple name of the underlying class as given in the source code.
// infName = "FutureDao";
String infName = targetInf.getSimpleName();
String content = "";
String packageContent = "package com.google;" + line;
// Returns the name of the entity represented by this {@code Class} object, as a {@code String}.
// targetInf.getName() = "com.futurell.dao.FutureDao"
String importContent = "import " + targetInf.getName() + ";" + line;
String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
String filedContent = tab + "private " + infName + " target;" + line;
String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
+ tab + tab + "this.target = target;"
+ line + tab + "}" + line;
String methodContent = "";
for (Method method : methods) {
// getReturnType(): Returns a Class object that represents the formal return type of the method represented by this {@code Method} object.
String returnTypeName = method.getReturnType().getSimpleName();
// getName(): Returns the name of the method represented by this {@code Method} object, as a {@code String}.
String methodName = method.getName();
// Sting.class String.class
Class args[] = method.getParameterTypes();
String argsContent = "";
String paramsContent = "";
int flag = 0;
for (Class arg : args) {
//getSimpleName(): Returns the simple name of the underlying class as given in the source code.
String temp = arg.getSimpleName();
// String
// String p0,Sting p1,
argsContent += temp + " p" + flag + ",";
paramsContent += "p" + flag + ",";
flag ++;
}
if (argsContent.length() > 0) {
// lastIndexOf(): Returns the index within this string of the last occurrence of the specified substring.
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
}
methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
+ tab + tab + "System.out.println(\" Log \");" + line;
if (returnTypeName.equals("void")) {
methodContent += tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line;
} else {
methodContent += tab + tab + "return target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line;
}
}
content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
File file = new File("e:\\com\\google\\$Proxy.java");
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
/**
* 編譯過程如下:
*/
// getSystemJavaCompiler(): Gets the Java; programming language compiler provided with this platform.
// 得到編譯類,可以動態的編譯一些文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// getStandardFileManager(): Gets a new instance of the standard file manager implementation for this tool.
// 編譯文件需要文件管理器
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
// getJavaFileObjects(): Gets file objects representing the given files.
// 把文件放到文件管理器中
Iterable units = fileMgr.getJavaFileObjects(file);
// getTask(): Creates a future for a compilation task with the given components and arguments.
// JavaCompiler.CompilationTask: Interface representing a future for a compilation task.
// 把文件管理器當成一個任務來執行
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
// call(): Performs this compilation task.
t.call();
fileMgr.close();
// URL[]: Class {@code URL} represents a Uniform Resource Locator, a pointer to a "resource" on the World Wide Web.
URL[] urls = new URL[]{new URL("file:E:\\\\")};
// URLClassLoader: This class loader(類加載器) is used to load classes and resources from a search path of URLs
// referring(引用) to both JAR files(Java Archive, Java歸檔) and directories(目錄).
// JAR: 通常用於聚合大量的Java類文件、相關的元數據和資源(文本、圖片等)文件到一個文件,以便開發Java平臺應用軟件或庫。
URLClassLoader urlClassLoader = new URLClassLoader(urls);
// loadClass(): Loads the class with the specified <a href="#name">binary name</a>.
Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
// getConstructor(): Returns a {@code Constructor} object that reflects(反應) the specified
// public constructor of the class represented by this {@code Class} object.
// clazz這個類它有構造方法,只能以構造方法來new這個類的實例,所以需要先得到該類的構造方法
Constructor constructor = clazz.getConstructor(targetInf);
// newInstance(): Uses the constructor represented by this {@code Constructor} object to
// create and initialize a new instance of the constructor's
// declaring class, with the specified initialization parameters.
// 通過構造方法來創建實例
proxy = constructor.newInstance(target);
// clazz.newInstance();
// Class.forName()
} catch (Exception e) {
e.printStackTrace();
}
/**
* public UserDaoLog(UserDao target){
* this.target = target;
* }
*/
return proxy;
}
}
測試類:Test
import com.futurell.dao.FutureDao;
import com.futurell.dao.FutureDaoImpl;
import com.futurell.proxy.ProxyUtil;
/**
* @description: 測試
* @author: Mr.Li
* @date: Created in 2020/7/1 10:56
* @version: 1.0
* @modified By:
*/
public class Test {
public static void main(String[] args) {
FutureDao fProxy = (FutureDao) ProxyUtil.newInstance(new FutureDaoImpl());
fProxy.query();
System.out.println("------------------");
FutureDao fProxyString = (FutureDao) ProxyUtil.newInstance(new FutureDaoImpl());
System.out.println(fProxyString.returnString());
}
}
/**
* 輸出:
* ------------------
* Log
* FutureLL
* ------------------
* Log
* Return String
*/
JDK 動態代理
接口和實現類與上述自定義相同
代理類:FutureInvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @description:
* @author: Mr.Li
* @date: Created in 2020/7/2 12:24
* @version: 1.0
* @modified By:
*/
public class FutureInvocationHandler implements InvocationHandler {
Object target;
public FutureInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy 代理對象
* @param method 代理對象包含目標對象,目標對象中的方法,就是method
* @param args 目標方法的參數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy");
return method.invoke(target, args);
}
}
測試類:Test
import com.futurell.JDK.dao.FutureDao;
import com.futurell.JDK.dao.FutureDaoImpl;
import com.futurell.JDK.proxy.FutureInvocationHandler;
import java.lang.reflect.Proxy;
/**
* @description: 測試
* @author: Mr.Li
* @date: Created in 2020/7/1 10:56
* @version: 1.0
* @modified By:
*/
public class Test {
public static void main(String[] args) {
FutureDao jdkProxy = (FutureDao) Proxy.newProxyInstance(
// getClassLoader(): Returns the class loader for the class.
// 判斷一個類是否相同,需要根據類加載器是否相同來判斷,爲了保證加載器可用,那麼傳入當前所在類的ClassLoader
// 在自定義動態代理的代碼中,使用了URLClassLoader,因爲自定義的類不在工程當中
Test.class.getClassLoader(),
// Class targetInf = target.getClass().getInterfaces()[0];
// 自定義使用了上邊的代碼,我們需要得到接口,用來得到接口中的方法,然後對方法進行代理
new Class[]{FutureDao.class},
new FutureInvocationHandler(new FutureDaoImpl()));
jdkProxy.query();
System.out.println("--------------");
System.out.println(jdkProxy.returnString());
}
}
CGLIB
如果是要實現 CGLIB,需要先添加 cglib 依賴,然後實現一個接口 MethodInterceptor,重寫 intercept() 方法
依賴
<!-- 使用CGLIB代理需要導入jar包 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
<scope>compile</scope>
</dependency>
代理類:MyMethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定義MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理對象
* method:被代理對象方法
* objects:方法入參
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入後者通知======");
return object;
}
}
JDK 底層原理
測試類:Test
FutureDao jdkProxy = (FutureDao) Proxy.newProxyInstance(
// getClassLoader(): Returns the class loader for the class.
// 判斷一個類是否相同,需要根據類加載器是否相同來判斷,爲了保證加載器可用,那麼傳入當前所在類的ClassLoader
// 在自定義動態代理的代碼中,使用了URLClassLoader,因爲自定義的類不在工程當中
Test.class.getClassLoader(),
// Class targetInf = target.getClass().getInterfaces()[0];
// 自定義使用了上邊的代碼,我們需要得到接口,用來得到接口中的方法,然後對方法進行代理
new Class[]{FutureDao.class},
new FutureInvocationHandler(new FutureDaoImpl())
);
測試類調用 Proxy 類的 newProxyInstance() 方法
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
Objects.requireNonNull(h);
// 拿到實現類的接口,使用接口可以對接口中的方法進行處理
final Class<?>[] intfs = interfaces.clone();
// 系統做的安全驗證,這個不需要我們去管
// getSecurityManager(): Gets the system security interface.
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);
}
// 通過 cl.getConstructor() 得到一個構造方法
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 成一個對象
// 所以我們需要看這裏 new 出來的對象是不是我們需要的代理對象
// 首先複製 cons.newInstance(new Object[]{h}),鼠標右鍵點擊 Evaluate Expression,將複製的代碼粘貼回車,看到 result 的值就是我們的代理對象
// 得到 cl 代理類,代理對象就輕易被 new 出來了,在我們的傳入的是 new FutureInvocationHandler(new FutureDaoImpl())
// 所以最重要的是我們怎麼得到的 cl 代理類,進入 getProxyClass0() 這個方法
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() 我們要知道如何得到的 cl 代理類
/**
* 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
// 如果實現給定接口的給定加載器定義的代理類存在,這將簡單地返回緩存的副本;否則,它將通過 ProxyClassFactory 創建代理類
// 這個方法只有一行代碼,我們進入 get() 方法
return proxyClassCache.get(loader, interfaces);
}
進入 get() 方法
/**
* Look-up the value through the cache. This always evaluates the
* {@code subKeyFactory} function and optionally evaluates
* {@code valueFactory} function if there is no entry in the cache for given
* pair of (key, subKey) or the entry has already been cleared.
*/
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
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
// 進入這個 get() 方法
V value = supplier.get();
if (value != null) {
// 最終返回的是這個 value,我們不知道從哪裏看的時候,就找到結果從下往上反推即可
// 向上看有一個 supplier.get() 它將返回值賦給了 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);
}
}
}
}
進入另一個 get() 方法
@Override
public synchronized V get() { // serialize access
// re-check
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
// 這個方法我們可以看出,結果需要返回 value,而 value 最終的取值是在下邊的 try-finally 中取值
V value = null;
try {
// 首先我們進入 apply() 方法
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);
// put into reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
// try replacing us with CacheValue (this should always succeed)
if (!valuesMap.replace(subKey, this, cacheValue)) {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
// 返回 value
return value;
}
進入 apply() 方法
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 這裏的 interfaces 的所有接口只有一個 FutureDao,循環所有接口,將接口賦給 intf
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
// 這個有個點需要注意, intf 本身就是一個 Class,爲什麼這裏還要再次得到一個 Class
// 判斷對象是否相同的一個前提,是否是同一個類加載器
// 原因: 這裏要判斷兩個接口是不是同一個接口,
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
// 如果兩個不相同那麼拋出異常: IllegalArgumentException,相等繼續執行後面的代碼
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.
*/
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();
// 這裏給 proxyPkg 加一個標識,防止併發的情況下類名相同
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
* 生成指定的代理類的二進制文件,
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
// 我們知道一個對象的產生順序:
// .java ---> .class ---ClassLoader---> JVM ---byte[]---> object
// 而這裏需要返回一個 Class,那麼將二進制文件轉換成對象就在 defineClass0() 方法中進行
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());
}
}
進入 defineClass0() 方法
// 這個方法是一個被 native 修飾的方法是一個本地方法,也就是一個 Java 調用非 Java 代碼的接口,再開發 JDK 的時候底層使用了 C 或 C++ 開發(可以去看一下 OpenJDK),也就是說 C 和 C++ 負責把 byte[] 變成 Class 對象
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
總結:
# 通過接口反射得到字節碼,然後把字節碼轉換成 Class,就得到了我們想要的對象
有興趣的同學可以關注我的個人公衆號,期待我們共同進步!!!