組件通信注解框架實(shí)踐
目錄介紹
- 01.為何需要組件間通信
- 02.實(shí)現(xiàn)同級(jí)組件通信方式
- 03.先看一個(gè)簡(jiǎn)單的案例
- 04.項(xiàng)目組件通信流程
- 05.逆向簡(jiǎn)化注冊(cè)流程
- 06.這個(gè)注解是做什么的
- 07.注解是如何生成代碼
- 08.如何定義注解處理器
- 09.項(xiàng)目庫的設(shè)計(jì)和完善
- 10.封裝該庫有哪些特點(diǎn)
- 11.一些常見的報(bào)錯(cuò)問題
- 12.部分原理分析的說明
01.為何需要組件間通信
- 明確一個(gè)前提:各個(gè)業(yè)務(wù)組件之間不會(huì)是相互隔離而是必然存在一些交互的趴久;
- 業(yè)務(wù)復(fù)用:在Module A需要引用Module B提供的某個(gè)功能瘫怜,比如需要版本更新業(yè)務(wù)邏輯,而我們一般都是使用強(qiáng)引用的Class顯式的調(diào)用勺鸦;
- 業(yè)務(wù)復(fù)用:在Module A需要調(diào)用Module B提供的某個(gè)方法赫冬,例如別的Module調(diào)用用戶模塊退出登錄的方法浓镜;
- 業(yè)務(wù)獲取參數(shù):登陸環(huán)境下,在Module A劲厌,C膛薛,D,E多個(gè)業(yè)務(wù)組件需要拿到Module B登陸注冊(cè)組件中用戶信息id补鼻,name哄啄,info等參數(shù)訪問接口數(shù)據(jù);
- 這幾種調(diào)用形式大家很容易明白风范,正常開發(fā)中大家也是毫不猶豫的調(diào)用咨跌。但是在組件化開發(fā)的時(shí)候卻有很大的問題:
- 由于業(yè)務(wù)組件之間沒有相互依賴,組件Module B的Activity Class在自己的Module中硼婿,那Module A必然引用不到锌半,這樣無法調(diào)用類的功能方法;由此:必然需要一種支持組件化需求的交互方式寇漫,提供平行級(jí)別的組件間調(diào)用函數(shù)通信交互的功能刊殉。
- 項(xiàng)目庫開源地址
02.實(shí)現(xiàn)同級(jí)組件通信方式
- 至于關(guān)于頁面跳轉(zhuǎn)
- 那肯定是首選路由殉摔,比如阿里的ARouter。但是涉及到組件之間業(yè)務(wù)復(fù)用冗澈,業(yè)務(wù)邏輯的交互等等钦勘,就有點(diǎn)難搞了……那該怎么處理比較方便呢?
- 組件業(yè)務(wù)邏輯交互通信
- 比如業(yè)務(wù)組件層劃分
- 組件A亚亲,組件B彻采,組件C,組件D捌归,組件E等等肛响,這些業(yè)務(wù)組件并不是相互依賴,它們之間是相同的層級(jí)惜索!
- 舉一個(gè)業(yè)務(wù)案例
- 比如有個(gè)選擇用戶學(xué)員的彈窗嘹屯,代碼寫到了組件A中瞬哼,這個(gè)時(shí)候組件C和組件D需要復(fù)用組件A中的彈窗,該業(yè)務(wù)邏輯如何處理?
- 比如組件E是我的用戶相關(guān)的業(yè)務(wù)邏輯潭陪,App登陸后缸榄,組件B和組件C需要用到用戶的id去請(qǐng)求接口镜遣,這個(gè)時(shí)候如何獲取組件E中用戶id呢纳寂?
- 該層級(jí)下定義一個(gè)公共通信組件
- 接口通信組件【被各個(gè)業(yè)務(wù)組件依賴】,該相同層級(jí)的其他業(yè)務(wù)組件都需要依賴這個(gè)通信組件圃伶。這個(gè)時(shí)候各個(gè)模塊都可以拿到通信組件的類……
- 比如業(yè)務(wù)組件層劃分
- 需要具備的那些特點(diǎn)
- 使用簡(jiǎn)單方便堤如,避免同級(jí)組件相互依賴。代碼入侵性要低窒朋,支持業(yè)務(wù)交互搀罢,自動(dòng)化等特性。
03.先看一個(gè)簡(jiǎn)單的案例
- 先說一下業(yè)務(wù)場(chǎng)景
- 版本更新業(yè)務(wù)組件(處理更新彈窗侥猩,apk下載榔至,apk的md5校驗(yàn),安裝等邏輯欺劳,還涉及到一些業(yè)務(wù)邏輯洛退,比如更新模式普通或者強(qiáng)更,還有渠道杰标,還有時(shí)間段等)
- 主模塊首頁,我的組件彩匕,設(shè)置中心組件等多個(gè)module組件中都會(huì)用到版本更新功能腔剂,除了主模塊外,其他組件沒有依賴版本更新組件驼仪,那么如何調(diào)用里面的更新彈窗業(yè)務(wù)邏輯呢掸犬?
- 創(chuàng)建一個(gè)接口通信組件
- 如上所示袜漩,各個(gè)同級(jí)的業(yè)務(wù)組件,A湾碎,B宙攻,C,D等都依賴該接口通信組件介褥。那么這樣就會(huì)拿到通信組件的類座掘,為了實(shí)現(xiàn)通信交互∪崽希可以在該接口通信組件中定義接口并暴露抽象更新彈窗方法溢陪,那么在版本更新組件中寫接口實(shí)現(xiàn)類。
- 創(chuàng)建一個(gè)map集合睛廊,存儲(chǔ)實(shí)現(xiàn)類的全路徑形真,然后put到map集合中;這樣可以get拿到實(shí)現(xiàn)類的路徑超全,就可以利用反射創(chuàng)建實(shí)例對(duì)象咆霜。
- 通信組件幾個(gè)主要類
- BusinessTransfer,主要是map集合中g(shù)et獲取和put添加接口類的對(duì)象嘶朱,利用反射機(jī)制創(chuàng)建實(shí)例對(duì)象蛾坯。該類放到通信組件中。
- IUpdateManager见咒,該類是版本更新接口類偿衰,定義更新抽象方法。該類放到通信組件中改览。
- UpdateManagerImpl下翎,該類是IUpdateManager接口實(shí)現(xiàn)類,主要是具體業(yè)務(wù)邏輯的實(shí)現(xiàn)宝当。該類放到具體實(shí)現(xiàn)庫代碼中视事,比如我的組件。
- 主要實(shí)現(xiàn)的代碼如下所示
//接口 public interface IUpdateManager extends Serializable { void checkUpdate(UpdateManagerCallBack updateManagerCallBack); interface UpdateManagerCallBack { void updateCallBack(boolean isNeedUpdate); } } //接口實(shí)現(xiàn)類 public class UpdateManagerImpl implements IUpdateManager { @Override public void checkUpdate(UpdateManagerCallBack updateManagerCallBack) { try { IConfigService configService = DsxxjServiceTransfer.$().getConfigureService(); String data = configService.getConfig(KEY_APP_UPDATE); if (TextUtils.isEmpty(data)) { if (updateManagerCallBack != null) { updateManagerCallBack.updateCallBack(false); } return; } ForceUpdateEntity xPageUpdateEntity = JSON.parseObject(data, ForceUpdateEntity.class); ForceUpdateManager.getInstance().checkForUpdate(xPageUpdateEntity, updateManagerCallBack); } catch (Exception e) { e.printStackTrace(); } } } //如何使用 //在初始化時(shí)注入庆揩,建議放在application中設(shè)置俐东,調(diào)用setImpl其實(shí)就是把路徑字符串put到map集合中 BusinessTransfer businessTransfer = BusinessTransfer.$(); businessTransfer.setImpl(BusinessTransfer.BUSINESS_IMPL_UPDATE_MANAGER, PACKAGE_NAME + ".base.businessimpl.UpdateManagerImpl");
- 那么如何調(diào)用呢?可以在各個(gè)組件中調(diào)用订晌,代碼如下所示……
//版本更新 BusinessTransfer.$().getUpdate().checkUpdate(new IUpdateManager.UpdateManagerCallBack() { @Override public void updateCallBack(boolean isNeedUpdate) { } });
- 反射創(chuàng)建接口的實(shí)現(xiàn)類對(duì)象
String className = implsMap.get(key); try { return (T) Class.forName(className).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); }
- 這種方式存在幾個(gè)問題
- 1.注入的時(shí)候要填寫正確的包名虏辫,否則在運(yùn)行期會(huì)出錯(cuò),且不容易找到锈拨;
- 2.針對(duì)接口實(shí)現(xiàn)類砌庄,不能混淆,否則會(huì)導(dǎo)致反射找不到具體的類,因?yàn)槭歉鶕?jù)類的全路徑反射創(chuàng)建對(duì)象娄昆;所以每次寫一個(gè)接口+實(shí)現(xiàn)類佩微,都要在混淆文件中添加一下,比較麻煩……
- 3.每次添加新的接口通信萌焰,都需要手動(dòng)去注入到map集合哺眯,稍微有點(diǎn)麻煩,能否改為自動(dòng)注冊(cè)呢扒俯?
- 4.每次還要在Transfer的類中奶卓,添加獲取該接口對(duì)象的方法,能否自動(dòng)一點(diǎn)陵珍?
- 5.可能出現(xiàn)空指針寝杖,一旦忘記沒有注入或者反射創(chuàng)建對(duì)象失敗,則直接導(dǎo)致崩潰……
04.項(xiàng)目組件通信流程
- 具體實(shí)現(xiàn)方案
- 比方說互纯,主app中的首頁有版本更新瑟幕,業(yè)務(wù)組件用戶中心的設(shè)置頁面也有版本更新,而版本升級(jí)的邏輯是寫在版本更新業(yè)務(wù)組件中留潦。這個(gè)時(shí)候操作如下所示
05.逆向簡(jiǎn)化注冊(cè)流程
- 在module通信組件中定義接口只盹,注意需要繼承IRouteApi接口
public interface IUpdateManager extends IRouteApi { void checkUpdate(UpdateManagerCallBack updateManagerCallBack); interface UpdateManagerCallBack { void updateCallBack(boolean isNeedUpdate); } }
- 在需要實(shí)現(xiàn)服務(wù)的組件中寫接口實(shí)現(xiàn)類,注意需要添加注解
@RouteImpl(IUpdateManager.class) public class UpdateImpl implements IUpdateManager { @Override public void checkUpdate(UpdateManagerCallBack updateManagerCallBack) { //省略 } }
- 如何獲取服務(wù)的實(shí)例對(duì)象
//無返回值的案例 //設(shè)置監(jiān)聽 IUpdateManager iUpdateManager = TransferManager.getInstance().getApi(IUpdateManager.class); iUpdateManager.checkUpdate(new IUpdateManager.UpdateManagerCallBack() { @Override public void updateCallBack(boolean isNeedUpdate) { } }); //有返回值的案例 userApi = TransferManager.getInstance().getApi(IUserManager.class); String userInfo = userApi.getUserInfo();
- 關(guān)于get/put主要是存屬什么呢
/** * key表示的是自定義通信接口 * value表示自定義通信接口的實(shí)現(xiàn)類 */ private Map<Class, Class> apiImplementMap = new HashMap<>();
- 代碼混淆
-keep class com.yc.api.**{*;} -keep public class * implements com.yc.api.** { *; }
- 不需要在額外添加通信接口實(shí)現(xiàn)類的混淆代碼
- 因?yàn)橛玫搅朔瓷渫迷海沂怯肅lass.forName(name)創(chuàng)建反射對(duì)象殖卑。所以必須保證name路徑是正確的,否則找不到類坊萝。
- 該庫孵稽,你定義的實(shí)現(xiàn)類已經(jīng)繼承了我定義的接口,因?yàn)獒槍?duì)繼承com.yc.api.**的子類十偶,會(huì)忽略混淆菩鲜。已經(jīng)處理……所以不需要你額外處理混淆問題!
06.這個(gè)注解是做什么的
- 這個(gè)注解有什么用呢
- 框架會(huì)在項(xiàng)目的編譯器掃描所有添加@RouteImpl注解的XxxImpl接口實(shí)現(xiàn)類惦积,然后傳入接口類的class對(duì)象接校。這樣就可以通過注解拿到接口和接口的實(shí)現(xiàn)類……
- apt編譯后生成的代碼
- build--->generated--->ap_generated_sources--->debug---->out---->com.yc.api.contract
- 這段代碼什么意思:編譯器生成代碼,并且該類是繼承自己自定義的接口狮崩,調(diào)用IRegister接口中的register方法蛛勉,key是接口class,value是接口實(shí)現(xiàn)類class睦柴,直接在編譯器把接口和實(shí)現(xiàn)類存儲(chǔ)起來诽凌。用的時(shí)候直接取……
public class IUpdateManager$$Contract implements IRouteContract { @Override public void register(IRegister register) { register.register(IUpdateManager.class, UpdateImpl.class); } }
07.注解是如何生成代碼
- 如何拿到注解標(biāo)注的類,看個(gè)案例
@RouteImpl(IUserInfoManager.class) public class Test implements IUserInfoManager { @Override public String getUserId() { return null; } } private void test(){ //這個(gè)地方先寫個(gè)假的業(yè)務(wù)代碼坦敌,實(shí)際apt中是通過roundEnvironment對(duì)象拿到注解標(biāo)記的類 Class c = Test.class; //Set<? extends Element> annotated = roundEnvironment.getElementsAnnotatedWith(typeElement); //找到修飾了注解RouteImpl的類 RouteImpl annotation = (RouteImpl) c.getAnnotation(RouteImpl.class); if (annotation != null) { try { //獲取ContentView的屬性值 Class value = annotation.value(); String name = value.getName(); System.out.println("注解標(biāo)記的類名"+name); } catch (RuntimeException e) { e.printStackTrace(); System.out.println("注解標(biāo)記的類名"+e.getMessage()); } } }
- 手動(dòng)編程還是自動(dòng)生成
- 在代碼的編寫過程中自己手動(dòng)實(shí)現(xiàn)皿淋,也可以通過apt生成招刹。作為一個(gè)框架,當(dāng)然是自動(dòng)解析RouteImpl注解然后生成這些類文件更好了窝趣。要想自動(dòng)生成代碼的映射關(guān)系,那么便要了解apt和javapoet了训柴。
08.如何定義注解處理器
- apt工具了解一下
- APT是Annotation Processing Tool的簡(jiǎn)稱,即注解處理工具哑舒。它是在編譯期對(duì)代碼中指定的注解進(jìn)行解析,然后做一些其他處理(如通過javapoet生成新的Java文件)幻馁。
- 定義注解處理器
- 用來在編譯期掃描加入@RouteImpl注解的類洗鸵,然后做處理。這也是apt最核心的一步仗嗦,新建RouteImplProcessor 繼承自 AbstractProcessor,然后實(shí)現(xiàn)process方法膘滨。在項(xiàng)目編譯期會(huì)執(zhí)行RouterProcessor的process()方法,我們便可以在這個(gè)方法里處理RouteImpl注解了稀拐。
- 初始化自定義Processor
@AutoService(Processor.class) public class RouteImplProcessor extends AbstractProcessor { }
- 在init方法中初始化獲取文件生成器信息
/** * 初始化方法 * @param processingEnvironment 獲取信息 */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //文件生成器 類/資源 filer = processingEnv.getFiler(); //節(jié)點(diǎn)工具類 (類火邓、函數(shù)、屬性都是節(jié)點(diǎn)) elements = processingEnv.getElementUtils(); }
- 在process方法中拿到注解標(biāo)記的類信息
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { for (TypeElement typeElement : set) { Set<? extends Element> annotated = roundEnvironment.getElementsAnnotatedWith(typeElement); for (Element apiImplElement : annotated) { //被 RouteImpl 注解的節(jié)點(diǎn)集合 RouteImpl annotation = apiImplElement.getAnnotation(RouteImpl.class); if (annotation == null || !(apiImplElement instanceof TypeElement)) { continue; } ApiContract<ClassName> apiNameContract = ElementTool.getApiClassNameContract(elements, annotationValueVisitor,(TypeElement) apiImplElement); if (RouteConstants.LOG){ System.out.println("RouteImplProcessor--------process-------apiNameContract---"+apiNameContract); } } } return true; }
- 然后生成代碼德撬,主要是指定生成代碼路徑铲咨,然后創(chuàng)建typeSpec注解生成代碼。
- 這個(gè)javapoet工具蜓洪,目前還緊緊是套用ARouter纤勒,創(chuàng)建類名,添加接口隆檀,添加注解摇天,添加方法,添加修飾符恐仑,添加函數(shù)體等等泉坐。也就是說將一個(gè)類代碼拆分成n個(gè)部分,然后逆向拼接到一起菊霜。最后去write寫入代碼……
//生成注解類相關(guān)代碼 TypeSpec typeSpec = buildClass(apiNameContract); String s = typeSpec.toString(); if (RouteConstants.LOG){ System.out.println("RouteImplProcessor--------process-------typeSpec---"+s); } try { //指定路徑:com.yc.api.contract JavaFile.builder(RouteConstants.PACKAGE_NAME_CONTRACT, typeSpec) .build() .writeTo(filer); } catch (IOException e) { e.printStackTrace(); }
- 來看看怎么創(chuàng)建注解類
- 大概思路就是坚冀,將我們平時(shí)的類,拆分鉴逞,然后拼接成實(shí)體记某。ParameterSpec是創(chuàng)建參數(shù)的實(shí)現(xiàn),MethodSpec是函數(shù)的生成實(shí)現(xiàn)等等……
private TypeSpec buildClass(ApiContract<ClassName> apiNameContract) { String simpleName = apiNameContract.getApi().simpleName(); //獲取 com.yc.api.IRouteContract 信息构捡,也就是IRouteContract接口的路徑 TypeElement typeElement = elements.getTypeElement(RouteConstants.INTERFACE_NAME_CONTRACT); ClassName className = ClassName.get(typeElement); String name = simpleName + RouteConstants.SEPARATOR + RouteConstants.CONTRACT; //這里面又有添加方法注解液南,添加修飾符,添加參數(shù)規(guī)格勾徽,添加函數(shù)題滑凉,添加返回值等等 MethodSpec methodSpec = buildMethod(apiNameContract); //創(chuàng)建類名 return TypeSpec.classBuilder(name) //添加super接口 .addSuperinterface(className) //添加修飾符 .addModifiers(Modifier.PUBLIC) //添加方法【然后這里面又有添加方法注解,添加修飾符,添加參數(shù)規(guī)格畅姊,添加函數(shù)題咒钟,添加返回值等等】 .addMethod(methodSpec) //創(chuàng)建 .build(); }
09.項(xiàng)目庫的設(shè)計(jì)和完善
- ModuleBus主要由三部分組成,包括對(duì)外提供的api調(diào)用模塊若未、注解模塊以及編譯時(shí)通過注解生產(chǎn)相關(guān)的類模塊朱嘴。
- api-compiler 編譯期解析注解信息并生成相應(yīng)類以便進(jìn)行注入的模塊
- api-manager 注解的聲明和信息存儲(chǔ)類的模塊,以及開發(fā)調(diào)用的api功能和具體實(shí)現(xiàn)
- 編譯生成代碼發(fā)生在編譯器
- 編譯期是在項(xiàng)目編譯的時(shí)候粗合,這個(gè)時(shí)候還沒有開始打包萍嬉,也就是沒有生成apk呢!框架在這個(gè)時(shí)期根據(jù)注解去掃描所有文件隙疚,然后生成路由映射文件壤追。這些文件都會(huì)統(tǒng)一打包到apk里!
- 無需初始化操作
- 先看ARouter供屉,會(huì)有初始化行冰,主要是收集路由映射關(guān)系文件,在程序啟動(dòng)的時(shí)候掃描這些生成的類文件贯卦,然后獲取到映射關(guān)系信息资柔,保存起來。這個(gè)封裝庫不需要初始化撵割,簡(jiǎn)化步驟贿堰,在獲取的時(shí)候如果沒有則在put操作map集合。具體看代碼啡彬!
10.封裝該庫有哪些特點(diǎn)
- 注解生成代碼自動(dòng)注冊(cè)
- 使用apt注解在編譯階段生成服務(wù)接口與實(shí)現(xiàn)的映射注冊(cè)幫助類羹与,其實(shí)這部分就相當(dāng)于是替代了之前在application初始化注入的步驟,獲取服務(wù)時(shí)自動(dòng)使用幫助類完成注冊(cè)庶灿,不必手動(dòng)調(diào)用注冊(cè)方法纵搁。
- 避免空指針崩潰
- 無服務(wù)實(shí)現(xiàn)注冊(cè)時(shí),使用空對(duì)象模式 + 動(dòng)態(tài)代理的設(shè)計(jì)提前暴露調(diào)用錯(cuò)誤往踢,主要拋出異常腾誉,在測(cè)試時(shí)就發(fā)現(xiàn)問題,防止空指針異常峻呕。
- 代碼入侵性低
- 無需改動(dòng)之前的代碼利职,只需要在之前的接口和接口實(shí)現(xiàn)類按照約定添加注解規(guī)范即可。其接口+接口實(shí)現(xiàn)類還是用之前的瘦癌,完全無影響……
- 按照你需要來加載
- 首次獲取接口服務(wù)的時(shí)候猪贪,用反射生成映射注冊(cè)幫助類的實(shí)例,再返回實(shí)現(xiàn)的實(shí)例讯私。
- 豐富的代碼案例
- 代碼案例豐富热押,提供豐富的案例西傀,然后多個(gè)業(yè)務(wù)場(chǎng)景,盡可能完善好demo桶癣。
- 該庫注解生成代碼在編譯器
- 在編譯器生成代碼拥褂,并且該類是繼承自己自定義的接口,存儲(chǔ)的是map集合牙寞,key是接口class肿仑,value是接口實(shí)現(xiàn)類class,直接在編譯器把接口和實(shí)現(xiàn)類存儲(chǔ)起來碎税。用的時(shí)候直接取……
11.一些常見的報(bào)錯(cuò)問題
- Didn't find class "com.yc.api.contract.IUserManager$$Contract" on path
- 注解生成的代碼失敗導(dǎo)致出現(xiàn)這個(gè)問題。為什么會(huì)出現(xiàn)這種情況馏锡?修改gradle的構(gòu)建版本……
public class IUpdateManager$$Contract implements IApiContract { @Override public void register(IRegister register) { register.register(IUpdateManager.class, UpdateImpl.class); } }
- 關(guān)于apt編譯器不能生成代碼的問題雷蹂,可能會(huì)有這么一些關(guān)鍵點(diǎn)
- 第一查看module的依賴,如果沒有依賴請(qǐng)先添加依賴
implementation project(path: ':api-manager') annotationProcessor project(path: ':api-compiler')
- 第二查看寫完wirter的流沒有關(guān)閉杯道,會(huì)造成生成文件匪煌,但文件內(nèi)容為空,或者不全党巾;
- 第三可能是Android Gradle及構(gòu)建版本問題萎庭,我的是3.4.1 + 5.2.1,會(huì)出現(xiàn)不兼容的情況齿拂,大神建議3.3.2 + 4.10.1以下都可以驳规。聽了建議降低版本果然構(gòu)建編譯,新的文件生成了署海。
12.部分原理分析的說明
- 注解是如何生成代碼的吗购?也就是javapoet原理……
- 這個(gè)javapoet工具,目前還緊緊是套用ARouter砸狞,創(chuàng)建類名捻勉,添加接口,添加注解刀森,添加方法踱启,添加修飾符,添加函數(shù)體等等研底。也就是說將一個(gè)類代碼拆分成n個(gè)部分埠偿,然后逆向拼接到一起。最后去write寫入代碼……
- 但是飘哨,怎么拼接和并且創(chuàng)建.java文件的原理胚想,待完善。目前處于會(huì)用……
- Class.forName(name)反射如何找到name路徑的這個(gè)類芽隆,從jvm層面分析浊服?
- 待完善
- new和Class.forName("").newInstance()創(chuàng)建對(duì)象有何區(qū)別统屈?
A a = (A)Class.forName("com.yc.demo.impl.UpdateImpl").newInstance(); A a = new A();
- 它們的區(qū)別在于創(chuàng)建對(duì)象的方式不一樣牙躺,前者(newInstance)是使用類加載機(jī)制愁憔,后者(new)是創(chuàng)建一個(gè)新類。
- 為什么會(huì)有兩種創(chuàng)建對(duì)象方式孽拷?
- 主要考慮到軟件的可伸縮吨掌、可擴(kuò)展和可重用等軟件設(shè)計(jì)思想。
- 從JVM的角度上看:
- 我們使用關(guān)鍵字new創(chuàng)建一個(gè)類的時(shí)候脓恕,這個(gè)類可以沒有被加載膜宋。但是使用newInstance()方法的時(shí)候,就必須保證:1炼幔、這個(gè)類已經(jīng)加載秋茫;2、這個(gè)類已經(jīng)連接了乃秀。
- 而完成上面兩個(gè)步驟的正是Class的靜態(tài)方法forName()所完成的肛著,這個(gè)靜態(tài)方法調(diào)用了啟動(dòng)類加載器,即加載 java API的那個(gè)加載器跺讯。
- 現(xiàn)在可以看出枢贿,newInstance()實(shí)際上是把new這個(gè)方式分解為兩步,即首先調(diào)用Class加載方法加載某個(gè)類刀脏,然后實(shí)例化局荚。 這樣分步的好處是顯而易見的。我們可以在調(diào)用class的靜態(tài)加載方法forName時(shí)獲得更好的靈活性火本,提供給了一種降耦的手段危队。
- 區(qū)別
- 首先,newInstance( )是一個(gè)方法钙畔,而new是一個(gè)關(guān)鍵字茫陆;其次,Class下的newInstance()的使用有局限擎析,因?yàn)樗蓪?duì)象只能調(diào)用無參的構(gòu)造函數(shù)簿盅,而使用 new關(guān)鍵字生成對(duì)象沒有這個(gè)限制。