一、概述 本篇介紹自適應擴展,方法getAdaptiveExtension()的實現。ExtensionLoader類本身很多功能也使用到了自適應擴展。包括ExtensionFactory擴展。 通俗的講,自適應擴展要實現的邏輯是:調用擴展點的方法時,自動判斷要調用那個擴展點實現類的方法。我們知道, ...
一、概述
本篇介紹自適應擴展,方法getAdaptiveExtension()的實現。ExtensionLoader類本身很多功能也使用到了自適應擴展。包括ExtensionFactory擴展。
通俗的講,自適應擴展要實現的邏輯是:調用擴展點的方法時,自動判斷要調用那個擴展點實現類的方法。我們知道,一個擴展點通常有多個實現類,在配置文本文件中分多行配置,在前面的分析中,我們知道通過getExtension(String name)方法,返回的是指定key的擴展點,而自適應擴展點方法getAdaptiveExtension()在調用前,不確認返回那個擴展點。而是在方法調用的時候,根據方法入參,進行確定,具體是調用那個實現類。
自適應擴展基於@Adaptive註解,可以修飾類,也可以修飾方法。修飾類的時候,邏輯比較簡單,不會動態生成代碼邏輯,使用的場景也比較少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修飾方法的時候,會動態生成一個新類,新類包括擴展點的所有方法,調用getAdaptiveExtension()返回的就是新類對象。
二、詳細介紹
前面我們說過,@Adaptive可以修飾類,也可以修飾方法。我們先看下修飾類的場景。
通過一個具體的實現類來看下,這裡我們分析AdaptiveCompiler類的實現:
1 @Adaptive 2 public class AdaptiveCompiler implements Compiler { 3 4 private static volatile String DEFAULT_COMPILER; 5 6 public static void setDefaultCompiler(String compiler) { 7 DEFAULT_COMPILER = compiler; 8 } 9 10 @Override 11 public Class<?> compile(String code, ClassLoader classLoader) { 12 Compiler compiler; 13 ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); 14 String name = DEFAULT_COMPILER; // copy reference 15 if (name != null && name.length() > 0) { 16 compiler = loader.getExtension(name); 17 } else { 18 compiler = loader.getDefaultExtension(); 19 } 20 return compiler.compile(code, classLoader); 21 } 22 23 }
此類,只有一個對外提供的方法compile(String code, ClassLoader classLoader),我們來看方法的實現。
首先溝通ExtensionLoader.getExtensionLoader(Compiler.class),獲取Compiler對應的ExtensionLoader對象,然後判斷構造器中初始化的DEFAULT_COMPILER 變數是否有值。如果存在就通過loader.getExtension(name)方法獲得擴展點實現。如果DEFAULT_COMPILER 為空,則調用loader.getDefaultExtension()方法,返回預設實現。獲取compiler擴展點實現對象後,調用對應的compile方法。
由此,我們可以看到,@Adaptive修飾的類,在調用具體方法的時候,是根據一定的條件進行判斷,確認具體調用的實現類對象。
我們再說下@Adaptive修飾方法的場景。
擴展點實現類的方法如果被@Adaptive修飾,在調用getAdaptiveExtension()方法時候,程式會自動生成一個新類,新類是一個名為擴展點介面名+$Adaptive,實現了擴展點介面的類。新類中的方法,主要分為兩類,一是有@Adaptive註解的方法,一個是沒有@Adaptive註解的方法。
有@Adaptive註解的方法,方法內部會判斷方法入參是否有URL(此處是dubbo內的URL),或是方法入參對象是否可以get到URL。如果都不能獲取到URL,直接throw 出Exception。如果能獲取到URL,則從URL對象中獲取需要調用的實現類對應的配置文本文件的key,根據什麼參數從URL中獲取呢?首先是從@Adaptive的value獲取(此value是一個字元串數組),如果@Adaptive為空,則根據類名進行轉換,得出從URL獲取key的參數名,轉換規則是根據駝峰規則,遇到大寫字元添加”.“,如 AdaptiveFactory 為:adaptive.factory。獲得參數後,再通過getExtension(..)方法,獲得需要調用的擴展點實現類對象。
到這裡,我們基本介紹了自適應擴展點的實現邏輯,但是有一點沒有說到,就是不管@Adaptive修飾類還是修飾方法,自適應擴展點的返回邏輯,這點是要結合代碼進行說明,接下來就開啟我們的源代碼分析。
三、源代碼分析
我們從getAdaptiveExtension()方法開始
1 public T getAdaptiveExtension() { 2 // 從緩存中獲取自定義拓展 3 Object instance = cachedAdaptiveInstance.get(); 4 if (instance == null) { 5 if (createAdaptiveInstanceError == null) { 6 synchronized (cachedAdaptiveInstance) { 7 instance = cachedAdaptiveInstance.get(); 8 if (instance == null) { 9 try { 10 instance = createAdaptiveExtension(); 11 cachedAdaptiveInstance.set(instance); 12 } catch (Throwable t) { 13 createAdaptiveInstanceError = t; 14 throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); 15 } 16 } 17 } 18 } else { 19 throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); 20 } 21 } 22 23 return (T) instance; 24 }
這個方法的邏輯很簡單,主要包括
1、從緩存對象cachedAdaptiveInstance獲取自適應擴展點實例
2、緩存有直接返回,沒有進行方法createAdaptiveExtension()調用
3、根據方法返回的實例,設置到到緩存里,併進行返回
所以我們接著分析createAdaptiveExtension方法
1 private T createAdaptiveExtension() { 2 try { 3 // injectExtension 為@Adaptive註解的類 可能存在的IOC服務 4 // @Adaptive註解方法 自動生成的代理類不存在IOC可能 5 T instance = (T) getAdaptiveExtensionClass().newInstance(); 6 return injectExtension(instance); 7 } catch (Exception e) { 8 throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); 9 } 10 }
可以看到,方法內部是通過getAdaptiveExtensionClass() 獲取到Class實例,再反射實例化,獲取到實例對象。
我們接著往下看getAdaptiveExtensionClass()方法
1 private Class<?> getAdaptiveExtensionClass() { 2 // 通過SPI獲取所有的擴展類,賦值相關的成員變數 3 getExtensionClasses(); 4 // 如果有@Adaptive修飾的類,cachedAdaptiveClass不為空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 沒有@Adaptive修飾的類時,根據@Adaptive修飾方法 創建自適應擴展類 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
首先執行的是getExtensionClasses()方法,之後判斷cachedAdaptiveClass 是否為空,不為空就直接返回了。這個cachedAdaptiveClass 變數,其實就是有@Adaptive修飾的擴展點實現。也就是說,如果在擴展點的實現類中,存在@Adaptive修飾的類,就直接返回這個類了。
那麼cachedAdaptiveClass 在是哪裡賦值的呢?我們需要再看getExtensionClasses()方法。getExtensionClasses這個方法在前面兩篇文章中已經都有介紹。在預設擴展點的實現裡面,cachedDefaultName變數的賦值就是在這個方法里進行的。cachedAdaptiveClass 的賦值的方法調用鏈我們這裡直接給出來
1 getExtensionClasses()-->loadExtensionClasses()-->loadDirectory()-->loadResource()-->loadClass()
隱藏的比較深,第5個方法才對cachedDefaultName進行了賦值。
我們一步一步來分析,先看getExtensionClasses()
1 private Map<String, Class<?>> getExtensionClasses() { 2 Map<String, Class<?>> classes = cachedClasses.get(); 3 if (classes == null) { 4 synchronized (cachedClasses) { 5 classes = cachedClasses.get(); 6 if (classes == null) { 7 classes = loadExtensionClasses(); 8 cachedClasses.set(classes); 9 } 10 } 11 } 12 return classes; 13 }
這個方法很簡單,我們接著看loadExtensionClasses()
1 private Map<String, Class<?>> loadExtensionClasses() { 2 // 獲取註解 SPI的介面 3 // type為傳入的擴展介面,必須有@SPI註解 4 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 5 // 獲取預設擴展實現value,如果存在,賦值給cachedDefaultName 6 if (defaultAnnotation != null) { 7 String value = defaultAnnotation.value(); 8 if ((value = value.trim()).length() > 0) { 9 // @SPI value 只能是一個,不能為逗號分割的多個 10 // @SPI value為預設的擴展實現 11 String[] names = NAME_SEPARATOR.split(value); 12 if (names.length > 1) { 13 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); 14 } 15 if (names.length == 1) 16 cachedDefaultName = names[0]; 17 } 18 } 19 // 載入三個目錄配置的擴展類 20 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); 21 // META-INF/dubbo/internal 22 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 23 // META-INF/dubbo 24 loadDirectory(extensionClasses, DUBBO_DIRECTORY); 25 // META-INF/services/ 26 loadDirectory(extensionClasses, SERVICES_DIRECTORY); 27 return extensionClasses; 28 }
很熟悉吧,這個方法我們在第一篇文章中已經有介紹了,我們再接著往下看loadDirectory方法
1 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) { 2 // 擴展配置文件完整文件路徑+文件名 3 String fileName = dir + type.getName(); 4 try { 5 6 Enumeration<java.net.URL> urls; 7 // 獲取類載入器 8 ClassLoader classLoader = findClassLoader(); 9 if (classLoader != null) { 10 urls = classLoader.getResources(fileName); 11 } else { 12 urls = ClassLoader.getSystemResources(fileName); 13 } 14 if (urls != null) { 15 while (urls.hasMoreElements()) { 16 java.net.URL resourceURL = urls.nextElement(); 17 // 載入 18 loadResource(extensionClasses, classLoader, resourceURL); 19 } 20 } 21 } catch (Throwable t) { 22 logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); 23 } 24 }
這個方法我們也很分析過了,再往下看loadResource()方法
1 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { 2 try { 3 BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); 4 try { 5 String line; 6 while ((line = reader.readLine()) != null) { 7 // 字元#是註釋開始標誌,只取#前面的字元 8 final int ci = line.indexOf('#'); 9 if (ci >= 0) 10 line = line.substring(0, ci); 11 line = line.trim(); 12 if (line.length() > 0) { 13 try { 14 String name = null; 15 int i = line.indexOf('='); 16 if (i > 0) { 17 // 解析出 name 和 實現類 18 name = line.substring(0, i).trim(); 19 line = line.substring(i + 1).trim(); 20 } 21 if (line.length() > 0) { 22 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); 23 } 24 } catch (Throwable t) { 25 IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); 26 exceptions.put(line, e); 27 } 28 } 29 } 30 } finally { 31 reader.close(); 32 } 33 } catch (Throwable t) { 34 logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); 35 } 36 }
這裡是解析配置文本文件的內容,通過反射獲得Class,再調用loadClass(),我們接著往下
1 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { 2 // type是否為clazz的超類,clazz是否實現了type介面 3 // 此處clazz 是擴展實現類的Class 4 if (!type.isAssignableFrom(clazz)) { 5 throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); 6 } 7 // clazz是否註解了 Adaptive 自適應擴展 8 // 不允許多個類註解Adaptive 9 // 註解adaptive的實現類,賦值給cachedAdaptiveClass 10 if (clazz.isAnnotationPresent(Adaptive.class)) { 11 if (cachedAdaptiveClass == null) { 12 cachedAdaptiveClass = clazz; 13 // 不允許多個實現類都註解@Adaptive 14 } else if (!cachedAdaptiveClass.equals(clazz)) { 15 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); 16 } 17 // 是否為包裝類,判斷擴展類是否提供了參數是擴展點的構造函數 18 } else if (isWrapperClass(clazz)) { 19 Set<Class<?>> wrappers = cachedWrapperClasses; 20 if (wrappers == null) { 21 cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); 22 wrappers = cachedWrapperClasses; 23 } 24 wrappers.add(clazz); 25 // 普通擴展類 26 } else { 27 // 檢測 clazz 是否有預設的構造方法,如果沒有,則拋出異常 28 clazz.getConstructor(); 29 // 此處name為 SPI配置中的key 30 // @SPI配置中key可以為空,此時key為擴展類的類名(getSimpleName())小寫 31 if (name == null || name.length() == 0) { 32 // 相容舊版本 33 name = findAnnotationName(clazz); 34 if (name.length() == 0) { 35 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); 36 } 37 } 38 // 逗號分割 39 String[] names = NAME_SEPARATOR.split(name); 40 if (names != null && names.length > 0) { 41 // 獲取Activate註解 42 Activate activate = clazz.getAnnotation(Activate.class); 43 if (activate != null) { 44 cachedActivates.put(names[0], activate); 45 } 46 for (String n : names) { 47 if (!cachedNames.containsKey(clazz)) { 48 cachedNames.put(clazz, n); 49 } 50 // name不能重覆 51 Class<?> c = extensionClasses.get(n); 52 if (c == null) { 53 extensionClasses.put(n, clazz); 54 } else if (c != clazz) { 55 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); 56 } 57 } 58 } 59 } 60 }
我們在第10行,終於看到了Adaptive註解判斷。
如果擴展點實現類存在@Adaptive註解,Class對象賦值給cachedAdaptiveClass,並且在第14行判斷是否存在多個類都是@Adaptive註解,如果同一個擴展點的多個實現類都有@Adaptive註解,則拋出異常。
到這裡,我們看到了擴展點自適應擴展點的類級別註解的調用及返回邏輯。其實前面也說過了,@Adaptive修飾類的場景並不多,也不是重點,重點是@Adaptive修飾方法的時候。
我們返回到getAdaptiveExtensionClass()方法,為了清晰,我們再看看這個方法的代碼
1 private Class<?> getAdaptiveExtensionClass() { 2 // 通過SPI獲取所有的擴展類,賦值相關的成員變數 3 getExtensionClasses(); 4 // 如果有@Adaptive修飾的類,cachedAdaptiveClass不為空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 沒有@Adaptive修飾的類時,根據@Adaptive修飾方法 創建自適應擴展類 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
通過前面的分析,如果@Adaptive沒有修飾類,則cachedAdaptiveClass 為空,此時,我們會進入createAdaptiveExtensionClass(),這個方法是實現@Adaptive修飾方法的邏輯實現,也是自適應擴展的重點所在。
我們來看createAdaptiveExtensionClass這個方法
1 private Class<?> createAdaptiveExtensionClass() { 2 // 創建自適應擴展代碼 字元串 3 String code = createAdaptiveExtensionClassCode(); 4 ClassLoader classLoader = findClassLoader(); 5 // 獲取編譯器實現類 6 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 7 // 編譯代碼,獲取自適應擴展類的Class 8 return compiler.compile(code, classLoader); 9 }
這個方法實現的功能主要兩點:
1、通過createAdaptiveExtensionClassCode()獲取創建的新類字元串
2、通過Compiler編譯器,編譯新類字元串,獲得新類的Class對象
所以,我們的重點是分析新類字元串的實現邏輯,這也是自適應擴展的重點。
我們接著看createAdaptiveExtensionClassCode()方法,這個方法有300多行,我們分段來看
1 private String createAdaptiveExtensionClassCode() { 2 StringBuilder codeBuilder = new StringBuilder(); 3 // 通過反射獲取所有方法 4 Method[] methods = type.getMethods(); 5 boolean hasAdaptiveAnnotation = false; 6 // 遍歷方法,判斷至少有一個方法被@Adaptive修飾 7 for (Method m : methods) { 8 if (m.isAnnotationPresent(Adaptive.class)) { 9 hasAdaptiveAnnotation = true; 10 break; 11 } 12 } 13 // no need to generate adaptive class since there's no adaptive method found. 14 // 沒有被@Adaptive修飾的方法,拋出異常 15 if (!hasAdaptiveAnnotation) 16 throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); 17 // 生成package代碼:package+type所在包 18 codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); 19 // 生成import代碼:import+ExtensionLoader許可權定名 20 codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); 21 // 生成類代碼:public class + type簡單名稱+$Adaptive+implements + type許可權定名+{ 22 codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {"); 23 ............... 24 ...暫時省略.... 25 .............. 26 }
方法開始的邏輯很簡單,拿到擴展點type的方法,迴圈方法列表,判斷是否存在一個方法是被@Adaptive註解的,如果存在則繼續,否則拋出異常。這也符合正常的邏輯,如果所有的方法都沒@Adaptive註解,那麼獲取自適應擴展就沒有意義了。
從15行開始進行新類字元串的構造,我們看到了關鍵字”package“、”import“、”class“等,執行到22行處,生成的代碼字元串,我們以擴展點Protocol為例,展示一下:
1 package com.alibaba.dubbo.rpc; 2 import com.alibaba.dubbo.common.extension.ExtensionLoader; 3 public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { 4 // 省略方法代碼 5 }
這個類就是我們生成的新的擴展點實現類,我們可以看到類名以及實現的介面。
我們接著往下分析,前面我們說過,擴展點方法分為兩種,一個是有@Adaptive註解的,一個是無@Adaptive註解的,既然實現了擴展點介面,這兩種方法都要在新類中實現。
我們首先分析沒有@Adaptive註解的方法。
1 private String createAdaptiveExtensionClassCode() { 2 ............... 3 ...暫時省略.... 4 .............. 5 // 生成方法 6 for (Method method : methods) { 7 // 方法返回類型 8 Class<?> rt = method.getReturnType(); 9 // 方法參數數組 10 Class<?>[] pts = method.getParameterTypes(); 11 // 方法異常數組 12 Class<?>[] ets = method.getExceptionTypes(); 13 // 方法的Adaptive註解 14 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); 15 StringBuilder code = new StringBuilder(512); 16 // 沒有@Adaptive註解的方法,生成的方法體內為 throw 異常 17 if (adaptiveAnnotation == null) { 18 // throw new UnsupportedOperationException( 19 // "method " + 方法簽名 + of interface + 全限定介面名 + is not adaptive method!”) 20 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()).append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 21 } else { 22 ............... 23 ...暫時省略.... 24 .............. 25 }
通過for迴圈,進行逐個方法生成。
在第14行,獲取到方法的@Adaptive註解,第17行進行判斷,如果為空,生成的代碼是拋出一個異常,示例如下:
1 throw new UnsupportedOperationException( 2 "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
我們接著分析存在@Adaptive註解的方法,生成代碼的邏輯
1 ...暫時省略.... 2 if (adaptiveAnnotation == null) { 3 // throw new UnsupportedOperationException( 4 // "method " + 方法簽名 + of interface + 全限定介面名 + is not adaptive method!”) 5 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()) 6 .append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 7 } else { 8 // 有@Adaptive註解的方法,參數中必須有URL,或是可以從方法參數中獲取URL 9 int urlTypeIndex = -1; 10 for (int i = 0; i < pts.length; ++i) { 11 if (pts[i].equals(URL.class)) { 12 urlTypeIndex = i; 13 break; 14 } 15 } 16 // found parameter in URL type 17 // 方法中存在URL 18 if (urlTypeIndex != -1) { 19 // Null Point check 20 // 為URL類型參數判斷空代碼,格式如下: 21 // if (arg + urlTypeIndex == null) 22 // throw new IllegalArgumentException("url == null"); 23 String s = String.format( 24 "\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", urlTypeIndex); 25 code.append(s); 26 // 為URL類型參數生成賦值代碼,例如:URL url = arg1; 27 s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 28 code.append(s); 29 } 30 ...暫時省略....
第10行,迴圈的變數pts 為方法參數數組,如果參數數組中有URL類型,數組下標賦值給urlTypeIndex,跳出迴圈。
第18行進行urlTypeIndex 判斷,此時如果不為-1,說明方法參數數組中存在URL類型的參數,生成的代碼首先是非空判斷,接著就是把對應的URL類型參數就行變數賦值。
接著往下看
1 ...暫時省略.... 2 else { 3 String attribMethod = null; 4 // find URL getter method 5 // 遍歷方法的參數類型 6 LBL_PTS: for (int i = 0; i < pts.length; ++i) { 7 // 方法參數類型的方法數組 8 Method[] ms = pts[i].getMethods(); 9 for (Method m : ms) { 10 String name = m.getName(); 11 // 1、方法名以get開頭,或方法名大於3個字元 12 // 2、方法的訪問許可權是public 13 // 3、非靜態方法 14 // 4、方法參數數量為0 15 // 5、方法返回值類型為URL 16 if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) 17 && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 18 && m.getReturnType() == URL.class) { 19 urlTypeIndex = i; 20 attribMethod = name; 21 // 結束for (int i = 0; i < pts.length; ++i)迴圈 22 break LBL_PTS; 23 } 24 } 25 } 26 ...暫時省略....
上面的代碼是參數數組中不存在URL類型參數的情況。如果不存在URL類型的參數,就需要從所有的入參中判斷,參數對象中是否可以通過get方法 獲取到URL對象。如果不可以則拋出異常。
標簽LBL_PTS用於結束最外部的迴圈。我們看到最外邊的迴圈,還是參數數組pts。
第8行是拿到參數數組中單個參數的所有方法,再進行迴圈,判斷是否存在滿足如下條件: 1、方法名以get開頭,或方法名大於3個字元;2、方法的訪問許可權是public; 3、非靜態方法;4、方法參數數量為0; 5、方法返回值類型為URL的方法,如果存在賦值方法名給attribMethod ,跳出最外變迴圈。
我們接著往下看
1 ...暫時省略.... 2 // 如果參數中都不包含可返回的URL的get方法,拋出異常 3 if (attribMethod == null) { 4 throw new IllegalStateException("fail to create adaptive class for interface " + type.getName() 5 + ": not found url parameter or url attribute in parameters of method " 6 + method.getName()); 7