您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“如何寫java代理”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
一般經(jīng)常做java開(kāi)發(fā)的知道java的代理模式一共有三種,第一種也就是靜態(tài)代理,這種用法比較簡(jiǎn)單,沒(méi)有什么魔法棒,比較好理解,另外兩種分別是JDK代理和cglib代理,他們分別是對(duì)接口代理和對(duì)class類本身進(jìn)行代理,jdk代理要求類必須實(shí)現(xiàn)有一個(gè)或者多個(gè)接口,對(duì)接口進(jìn)行字節(jié)碼增強(qiáng)在內(nèi)存中實(shí)現(xiàn)新的class類去反射調(diào)用用戶target的實(shí)現(xiàn)類,這里需要說(shuō)明的是不管是cglic代理也好還是jdk代理他們?cè)趦?nèi)存中都要占據(jù)方法區(qū)資源(jdk8 叫原空間),從而達(dá)到代理目的,而cglib代理是對(duì)class類本身進(jìn)行字節(jié)碼增強(qiáng)配合fastclass來(lái)實(shí)現(xiàn)代理,關(guān)于更多的cglib和jdk代理相關(guān)的內(nèi)容大家可以google搜索一下,網(wǎng)上有很多這里不做再多的說(shuō)明。下面我們摒棄jdk,和cglib的復(fù)雜源碼來(lái)自己實(shí)現(xiàn)一個(gè)代理模式,來(lái)更深刻的了解一下代理究竟是怎么形成的。
代理模式是指給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。通俗的來(lái)講代理模式就是我們生活中常見(jiàn)的中介。這種模式有什么用呢?它可以在原對(duì)象的基礎(chǔ)上增強(qiáng)原對(duì)象的功能,比如在原對(duì)象調(diào)用一個(gè)方法的前后進(jìn)行日志、事務(wù)操作等。Spring AOP就使用了代理模式。如何實(shí)現(xiàn)代理模式呢?首先來(lái)看靜態(tài)代理。靜態(tài)代理是指在程序運(yùn)行前就已經(jīng)存在的編譯好的代理類是為靜態(tài)代理。實(shí)現(xiàn)靜態(tài)代理有四個(gè)步驟:
①定義業(yè)務(wù)接口;
②被代理類實(shí)現(xiàn)業(yè)務(wù)接口;
③定義代理類并實(shí)現(xiàn)業(yè)務(wù)接口;
④最后便可通過(guò)客戶端進(jìn)行調(diào)用。(這里可以理解成程序的main方法里的內(nèi)容)
我們按照這個(gè)步驟去實(shí)現(xiàn)靜態(tài)代理。需求:在向數(shù)據(jù)庫(kù)添加一個(gè)用戶時(shí)前后打印日志。
JDK DEMO示例
IUserService.java
public interface IUserService { void add(String name); }
UserServiceImpl.java
public class UserServiceImpl implements IUserService{ @Override public void add(String name) { System.out.println("數(shù)據(jù)庫(kù)中插入: "+name+" 的用戶"); } }
MyInvocationHandler.java
public class MyInvocationHandler implements InvocationHandler { //被代理對(duì)象,Object類型 private Object target; public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("準(zhǔn)備向數(shù)據(jù)庫(kù)中插入數(shù)據(jù)"); Object returnvalue = method.invoke(target, args); System.out.println("插入數(shù)據(jù)庫(kù)成功"); return returnvalue; } }
測(cè)試類
public static void main(String[] args) { IUserService target = new UserServiceImpl(); MyInvocationHandler handler = new MyInvocationHandler(target); IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(), target.getClass().getInterfaces(), handler); proxyObject.add("張玉龍"); }
使用上非常簡(jiǎn)單、網(wǎng)上demo也很多,不做充分講解,對(duì)jdk代理用法的小伙伴如果還不熟悉這塊代碼,就先了解一下jdk代理的使用方式,然后在回來(lái)繼續(xù)看下面的源碼分析
JDK代理源碼深度分析
這部分如果想要更快更好的理解,建議一邊對(duì)著源碼(本文JDK 1.8),一邊看著博客。畢竟自己親身實(shí)踐效果才好嘛。
Proxy.newProxyInstance( ClassLoaderloader, Class[] interfaces, InvocationHandler h)
產(chǎn)生了代理對(duì)象,所以我們進(jìn)到
newProxyInstance
的實(shí)現(xiàn):
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); 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; } }); } 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); } }
這段代碼核心就是通過(guò) getProxyClass0(loader, intfs)
得到代理類的Class對(duì)象,然后通過(guò)Class對(duì)象得到構(gòu)造方法,進(jìn)而創(chuàng)建代理對(duì)象。下一步看 getProxyClass0
這個(gè)方法。
//此方法也是Proxy類下的方法 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 //意思是:如果代理類被指定的類加載器loader定義了,并實(shí)現(xiàn)了給定的接口interfaces, //那么就返回緩存的代理類對(duì)象,否則使用ProxyClassFactory創(chuàng)建代理類。 return proxyClassCache.get(loader, interfaces); }
這里看到proxyClassCache,有Cache便知道是緩存的意思,正好呼應(yīng)了前面Look up or generate the designated proxy class。查詢(在緩存中已經(jīng)有)或生成指定的代理類的class對(duì)象這段注釋。
proxyClassCache
是個(gè)WeakCache類的對(duì)象,調(diào)用proxyClassCache.get(loader, interfaces); 可以得到緩存的代理類或創(chuàng)建代理類(沒(méi)有緩存的情況)。說(shuō)明WeakCache中有
get
這個(gè)方法。先看下WeakCache類的定義(這里先只給出變量的定義和構(gòu)造函數(shù)):
//K代表key的類型,P代表參數(shù)的類型,V代表value的類型。 // WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache 說(shuō)明proxyClassCache存的值是Class<?>對(duì)象,正是我們需要的代理類對(duì)象。 final class WeakCache<K, P, V> { private final ReferenceQueue<K> refQueue = new ReferenceQueue<>(); // the key type is Object for supporting null key private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>(); private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>(); private final BiFunction<K, P, ?> subKeyFactory; private final BiFunction<K, P, V> valueFactory; public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) { this.subKeyFactory = Objects.requireNonNull(subKeyFactory); this.valueFactory = Objects.requireNonNull(valueFactory); }
其中map變量是實(shí)現(xiàn)緩存的核心變量,他是一個(gè)雙重的Map結(jié)構(gòu):
(key, sub-key) -> value
。其中key是傳進(jìn)來(lái)的Classloader進(jìn)行包裝后的對(duì)象,sub-key是由WeakCache構(gòu)造函數(shù)傳人的
KeyFactory()
生成的。value就是產(chǎn)生代理類的對(duì)象,是由WeakCache構(gòu)造函數(shù)傳人的
ProxyClassFactory()
生成的
好,大體上說(shuō)完WeakCache這個(gè)類的作用,我們回到剛才 proxyClassCache.get(loader, interfaces);
這句代碼。get是WeakCache里的方法。源碼如下
//K和P就是WeakCache定義中的泛型,key是類加載器,parameter是接口類數(shù)組 public V get(K key, P parameter) { //檢查parameter不為空 Objects.requireNonNull(parameter); //清除無(wú)效的緩存 expungeStaleEntries(); // cacheKey就是(key, sub-key) -> value里的一級(jí)key, Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey //根據(jù)一級(jí)key得到 ConcurrentMap<Object, Supplier<V>>對(duì)象。如果之前不存在,則新建一個(gè)ConcurrentMap<Object, Supplier<V>>和cacheKey(一級(jí)key)一起放到map中。 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 //這部分就是調(diào)用生成sub-key的代碼,上面我們已經(jīng)看過(guò)怎么生成的了 Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); //通過(guò)sub-key得到supplier Supplier<V> supplier = valuesMap.get(subKey); //supplier實(shí)際上就是這個(gè)factory Factory factory = null; while (true) { //如果緩存里有supplier ,那就直接通過(guò)get方法,得到代理類對(duì)象,返回,就結(jié)束了,一會(huì)兒分析get方法。 if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value != null) { 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 //下面的所有代碼目的就是:如果緩存中沒(méi)有supplier,則創(chuàng)建一個(gè)Factory對(duì)象,把factory對(duì)象在多線程的環(huán)境下安全的賦給supplier。 //因?yàn)槭窃趙hile(true)中,賦值成功后又回到上面去調(diào)get方法,返回才結(jié)束。 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); } } } }
所以接下來(lái)我們看Factory類中的get方法。
public synchronized V get() { // serialize access // re-check Supplier<V> supplier = valuesMap.get(subKey); //重新檢查得到的supplier是不是當(dāng)前對(duì)象 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 { //代理類就是在這個(gè)位置調(diào)用valueFactory生成的 //valueFactory就是我們傳入的 new ProxyClassFactory() //一會(huì)我們分析ProxyClassFactory()的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) //把value包裝成弱引用 CacheValue<V> cacheValue = new CacheValue<>(value); // put into reverseMap // reverseMap是用來(lái)實(shí)現(xiàn)緩存的有效性 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 return value; } }
撥云見(jiàn)日,來(lái)到ProxyClassFactory的apply方法,代理類就是在這里生成的。
//這里的BiFunction<T, U, R>是個(gè)函數(shù)式接口,可以理解為用T,U兩種類型做參數(shù),得到R類型的返回值 private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names //所有代理類名字的前綴 private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names //用于生成代理類名字的計(jì)數(shù)器 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); //驗(yàn)證代理接口,可不看 for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { 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 //代理類訪問(wèn)控制符: public ,final 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. */ //驗(yàn)證所有非公共的接口在同一個(gè)包內(nèi);公共的就無(wú)需處理 //生成包名和類名的邏輯,包名默認(rèn)是com.sun.proxy,類名默認(rèn)是$Proxy 加上一個(gè)自增的整數(shù)值 //如果被代理類是 non-public proxy interface ,則用和被代理類接口一樣的包名 for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); 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(); //代理類的完全限定名,如com.sun.proxy.$Proxy0.calss String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ //核心部分,生成代理類的字節(jié)碼 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { //把代理類加載到JVM中,至此動(dòng)態(tài)代理過(guò)程基本結(jié)束了 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()); } } }
到這里其實(shí)已經(jīng)分析完了,但是本著深究的態(tài)度,決定看看JDK生成的動(dòng)態(tài)代理字節(jié)碼是什么,于是我們將字節(jié)碼保存到磁盤上的class文件中。代碼如下:
public static void main(String[] args) { IUserService target = new UserServiceImpl(); MyInvocationHandler handler = new MyInvocationHandler(target); //第一個(gè)參數(shù)是指定代理類的類加載器(我們傳入當(dāng)前測(cè)試類的類加載器) //第二個(gè)參數(shù)是代理類需要實(shí)現(xiàn)的接口(我們傳入被代理類實(shí)現(xiàn)的接口,這樣生成的代理類和被代理類就實(shí)現(xiàn)了相同的接口) //第三個(gè)參數(shù)是invocation handler,用來(lái)處理方法的調(diào)用。這里傳入我們自己實(shí)現(xiàn)的handler IUserService proxyObject = (IUserService) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader(), target.getClass().getInterfaces(), handler); proxyObject.add("張玉龍"); String path = "D:/$Proxy0.class"; byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", HelloworldImpl.class.getInterfaces()); FileOutputStream out = null; try { out = new FileOutputStream(path); out.write(classFile); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } }
運(yùn)行這段代碼,會(huì)在D盤生成一個(gè)名為$Proxy0.class的文件。通過(guò)反編譯工具,得到JDK為我們生成的代理類是這樣的:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://kpdus.tripod.com/jad.html // Decompiler options: packimports(3) fieldsfirst ansi space import com.zhb.jdk.proxy.IUserService; import java.lang.reflect.*; public final class $Proxy0 extends Proxy implements IUserService { private static Method m1; private static Method m2; private static Method m3; private static Method m0; //代理類的構(gòu)造函數(shù),其參數(shù)正是是InvocationHandler實(shí)例, //Proxy.newInstance方法就是通過(guò)通過(guò)這個(gè)構(gòu)造函數(shù)來(lái)創(chuàng)建代理實(shí)例的 public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } // Object類中的三個(gè)方法,equals,toString, hashCode public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } //接口代理方法 public final void add(String s) { try { // invocation handler的 invoke方法在這里被調(diào)用 super.h.invoke(this, m3, new Object[] { s }); return; } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { // 在這里調(diào)用了invoke方法。 return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } // 靜態(tài)代碼塊對(duì)變量進(jìn)行一些初始化工作 static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.zhb.jdk.proxy.IUserService").getMethod("add", new Class[] { Class.forName("java.lang.String") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); } catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
生成了Object類的三個(gè)方法:toString,hashCode,equals。還有我們需要被代理的方法。
JDK代理類的cache clear機(jī)制
大家都知道、在項(xiàng)目中被代理的class越來(lái)越多,所以jdk會(huì)搞一個(gè)cache的方式來(lái)防止相同的代理接口重復(fù)生成class,影響性能不說(shuō),實(shí)現(xiàn)也不是很優(yōu)雅,那么現(xiàn)在就會(huì)有一個(gè)問(wèn)題了,當(dāng)classloader已經(jīng)在內(nèi)存中沒(méi)有依賴的時(shí)候,被代理的proxy class其實(shí)也沒(méi)有什么意義了,這樣就需要清空無(wú)用的cache,java Proxy采用了非常巧妙的“弱引用機(jī)制”,我們來(lái)看下面的代碼
我們還是繼續(xù)看get方法的源碼
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; } } ....... }
其中源碼中有一個(gè)方法expungeStaleEntries、我們進(jìn)去這個(gè)方法一窺究竟
private void expungeStaleEntries() { CacheKey<K> cacheKey; while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) { cacheKey.expungeFrom(map, reverseMap); } }
在看看expungeFrom方法源碼干了些什么
void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map, ConcurrentMap<?, Boolean> reverseMap) { // removing just by key is always safe here because after a CacheKey // is cleared and enqueue-ed it is only equal to itself // (see equals method)... ConcurrentMap<?, ?> valuesMap = map.remove(this); // remove also from reverseMap if needed if (valuesMap != null) { for (Object cacheValue : valuesMap.values()) { reverseMap.remove(cacheValue); } } }
代碼很清晰了,清空被代理的對(duì)象。現(xiàn)在的關(guān)鍵就是refQueue對(duì)象是怎么來(lái)的。我們繼續(xù)找一下跟refQueue相關(guān)的源碼、在get中還有一段代碼是這樣的
Object cacheKey = CacheKey.valueOf(key, refQueue);
private static final class CacheKey<K> extends WeakReference<K> { // a replacement for null keys private static final Object NULL_KEY = new Object(); static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) { return key == null // null key means we can't weakly reference it, // so we use a NULL_KEY singleton as cache key ? NULL_KEY // non-null key requires wrapping with a WeakReference : new CacheKey<>(key, refQueue); } private final int hash; private CacheKey(K key, ReferenceQueue<K> refQueue) { super(key, refQueue); this.hash = System.identityHashCode(key); // compare by identity } ..... }
這樣看就非常清晰了、原來(lái)是CacheKey繼承了WeakReference弱引用機(jī)制,當(dāng)弱引用依賴的key沒(méi)有引用的時(shí)候,當(dāng)前失效的對(duì)象就會(huì)進(jìn)入ReferenceQueue中來(lái)實(shí)現(xiàn)清空cache的功能、這種實(shí)現(xiàn)思路和ThreadLocal的實(shí)現(xiàn)原理是一樣的、大家有興趣可以去閱讀以下相關(guān)源碼。
三:手把手寫基于接口的java代理
上面我們分析了jdk動(dòng)態(tài)代理源碼、那我們是不是可以自己用自己的方式去寫一個(gè)屬于自己的jdk代理呢,答案是可以的
首先我們寫一個(gè)基類,當(dāng)然我并沒(méi)有在基類里面寫什么東西,只是模擬java中的proxy類而已,當(dāng)然我們也可以豐富的去拓展一下這個(gè)類的方法,來(lái)實(shí)現(xiàn)更多的功能,讀者可以通過(guò)讀完這篇文章之后自己去考慮一下如何來(lái)拓展。
1 package meituan.zylproxy.handlder;2 public class ZylProxy {3 public ZylProxy(){4 }5 }
代理的核心接口,我們?nèi)プ龃淼臅r(shí)候一定是通過(guò)反射去調(diào)用的,不管jdk也好還是cglib也好,永遠(yuǎn)也無(wú)法脫離反射,我們照貓畫虎,自己寫一個(gè)代理接口核心類,這并不是什么難題,看起來(lái)和jdk的核心類接口也沒(méi)有什么區(qū)別。
1 package meituan.zylproxy.handlder;2 3 import java.lang.reflect.Method;4 5 public interface ZYLInvocationHandler {6 7 public Object invoke(Object proxy, Method method, Object[] args)8 throws Exception;9 }
說(shuō)明一下 第一個(gè)參數(shù)proxy是代表代理類,而不是用戶自己寫的原生類實(shí)現(xiàn)。參數(shù)Method是接口的方法,args是運(yùn)行時(shí)參數(shù)列表,在運(yùn)行時(shí)傳遞過(guò)來(lái)的實(shí)際上就是實(shí)現(xiàn)類的參數(shù),好了,下面讓我們?nèi)ド钊牒诵摹?/p>
我們自定義兩個(gè)接口和接口的實(shí)現(xiàn)Idto,Idto2,和Dtoimpl如下:
1 package meituan.zylproxy.test.i;2 3 public interface Idto {4 5 public void add();6 7 public String get();8 9 }
package meituan.zylproxy.test.i;public interface Idto2 {public void adda(); public String geta(); }
package meituan.zylproxy.test.i.impl;import meituan.zylproxy.test.i.Idto;import meituan.zylproxy.test.i.Idto2;public class DtoImpl implements Idto,Idto2{ @Overridepublic void add() { System.out.println("add"); } @Overridepublic String get() { System.out.println("get");return "return get"; } @Overridepublic void adda() { System.out.println("adda"); } @Overridepublic String geta() { System.out.println("geta");return "return geta"; } }
這是幾個(gè)再簡(jiǎn)單不過(guò)的接口和實(shí)現(xiàn)類了,也沒(méi)有什么可說(shuō)的。接下來(lái)我們想對(duì)接口進(jìn)行代理,無(wú)非是我們動(dòng)態(tài)將接口進(jìn)行實(shí)現(xiàn),從而達(dá)到對(duì)使用者進(jìn)行自定義handle接口暴露而已,下面看一下我們需要生成一個(gè)什么樣的代理類。
import java.lang.reflect.Method;import meituan.zylproxy.handlder.ZylProxy;import meituan.zylproxy.handlder.ZYLInvocationHandler;import meituan.zylproxy.test.i.Idto;import meituan.zylproxy.test.i.Idto2;public class IdtoPorxy extends ZylProxy implements Idto, Idto2 {public ZYLInvocationHandler zYLInvocationHandler;public static Method add1;public static Method get2;public static Method adda3;public static Method geta4;static {try { add1 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "add", new Class[0] ); get2 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "get", new Class[0] ); adda3 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "adda", new Class[0] ); geta4 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "geta", new Class[0] ); } catch (Exception e) { } }public IdtoPorxy(ZYLInvocationHandler zYLInvocationHandler) {this.zYLInvocationHandler = zYLInvocationHandler; }public void add() { Object[] o = {};try {this.zYLInvocationHandler.invoke ( this, add1, o );return; } catch (Throwable e) { e.printStackTrace (); } }public java.lang.String get() { Object[] o = {};try {return (java.lang.String) this.zYLInvocationHandler.invoke ( this, get2, o ); } catch (Exception e) { e.printStackTrace (); }return null; }public void adda() { Object[] o = {};try {this.zYLInvocationHandler.invoke ( this, adda3, o );return; } catch (Throwable e) { e.printStackTrace (); } }public java.lang.String geta() { Object[] o = {};try {return (java.lang.String) this.zYLInvocationHandler.invoke ( this, geta4, o ); } catch (Exception e) { e.printStackTrace (); }return null; } }
這個(gè)類不是由用戶寫的,而是我們動(dòng)態(tài)生成的,對(duì)于jdk來(lái)說(shuō)是生成了字節(jié)碼,對(duì)cglib來(lái)說(shuō)是通過(guò)字節(jié)碼增強(qiáng),其實(shí)實(shí)現(xiàn)的方式有多種,后面為了更方便大家理解我用字符串的形式來(lái)動(dòng)態(tài)生成這么一個(gè)"家伙",先看看這個(gè)類干了些什么吧,也很簡(jiǎn)單。
public class IdtoPorxy extends ZylProxy implements Idto, Idto2
首先是繼承了剛才我們所說(shuō)的ZylProxy,留著今后拓展,可以參照java的Proxy,然后并且動(dòng)態(tài)的實(shí)現(xiàn)了這兩個(gè)接口。很簡(jiǎn)單
public ZYLInvocationHandler zYLInvocationHandler;
public IdtoPorxy(ZYLInvocationHandler zYLInvocationHandler) { this.zYLInvocationHandler = zYLInvocationHandler; }
這個(gè)是通過(guò)構(gòu)造函數(shù)傳進(jìn)來(lái)一個(gè)handler對(duì)象,對(duì)實(shí)現(xiàn)類的操作都靠它了。
public static Method add1;public static Method get2;public static Method adda3;public static Method geta4;static {try { add1 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "add", new Class[0] ); get2 = Class.forName ( "meituan.zylproxy.test.i.Idto" ).getMethod ( "get", new Class[0] ); adda3 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "adda", new Class[0] ); geta4 = Class.forName ( "meituan.zylproxy.test.i.Idto2" ).getMethod ( "geta", new Class[0] ); } catch (Exception e) { } }
枚舉出來(lái)所有的接口的方法,通過(guò)class.forname來(lái)獲取到Method元數(shù)據(jù)。備用
public void add() { Object[] o = {};try {this.zYLInvocationHandler.invoke ( this, add1, o );return; } catch (Throwable e) { e.printStackTrace (); } }public java.lang.String get() { Object[] o = {};try {return (java.lang.String) this.zYLInvocationHandler.invoke ( this, get2, o ); } catch (Exception e) { e.printStackTrace (); }return null; }public void adda() { Object[] o = {};try {this.zYLInvocationHandler.invoke ( this, adda3, o );return; } catch (Throwable e) { e.printStackTrace (); } }public java.lang.String geta() { Object[] o = {};try {return (java.lang.String) this.zYLInvocationHandler.invoke ( this, geta4, o ); } catch (Exception e) { e.printStackTrace (); }return null; }
上面是要枚舉出來(lái)所有的方法的實(shí)現(xiàn),很簡(jiǎn)單都一個(gè)模樣,把實(shí)現(xiàn)交給handler去做就可以了。至于怎么實(shí)現(xiàn)靠handler,我們動(dòng)態(tài)生成的這個(gè)類只負(fù)責(zé)委托,不做任何事情??吹竭@里大家一定急不可待的想知道這個(gè)類怎么生成的了,我把我寫的源碼給大家貼出來(lái)看一下。
package meituan.zylproxy.util;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import meituan.zylproxy.test.i.Idto;import meituan.zylproxy.test.i.Idto2;public class ClassUtil {public static String mackProxyClass(Class<?> c) throws Exception{if(!c.isInterface()){throw new Exception("代理的類必須是接口"); } StringBuffer importsp = new StringBuffer(); importsp.append("import java.lang.reflect.Method;\n"); importsp.append("import meituan.zylproxy.handlder.ZylProxy;\n"); importsp.append("import meituan.zylproxy.handlder.ZYLInvocationHandler;\n"); importsp.append("import " +c.getName() + ";\n"); StringBuilder publicStaticMethods = new StringBuilder(); //public static Method add;StringBuilder publicMethods = new StringBuilder(); publicMethods.append("public ZYLInvocationHandler zYLInvocationHandler;\n"); StringBuilder constructorsp = new StringBuilder(); String interFaceName = c.getName().substring(c.getName().lastIndexOf(".")+1); constructorsp.append("public ").append("" + interFaceName + "Porxy"). append("(ZYLInvocationHandler zYLInvocationHandler) { " + "this.zYLInvocationHandler = zYLInvocationHandler;" + "}"); publicStaticMethods.append(" static { try { "); StringBuilder classsp = new StringBuilder(); classsp.append("public class").append(" " + interFaceName + "Porxy").append(" extends ZylProxy implements ").append(interFaceName).append("{"); StringBuilder allMethods = new StringBuilder(); Method[] Methods = c.getMethods(); int curr=0;for (Method m_:Methods) { curr++; publicMethods.append("public static Method ").append(m_.getName() + String.valueOf(curr)).append(";\n"); publicStaticMethods.append("").append(m_.getName() + String.valueOf(curr)).append("="); publicStaticMethods.append("Class.forName(\"" + c.getName() + "\")" + ".getMethod(\""+ m_.getName() +"\", "); StringBuilder sp =new StringBuilder(); StringBuilder spArgs = new StringBuilder(); spArgs.append("Object[] o ={");//public sp.append(Modifier.toString(m_.getModifiers()).replace("abstract", "")).append(" ");//void | java.lang.Stringsp.append(m_.getReturnType().getName()).append(" ");//add()|get()sp.append(m_.getName().concat("(")); StringBuilder methodCLass = new StringBuilder(); if(m_.getParameterTypes().length>0){ Class<?>[] claszz = m_.getParameterTypes();int methodOffset = 0; methodCLass.append("new Class[] { ");for (Class<?> c_ : claszz) { String paramStr = "obj" + String.valueOf(++methodOffset); spArgs.append(paramStr.concat(",")); sp.append(c_.getName().toString().concat(" ").concat(paramStr)).append(","); methodCLass.append("Class.forName(\"" + c_.getName()).append("\"),"); } sp = new StringBuilder(sp.substring(0, sp.length()-1)); spArgs = new StringBuilder(spArgs.substring(0, spArgs.length()-1)); methodCLass = new StringBuilder(methodCLass.substring(0, methodCLass.length()-1)); } if(methodCLass.length()>0){ methodCLass.append("}"); } else{ methodCLass.append("new Class[0]"); } sp.append("){\n"); spArgs.append("}"); sp.append(spArgs+";\n"); if(sp.toString().contains("void")){ sp.append("try {\n this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);\n return;\n"); sp.append("} catch (Throwable e) {e.printStackTrace();}}"); } else{ sp.append("try {return " + "(" + m_.getReturnType().getName()+ ")" + "this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);\n"); sp.append("} catch (Exception e) {e.printStackTrace();} return null;"); } publicStaticMethods.append(methodCLass).append(");\n"); allMethods.append(sp); } publicStaticMethods.append("} catch(Exception e){}}"); classsp.append(publicMethods) .append(publicStaticMethods) .append(constructorsp).append(allMethods).append("}"); classsp.append("}"); importsp.append(classsp);return importsp.toString(); } public static String mackMultiProxyClass(Class<?>[] cs) throws Exception{ StringBuffer importsp = new StringBuffer(); importsp.append("import java.lang.reflect.Method;\n"); importsp.append("import meituan.zylproxy.handlder.ZylProxy;\n"); importsp.append("import meituan.zylproxy.handlder.ZYLInvocationHandler;\n"); StringBuilder publicStaticMethods = new StringBuilder(); publicStaticMethods.append(" static { try { "); //public static Method add;StringBuilder publicMethods = new StringBuilder(); publicMethods.append("public ZYLInvocationHandler zYLInvocationHandler;\n"); int curr=0; StringBuilder constructorsp = new StringBuilder(); String interFaceName = cs[0].getName().substring(cs[0].getName().lastIndexOf(".")+1); constructorsp.append("public ").append("" + interFaceName + "Porxy"). append("(ZYLInvocationHandler zYLInvocationHandler) { " + "this.zYLInvocationHandler = zYLInvocationHandler;" + "}"); StringBuilder allMethods = new StringBuilder(); StringBuilder classsp = new StringBuilder(); classsp.append("public class").append(" " + interFaceName + "Porxy").append(" extends ZylProxy implements "); for (Class<?> c:cs) {if(!c.isInterface()){throw new Exception("代理的類必須是接口"); } classsp.append(c.getName().substring(c.getName().lastIndexOf(".")+1)).append(","); importsp.append("import " +c.getName() + ";\n"); Method[] Methods = c.getMethods(); for (Method m_:Methods) { curr++; publicMethods.append("public static Method ").append(m_.getName() + String.valueOf(curr)).append(";\n"); publicStaticMethods.append("").append(m_.getName() + String.valueOf(curr)).append("="); publicStaticMethods.append("Class.forName(\"" + c.getName() + "\")" + ".getMethod(\""+ m_.getName() +"\", "); StringBuilder sp =new StringBuilder(); StringBuilder spArgs = new StringBuilder(); spArgs.append("Object[] o ={");//public sp.append(Modifier.toString(m_.getModifiers()).replace("abstract", "")).append(" ");//void | java.lang.Stringsp.append(m_.getReturnType().getName()).append(" ");//add()|get()sp.append(m_.getName().concat("(")); StringBuilder methodCLass = new StringBuilder(); if(m_.getParameterTypes().length>0){ Class<?>[] claszz = m_.getParameterTypes();int methodOffset = 0; methodCLass.append("new Class[] { ");for (Class<?> c_ : claszz) { String paramStr = "obj" + String.valueOf(++methodOffset); spArgs.append(paramStr.concat(",")); sp.append(c_.getName().toString().concat(" ").concat(paramStr)).append(","); methodCLass.append("Class.forName(\"" + c_.getName()).append("\"),"); } sp = new StringBuilder(sp.substring(0, sp.length()-1)); spArgs = new StringBuilder(spArgs.substring(0, spArgs.length()-1)); methodCLass = new StringBuilder(methodCLass.substring(0, methodCLass.length()-1)); } if(methodCLass.length()>0){ methodCLass.append("}"); } else{ methodCLass.append("new Class[0]"); } sp.append("){\n"); spArgs.append("}"); sp.append(spArgs+";\n"); if(sp.toString().contains("void")){ sp.append("try {\n this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);\n return;\n"); sp.append("} catch (Throwable e) {e.printStackTrace();}}"); } else{ sp.append("try {return " + "(" + m_.getReturnType().getName()+ ")" + "this.zYLInvocationHandler.invoke(this,").append(m_.getName() + String.valueOf(curr)).append(",").append("o);\n"); sp.append("} catch (Exception e) {e.printStackTrace();} return null;}"); } publicStaticMethods.append(methodCLass).append(");\n"); allMethods.append(sp); } } classsp = new StringBuilder(classsp.substring(0, classsp.length()-1)).append("{"); publicStaticMethods.append("} catch(Exception e){}}"); classsp.append(publicMethods) .append(publicStaticMethods) .append(constructorsp).append(allMethods).append(""); classsp.append("}"); importsp.append(classsp);return importsp.toString(); } public static void main(String[] args) throws Exception { System.out.println(mackMultiProxyClass(new Class<?>[]{Idto.class})); } }
看起來(lái)很復(fù)雜,仔細(xì)看一下就看了那么幾個(gè)事情,把一個(gè)接口class或者多個(gè)接口class變成純字符串的過(guò)程,一共兩個(gè)方法,一個(gè)是單接口的實(shí)現(xiàn),很早之前寫的,第二個(gè)方法是多接口的實(shí)現(xiàn)支持多接口,只需要傳一個(gè)class對(duì)象就會(huì)生成代理類的字符串,這里僅僅是字符串,需要編譯成class使用。那么如何編譯成class呢。通過(guò)java中的工具類 JavaCompiler很簡(jiǎn)單的就可以生成了。我們來(lái)看兩個(gè)工具類實(shí)現(xiàn)
package meituan.zylproxy;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FilterOutputStream;import java.io.IOException;import java.io.OutputStream;import java.io.Reader;import java.io.StringReader;import java.net.URI;import java.nio.CharBuffer;import java.nio.file.WatchEvent.Kind;import java.util.HashMap;import java.util.Map;import javax.tools.FileObject;import javax.tools.ForwardingJavaFileManager;import javax.tools.JavaFileManager;import javax.tools.JavaFileObject;import javax.tools.SimpleJavaFileObject; @SuppressWarnings("unchecked")final class MemoryJavaFileManager extends ForwardingJavaFileManager {private final static String EXT = ".java";private Map<String, byte[]> classBytes;public MemoryJavaFileManager(JavaFileManager fileManager) {super(fileManager); classBytes = new HashMap<String, byte[]>(); }public Map<String, byte[]> getClassBytes() {return classBytes; }public void close() throws IOException { classBytes = new HashMap<String, byte[]>(); }public void flush() throws IOException { } private static class StringInputBuffer extends SimpleJavaFileObject {final String code; StringInputBuffer(String name, String code) {super(toURI(name), Kind.SOURCE);this.code = code; }public CharBuffer getCharContent(boolean ignoreEncodingErrors) {return CharBuffer.wrap(code); }public Reader openReader() {return new StringReader(code); } } private class ClassOutputBuffer extends SimpleJavaFileObject {private String name; ClassOutputBuffer(String name) {super(toURI(name), Kind.CLASS);this.name = name; }public OutputStream openOutputStream() {return new FilterOutputStream(new ByteArrayOutputStream()) {public void close() throws IOException { out.close(); ByteArrayOutputStream bos = (ByteArrayOutputStream) out; classBytes.put(name, bos.toByteArray()); } }; } }public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {if (kind == JavaFileObject.Kind.CLASS) {return new ClassOutputBuffer(className); } else {return super.getJavaFileForOutput(location, className, kind, sibling); } }static JavaFileObject makeStringSource(String name, String code) {return new StringInputBuffer(name, code); }static URI toURI(String name) { File file = new File(name);if (file.exists()) {return file.toURI(); } else {try {final StringBuilder newUri = new StringBuilder(); newUri.append("mfm:///"); newUri.append(name.replace('.', '/'));if (name.endsWith(EXT)) newUri.replace(newUri.length() - EXT.length(), newUri.length(), EXT);return URI.create(newUri.toString()); } catch (Exception exp) {return URI.create("mfm:///com/sun/script/java/java_source"); } } } }
package meituan.zylproxy;import java.io.IOException;import java.net.URL;import java.net.URLClassLoader;import java.util.Arrays;import java.util.HashMap;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;import javax.tools.JavaCompiler;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;public class DynamicLoader {public static Map<String, byte[]> compile(String javaSrc) { Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)"); Matcher matcher = pattern.matcher(javaSrc);if (matcher.find())return compile(matcher.group(1) + ".java", javaSrc);return null; } public static Map<String, byte[]> compile(String javaName, String javaSrc) { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager stdManager = compiler.getStandardFileManager(null, null, null);try (MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager)) { JavaFileObject javaFileObject = manager.makeStringSource(javaName, javaSrc); JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));if (task.call())return manager.getClassBytes(); } catch (IOException e) { e.printStackTrace(); }return null; }public static class MemoryClassLoader extends URLClassLoader { Map<String, byte[]> classBytes = new HashMap<String, byte[]>();public MemoryClassLoader(Map<String, byte[]> classBytes) {super(new URL[0], MemoryClassLoader.class.getClassLoader());this.classBytes.putAll(classBytes); } @Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] buf = classBytes.get(name);if (buf == null) {return super.findClass(name); } classBytes.remove(name);return defineClass(name, buf, 0, buf.length); } } }
通過(guò)DynamicLoader的compile方法可以把純字符串的str轉(zhuǎn)成byte[]數(shù)組,有了byte[]數(shù)組就可以很方便的獲取到class對(duì)象了,自定義一個(gè)MemoryClassLoader通過(guò)defineClass方法來(lái)獲取到class對(duì)象。這樣基本所有的事情都做完了。下面我們寫一個(gè)工廠類來(lái)獲取代理類。
package meituan.zylproxy.util;import java.util.Map;import meituan.zylproxy.DynamicLoader;import meituan.zylproxy.handlder.ZYLInvocationHandler;public class PorxyFactory { //單interface的時(shí)候用public static Object newProxyInstance(Class<?> c,ZYLInvocationHandler h) throws Exception{ String classStr = ClassUtil.mackProxyClass(c); Map<String, byte[]> m = DynamicLoader.compile(classStr); DynamicLoader.MemoryClassLoader classLoader = new DynamicLoader.MemoryClassLoader(m); Class<?> proxy =classLoader.loadClass(m.keySet().toArray(new String[0])[0]);return proxy.getConstructor(ZYLInvocationHandler.class).newInstance(h); }//多interface的時(shí)候用public static Object newProxyInstancewWithMultiClass(Class<?>[] c,ZYLInvocationHandler h) throws Exception{ String classStr = ClassUtil.mackMultiProxyClass(c); System.out.println (classStr); Map<String, byte[]> m = DynamicLoader.compile(classStr); DynamicLoader.MemoryClassLoader classLoader = new DynamicLoader.MemoryClassLoader(m); Class<?> proxy =classLoader.loadClass(m.keySet().toArray(new String[0])[0]);return proxy.getConstructor(ZYLInvocationHandler.class).newInstance(h); } }
最后一步我們測(cè)試一下結(jié)果吧,寫一個(gè)測(cè)試類
package meituan.zylproxy.test;import meituan.zylproxy.handlder.Hander;import meituan.zylproxy.test.i.Idto;import meituan.zylproxy.test.i.impl.DtoImpl;import meituan.zylproxy.util.PorxyFactory;public class ZylPorxyTest { public static void main(String[] args) throws Exception { Idto d = (Idto) PorxyFactory.newProxyInstancewWithMultiClass(DtoImpl.class.getInterfaces(), new Hander(new DtoImpl())); d.add(); } }
很簡(jiǎn)單,第一個(gè)參數(shù)是所有的接口,第二個(gè)是handler實(shí)現(xiàn)。最后我們看看結(jié)果。
“如何寫java代理”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。