嗨列林,我是哈利迪~《看完不忘系列》之dagger(樹干篇)一文對dagger做了初步介紹,下面我們一起來瞅瞅dagger的一些細(xì)節(jié)杠氢。
本文約3.5k字,閱讀大約9分鐘续崖。
Dagger源碼基于最新版本2.28.3
目錄:
@Binds和@Provides區(qū)別
Binds和Provides都是搭配Module(模塊敲街,用于支持模塊化)使用的,Binds表示抽象和具體的綁定严望,作用在抽象方法上多艇,如下,
@Module
abstract class GasEngineModule {//汽油引擎模塊
@Binds
abstract IEngine makeGasEngine(GasEngine engine);
//抽象的IEngine接口作為返回值像吻,具體的GasEngine作為入?yún)⒕颍纬山壎P(guān)系
//當(dāng)有地方依賴了IEngine時(shí),這里可以為他提供GasEngine實(shí)例
}
為什么用抽象方法拨匆,因?yàn)檫@里我們要做的只是聲明綁定關(guān)系姆涩,dagger根據(jù)聲明就知道如何提供實(shí)例了,dagger不會調(diào)用這個(gè)方法或?yàn)樗删唧w實(shí)現(xiàn)惭每。
當(dāng)需要提供的實(shí)例不是一個(gè)而是一組時(shí)骨饿,Binds可以和集合注解:@IntoSet、@ElementsIntoSet台腥、@IntoMap一起使用宏赘,
@Module
abstract class GasEngineModule {//汽油引擎模塊
@Binds
@IntoMap //支持入map
@StringKey("Gas") //使用IntoMap的話,還需要指定一個(gè)key
abstract IEngine makeGasEngine(GasEngine engine);
}
@Module
abstract class ElectricEngineModule {//電動引擎模塊
@Binds
@IntoMap //支持入map
@StringKey("Electric")
abstract IEngine makeElectricEngine(ElectricEngine engine);
}
key的指定也有多種類型黎侈,如@StringKey置鼻、@IntKey、@LongKey蜓竹、@ClassKey等。
而Provides就比較簡單了储藐,當(dāng)我們沒法用@Inject來標(biāo)記實(shí)例的創(chuàng)建姿勢時(shí)俱济,可以用@Provides來提供實(shí)例,比如Retrofit是三方庫的類我們沒法標(biāo)記其構(gòu)造方法钙勃,則可以用Provides提供蛛碌,
@Module
public class NetworkModule {
@Provides
public Retrofit provideRetrofit() {
return new Retrofit.Builder()
.baseUrl("xxx")
.build();
}
}
可見Binds用于聲明接口和實(shí)例的綁定關(guān)系
,Provides用于直接提供實(shí)例
辖源,他們都寫在可模塊化的Module里蔚携。
然后經(jīng)過Component指定modules,這些聲明就能被dagger找到了克饶,
@Component(modules = {GasEngineModule.class, ElectricEngineModule.class}) //傳入
public interface CarGraph {
//...
}
@Subcomponent
Subcomponent即子組件酝蜒,可以將父組件的能力繼承過來,同時(shí)擴(kuò)展自己的能力》龋現(xiàn)在已經(jīng)有了@Component的CarGraph來造車亡脑,我們可以建一個(gè)@Subcomponent的WheelGraph來造輪胎,(雖是輪胎圖紙,但也有造車之心)
@Subcomponent //子組件霉咨,輪胎圖紙
public interface WheelGraph {
Wheel makeWheel(); //造輪胎
@Subcomponent.Factory //告知CarGraph如何創(chuàng)建WheelGraph
interface Factory {
WheelGraph create();
}
}
下面要告知dagger WheelGraph是CarGraph的子組件蛙紫,需要提供Module,借助Module將子組件的類WheelGraph傳給subcomponents途戒,
@Module(subcomponents = WheelGraph.class)
public class WheelModule {
}
將Module添加到CarGraph坑傅,
@Component(modules = {WheelModule.class})
public interface CarGraph {
//...
//提供接口,讓使用者能借助Factory拿到WheelGraph實(shí)例
WheelGraph.Factory wheelGraph();
}
使用喷斋,
//輪胎圖紙
WheelGraph wheelGraph = carGraph.wheelGraph().create();
//造輪胎
Wheel wheel = wheelGraph.makeWheel();
然后我們看下子組件是如何使用父組件的能力的唁毒,在WheelGraph里添加一個(gè)inject,
@Subcomponent
public interface WheelGraph {
//...
//告訴dagger誰要注入
void inject(DaggerActivity activity);
}
把DaggerActivity里的 carGraph.inject(this) 換成 wheelGraph.inject(this)继准,發(fā)現(xiàn)mCar也能正常運(yùn)行枉证,
class DaggerActivity extends AppCompatActivity {
@Inject
Car mCar;
void onCreate(Bundle savedInstanceState) {
CarGraph carGraph = DaggerCarGraph.create();
//carGraph.inject(this);
WheelGraph wheelGraph = carGraph.wheelGraph().create();
wheelGraph.inject(this);
//正常發(fā)動
mCar.start();
}
}
可見wheelGraph把carGraph的造車能力也給繼承過來了。
我們看到生成的DaggerCarGraph類移必,里面有個(gè)內(nèi)部類WheelGraphImpl(子組件不會生成單獨(dú)的DaggerXXX類室谚,而是依附于父組件),
class WheelGraphImpl implements WheelGraph {
@Override
public Wheel makeWheel() {
return new Wheel();
}
@Override
public void inject(DaggerActivity activity) {
injectDaggerActivity(activity);// ↓
}
private DaggerActivity injectDaggerActivity(DaggerActivity instance) {
//跟父組件的注入邏輯一致崔泵,還調(diào)用了父組件的造車能力DaggerCarGraph.this.makeCar()
DaggerActivity_MembersInjector.injectMCar(instance, DaggerCarGraph.this.makeCar());
return instance;
}
}
谷歌示例會形象些秒赤,ApplicationComponent和LoginComponent就是父與子的關(guān)系,LoginComponent自己管理LoginViewModel憎瘸,但同時(shí)又繼承了父組件的數(shù)據(jù)獲取能力入篮,
@Scope
Scope即作用域,是一種元注解幌甘,即作用于注解的注解潮售。@Singleton可以實(shí)現(xiàn)全局單例,當(dāng)父組件CarGraph標(biāo)記了@Singleton锅风,子組件WheelGraph就不能使用相同的@Singleton了酥诽,我們可以自定義注解。
@Scope //元注解
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope { //表示我想要一個(gè)Activity級別的作用域
}
使用皱埠,
@ActivityScope //使用自定義注解
public class Wheel {
}
@ActivityScope //使用自定義注解
@Subcomponent
public interface WheelGraph {
}
@Qualifier
Qualifier即限定符肮帐,也是一種元注解。當(dāng)有多個(gè)相同類型的實(shí)例要提供時(shí)边器,比如輪胎有最小和最大尺寸都是相同的int類型训枢,
public class Wheel {
int minSize;
int maxSize;
@Inject
public Wheel(int minSize, int maxSize) {
this.minSize = minSize;
this.maxSize = maxSize;
}
}
WheelModule進(jìn)行提供,
@Module(subcomponents = WheelGraph.class)
public class WheelModule {
@Provides
static int minSize() {
return 12;
}
@Provides
static int maxSize() {
return 23;
}
}
這時(shí)dagger就會出錯(cuò)了忘巧,他不知道多個(gè)int的對應(yīng)關(guān)系恒界,需要自定義注解來指定,
@Qualifier //元注解
@Retention(RUNTIME)
public @interface MinSize {} //限定符注解
@Qualifier //元注解
@Retention(RUNTIME)
public @interface MaxSize {} //限定符注解
然后在Wheel和WheelModule都加上限定即可
public class Wheel {
int minSize;
int maxSize;
@Inject
public Wheel(@MinSize int minSize, @MaxSize int maxSize) {//使用自定義注解
this.minSize = minSize;
this.maxSize = maxSize;
}
}
@Module(subcomponents = WheelGraph.class)
public class WheelModule {
@Provides
@MinSize //使用自定義注解
static int minSize() {
return 12;
}
@Provides
@MaxSize //使用自定義注解
static int maxSize() {
return 23;
}
}
SPI機(jī)制
dagger文檔里提到了SPI機(jī)制砚嘴,哈迪蹩腳的英文看得一頭霧水仗处,不過最近剛好看到了好友帥丙的文章Dubbo的SPI機(jī)制眯勾,做了個(gè)簡單的理解,SPI(Service Provider Interface婆誓,服務(wù)提供者接口)簡單來說吃环,就是先統(tǒng)一接口,然后按需擴(kuò)展自己的實(shí)現(xiàn)類洋幻,到了運(yùn)行的時(shí)候再去查找加載對應(yīng)的實(shí)現(xiàn)類郁轻,不過這里的實(shí)現(xiàn)類,我們集中放在一個(gè)事先約定好的地方文留。
比如MySQL好唯,
約定在 Classpath 下的 META-INF/services/ 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,然后文件里面記錄的是此 jar 包提供的具體實(shí)現(xiàn)類的全限定名燥翅。
這樣當(dāng)我們引用了某個(gè) jar 包的時(shí)候就可以去找這個(gè) jar 包的 META-INF/services/ 目錄骑篙,再根據(jù)接口名找到文件,然后讀取文件里面的內(nèi)容去進(jìn)行實(shí)現(xiàn)類的加載與實(shí)例化森书。
-- 敖丙
聲明好了實(shí)現(xiàn)類后靶端,運(yùn)行時(shí)就會有java自帶的ServiceLoader來解析文件,加載實(shí)現(xiàn)類(不過Dubbo為了優(yōu)化自建了一套)凛膏。
再比如我們更為熟悉的gradle-plugin開發(fā)杨名,也會約定一個(gè)地方來聲明實(shí)現(xiàn)類,在插件工程的resources/META-INF/gradle-plugins/目錄下建一個(gè)plugin_name.properties
文件猖毫,聲明類的全名台谍,(參見butterknife)
implementation-class=butterknife.plugin.ButterKnifePlugin
所以SPI簡單理解就是一種可擴(kuò)展的、提供實(shí)現(xiàn)類的約定機(jī)制
吁断。那么SPI對dagger來說有啥用途呢趁蕊?
注意:dagger的SPI還是實(shí)驗(yàn)性的,隨時(shí)可變
我們看到dagger的spi包下的一個(gè)接口BindingGraphPlugin仔役,
//我是一個(gè)插件接口掷伙,可以插在dagger圖綁定(創(chuàng)建類連接依賴關(guān)系)的過程中
public interface BindingGraphPlugin {
//為Dagger處理器遇到的每個(gè)有效根綁定圖調(diào)用一次。 可以使用diagnosticReporter報(bào)告診斷
void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter);
//使用Filer初始化此插件骂因,該Filer可用于基于綁定圖編寫Java或其他文件。
//在訪問任何圖形之前赃泡,此插件的每個(gè)實(shí)例將調(diào)用一次
default void initFiler(Filer filer) {}
//...
default void initTypes(Types types) {}
//...
default void initElements(Elements elements) {}
//...
default void initOptions(Map<String, String> options) {}
//返回此插件用于配置行為的注釋處理選項(xiàng)寒波。
default Set<String> supportedOptions() {
return Collections.emptySet();
}
//插件的唯一名稱,將在打印到Messager的診斷程序中使用升熊。 默認(rèn)情況下俄烁,使用插件的標(biāo)準(zhǔn)名稱
default String pluginName() {
return getClass().getCanonicalName();
}
}
咦,BindingGraphPlugin是啥玩意级野?你想想页屠,再好好想想,他是不是長得很像我們開發(fā)注解處理器APT時(shí)做的一些事情,看下ButterKnifeProcessor辰企,
@AutoService(Processor.class)
//使用AutoService自動幫我們做SPI聲明风纠,
//他會創(chuàng)建META-INF/services/javax.annotation.processing.Processor文件,幫我們寫入注解處理器的類全名
public final class ButterKnifeProcessor extends AbstractProcessor {
public void init(ProcessingEnvironment env) {}
public Set<String> getSupportedOptions() {}
public Set<String> getSupportedAnnotationTypes() {}
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {}
}
怎么樣牢贸,長得是不是還挺像竹观,那就很好理解了,注解處理器在構(gòu)建時(shí)插入一段邏輯來解析注解生成輔助類潜索,那dagger的BindingGraphPlugin就是在圖綁定階段插一段邏輯來做自定義處理嘛臭增。
dagger內(nèi)置了一些實(shí)現(xiàn)類,如DependencyCycleValidator用來檢測是否有循環(huán)依賴竹习、FailingPlugin用來生成錯(cuò)誤報(bào)告誊抛、InjectBindingValidator用于驗(yàn)證@Inject標(biāo)記的構(gòu)造方法...
如果有特殊需求,就可以自定義插件整陌,插入自己的邏輯拗窃,例如實(shí)現(xiàn)以下功能:
- 添加項(xiàng)目特定的錯(cuò)誤和警告,例如android.content.Context的綁定必須具有@Qualifier或?qū)崿F(xiàn)DatabaseRelated的綁定必須是作用域
- 生成額外的Java源文件
- 將Dagger模型序列化為資源文件
- 建立Dagger模型的可視化(可將綁定圖(依賴關(guān)系)轉(zhuǎn)成json/proto蔓榄,然后渲染成UI)
-- dagger
自定義插件使用@AutoService(BindingGraphPlugin.class)進(jìn)行SPI聲明并炮,例如dagger的一個(gè)TestPlugin,
@AutoService(BindingGraphPlugin.class)
public final class TestPlugin implements BindingGraphPlugin {
private Filer filer;//文件操作
@Override
public void initFiler(Filer filer) {
this.filer = filer;
}
@Override
public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
//定制邏輯...
}
}
定義好插件后甥郑,dagger就會使用java自帶的ServiceLoader進(jìn)行查找和加載逃魄,在SpiModule中,
@Module
abstract class SpiModule {
private SpiModule() {}
@Provides
@Singleton
static ImmutableSet<BindingGraphPlugin> externalPlugins(
@TestingPlugins Optional<ImmutableSet<BindingGraphPlugin>> testingPlugins,
@ProcessorClassLoader ClassLoader processorClassLoader) {
return testingPlugins.orElseGet(
() ->
ImmutableSet.copyOf(
//ServiceLoader.load
ServiceLoader.load(BindingGraphPlugin.class, processorClassLoader)));
}
}
對ServiceLoader.load實(shí)現(xiàn)感興趣的讀者澜搅,可以看敖丙的文章Dubbo的SPI機(jī)制伍俘,實(shí)現(xiàn)不是很復(fù)雜。
延伸
延伸部分略枯燥勉躺,純屬個(gè)人裝B學(xué)習(xí)記錄用癌瘾,不感興趣可跳過~??
偷偷裝個(gè)B,哈迪在公司其實(shí)腳本和后端都寫過(當(dāng)然只是內(nèi)部的管理平臺饵溅,不可能是核心業(yè)務(wù)啦)妨退,所以分析技術(shù)時(shí)已經(jīng)習(xí)慣了各種發(fā)散和對比,越發(fā)覺得其實(shí)很多端的技術(shù)很是相通~看過Tangram系列的讀者應(yīng)該也見過TangramService項(xiàng)目蜕企,這還是用大學(xué)時(shí)搭的殼來寫的...
@Autowired注入
源碼基于spring 4.3.13疹味,spring boot 1.5.9
使用姿勢齿诉,
@RestController
public class ActivityController {
@Autowired //自動注入
private ActivityService activityService;
}
@Service
public class ActivityService {} //注意:因?yàn)榇a不多,所以就沒按規(guī)范抽成接口和Impl
那么activityService實(shí)例是如何自動注入的呢?
Spring會掃描@Component注解的Bean掏呼,然后AbstractAutowireCapableBeanFactory#populateBean負(fù)責(zé)注入bean的屬性鲜屏,他里邊調(diào)了ibp.postProcessPropertyValues风范,我們看到AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues,
//AutowiredAnnotationBeanPostProcessor.java
public PropertyValues postProcessPropertyValues() {
//1.獲取Class中關(guān)于屬性注入相關(guān)注解的元數(shù)據(jù)
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
//2.完成屬性注入操作
metadata.inject(bean, beanName, pvs);
return pvs;
}
1.findAutowiringMetadata主要是做檢查和緩存聚唐,他調(diào)了buildAutowiringMetadata,跟進(jìn)腔召,
//AutowiredAnnotationBeanPostProcessor.java
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList<InjectionMetadata.InjectedElement> elements =
new LinkedList<InjectionMetadata.InjectedElement>();
//目標(biāo)類杆查,如我們的ActivityController
Class<?> targetClass = clazz;
do {
//收集
final LinkedList<InjectionMetadata.InjectedElement> currElements =
new LinkedList<InjectionMetadata.InjectedElement>();
//記錄有@Autowired,@Value等注解的field
ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {
//...
currElements.add(new AutowiredFieldElement(field, required));
}
}
});
//記錄有@Autowired宴咧,@Value等注解的method
ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
//...
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
//...
currElements.add(new AutowiredMethodElement(method, required, pd));
}
}
});
//收集
elements.addAll(0, currElements);
//繼續(xù)往上查找父類
targetClass = targetClass.getSuperclass();
}while (targetClass != null && targetClass != Object.class);
//返回包裝好的元數(shù)據(jù)
return new InjectionMetadata(clazz, elements);
}
解析類的注解得到包裝好的元數(shù)據(jù)后根灯,就調(diào)用2.metadata.inject進(jìn)行注入,他又調(diào)了element.inject掺栅,我們直接看到AutowiredFieldElement#inject烙肺,
//AutowiredFieldElement
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
//根據(jù)元數(shù)據(jù),解析屬性值氧卧,找到我們的ActivityService實(shí)例
//DefaultListableBeanFactory.resolveDependency -> doResolveDependency ->
//findAutowireCandidates -> addCandidateEntry -> candidates.put ->
//descriptor.resolveCandidate -> AbstractBeanFactory.getBean -> getObjectForBeanInstance...
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
//設(shè)置可訪問
ReflectionUtils.makeAccessible(field);
//將實(shí)例注入
field.set(bean, value);
}
可見@Autowired的注入是基于反射
來做的~
gRPC
RPC(Remote Procedure Call)是遠(yuǎn)程過程調(diào)用
桃笙,后端進(jìn)行服務(wù)化后,通常會在一個(gè)主機(jī)上調(diào)用另一個(gè)主機(jī)提供的方法沙绝,比如獲取一個(gè)用戶收藏的商品搏明,用戶服務(wù)器就會調(diào)用商品服務(wù)器的方法,什么闪檬?居然還能調(diào)用遠(yuǎn)程主機(jī)應(yīng)用的方法星著?其實(shí)可以這么理解,就是基于一定協(xié)議和數(shù)據(jù)格式進(jìn)行通信
粗悯,比如http+json/proto虚循,但是直接這么用不方便,就封裝成接口方法的方式來調(diào)用了样傍,看起來是調(diào)了一個(gè)方法横缔,其實(shí)內(nèi)部進(jìn)行了遠(yuǎn)程通信。像Dubbo衫哥、Spring Cloud茎刚、gRPC就是一些遠(yuǎn)程過程調(diào)用的框架〕贩辏可以類比Android的跨進(jìn)程通信IPC膛锭,只不過RPC不僅跨了進(jìn)程,還跨了主機(jī)蚊荣。
gRPC是谷歌開源的高性能遠(yuǎn)程過程調(diào)用框架初狰,dagger文檔的gRPC就寥寥幾句,還不完善妇押,看不出個(gè)所以然跷究,就先不看了姓迅,簡單了解下就行...
尾聲
由于哈迪沒有dagger實(shí)戰(zhàn)經(jīng)驗(yàn)敲霍,寫起來還是有點(diǎn)吃力的俊马,寫著寫著就有種搬文檔的感覺...而且dagger的使用程度貌似也不怎么高,真要用得爐火純青肩杈,可能還得要靠深刻理解業(yè)務(wù)柴我,能夠發(fā)掘契合度極高的業(yè)務(wù)高手了吧...怎么說呢,趁年輕好奇心足夠強(qiáng)烈扩然,多學(xué)點(diǎn)還是好的艘儒,我們下期見~??
系列文章:
參考資料
- GitHub & 文檔 & API
- csdn - Dubbo & csdn - Dubbo的SPI機(jī)制
- 簡書 - SpringBoot源碼解析@Value,@Autowired實(shí)現(xiàn)原理