Realm原始碼分析之Writes
前言
上篇是Realm原始碼分析的開篇,是關於Realm資料庫的初始化,關鍵一點是要抓住RealmProxyMediator這個代理中介者的例項化過程。在理解了Realm初始化之後,接下來就可以追蹤Realm資料庫讀寫等操作的原始碼了。因篇幅限制,先分析Writes。
Realm Writes
在基礎篇中提到過凡是繼承RealmObject的物件在Realm中被稱作Model,它對應著Realm資料庫中的一張表,而Model中的屬性對映著Realm資料庫表中的列。
先建立一個的Model類,後面會以這個Model來分析Realm資料庫的讀寫相關的操作,其程式碼如下
public class
檢視RealmObject原始碼,它的註釋很詳細,就不再贅述。注意到它是個抽象類,並實現了RealmModel與ManagableObject兩個介面。它還提供了不少方法,目前我們先忽略這些細節。另外RealmObject還使用RealmClass註解,推測應該會在自動生成程式碼的過程中被使用。如下縮略程式碼:
一大堆的註釋 ... ... @RealmClass public abstract class RealmObject implements RealmModel, ManagableObject { ... ... }
再看RealmModel,它是個空介面,註釋說得很明白:實現這個介面的並標註RealmClass註解就能由註解處理器自動生成代理類。因此,我們可以推測自己寫的這個Dog類,應該會被註解處理器自動生成一個代理類。
/** * Note: Object implementing this interface needs also to be annotated with {@link RealmClass}, * so the annotation processor can generate the underlining proxy class. */ public interface RealmModel { }
而ManagableObject介面,按註釋的說法是用於判斷RealmObject物件例項的合法性,其程式碼如下:
/** * This internal interface represents a java object that corresponds to data * that may be managed in the Realm core. It specifies the operations common to all such objects. */ public interface ManagableObject { boolean isManaged(); boolean isValid(); }
在瞭解了RealmObject類關係結構之後,再來建立RealmObject例項,程式碼如下:
public void test() { realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Dog myDog = realm.createObject(Dog.class); myDog.setName("Fido"); myDog.setAge(1); } }); }
上面的executeTransaction方法會同步執行事務操作,在回撥方法中就完成了Dog表的建立。先看看createObject方法:
public <E extends RealmModel> E createObject(Class<E> clazz) { checkIfValid(); return createObjectInternal(clazz, true, Collections.<String>emptyList()); }
createObject做了兩件事:一是Realm執行環境檢查,而是呼叫了createObjectInternal:
/** * Same as {@link #createObject(Class)} but this does not check the thread. * * @param clazz the Class of the object to create. * @param acceptDefaultValue if {@code true}, default value of the object will be applied and * if {@code false}, it will be ignored. * @return the new object. * @throws RealmException if the primary key is defined in the model class or an object cannot be created. */ // Called from proxy classes. <E extends RealmModel> E createObjectInternal( Class<E> clazz, boolean acceptDefaultValue, List<String> excludeFields) { Table table = schema.getTable(clazz); // Checks and throws the exception earlier for a better exception message. if (table.hasPrimaryKey()) { throw new RealmException(String.format(Locale.US, "'%s' has a primary key, use" + " 'createObject(Class<E>, Object)' instead.", table.getClassName())); } return configuration.getSchemaMediator().newInstance(clazz, this, OsObject.create(table), schema.getColumnInfo(clazz), acceptDefaultValue, excludeFields); }
注意到createObjectInternal方法前面的最後一行註釋,說這個方法會被代理物件呼叫。它主要做了兩件事:一是建立了一張Table,直接呼叫Mediator建立目標物件。先看Table的類繼承關係:
/** * This class is a base class for all Realm tables. The class supports all low level methods * (define/insert/delete/update) a table has. All the native communications to the Realm C++ * library are also handled by this class. */ public class Table implements TableSchema, NativeObject { ... ... } public interface TableSchema { long addColumn(RealmFieldType type, String name); void removeColumn(long columnIndex); void renameColumn(long columnIndex, String newName); } /** * This abstract class represents a native object from core. * It specifies the operations common to all such objects. * All Java classes wrapping a core class should implement NativeObject. */ public interface NativeObject { long NULLPTR = 0L; long getNativePtr(); long getNativeFinalizerPtr(); }
從上述原始碼與註釋,就可以將Table當作讀寫Realm Native層資料庫的API。繼續看Table的例項化程式碼:
Table getTable(Class<? extends RealmModel> clazz) { //classToTable是個快取,因此首次建立Table是會執行到下面的程式碼。 Table table = classToTable.get(clazz); if (table != null) { return table; } //獲取原始的ModelClass,即直接繼承RealmObject的那個model類,這裡的例項是Dog.class Class<? extends RealmModel> originalClass = Util.getOriginalModelClass(clazz); if (isProxyClass(originalClass, clazz)) { // If passed 'clazz' is the proxy, try again with model class. table = classToTable.get(originalClass); } //首次例項化會走到這裡,最終是其實是SharedRealm例項化了Table並快取起來,如下getTable原始碼 if (table == null) { table = realm.getSharedRealm() .getTable(realm.getConfiguration().getSchemaMediator().getTableName(originalClass)); classToTable.put(originalClass, table); } if (isProxyClass(originalClass, clazz)) { // 'clazz' is the proxy class for 'originalClass'. classToTable.put(clazz, table); } return table; }
public final class SharedRealm implements Closeable, NativeObject { ... ... public Table getTable(String name) { //至此就Table就與Realm native資料庫關聯起來 long tablePtr = nativeGetTable(nativePtr, name); return new Table(this, tablePtr); } // Throw IAE if the table doesn't exist. private static native long nativeGetTable(long nativeSharedRealmPtr, String tableName); }
Table的建立流程到此為止,回到9再看Mediator是如何建立目標Model物件的。基礎篇已經說過這個Mediator物件其實就是註解處理器知道生成的DefaultRealmModuleMediator,在我的例子中程式碼如下:
@Override public <E extends RealmModel> E newInstance(Class<E> clazz, Object baseRealm, Row row, ColumnInfo columnInfo, boolean acceptDefaultValue, List<String> excludeFields) { final BaseRealm.RealmObjectContext objectContext = BaseRealm.objectContext.get(); try { objectContext.set((BaseRealm) baseRealm, row, columnInfo, acceptDefaultValue, excludeFields); checkClass(clazz); if (clazz.equals(com.example.app.org.realm.demo.Dog.class)) { return clazz.cast(new io.realm.DogRealmProxy()); } throw getMissingProxyClassException(clazz); } finally { objectContext.clear(); } }
注意上面方法中的Row例項的是由OsObject.create(table)建立的,看看這個方法的原始碼:
public static UncheckedRow create(Table table) { final SharedRealm sharedRealm = table.getSharedRealm(); return new UncheckedRow(sharedRealm.context, table, nativeCreateNewObject(sharedRealm.getNativePtr(), table.getNativePtr())); }
再回到11中的程式碼,newInstance建立的不是Dog例項,而是其子類DogRealmProxy的例項。而DogRealmProxy則是由註解處理器自動生成,如下部分程式碼:
public class DogRealmProxy extends com.example.app.org.realm.demo.Dog implements RealmObjectProxy, DogRealmProxyInterface { ... ... private ProxyState<com.example.app.org.realm.demo.Dog> proxyState; ... ... DogRealmProxy() { //這裡的寫法比較神奇,realm$injectObjectContext一定是比較早在此直接注入的 proxyState.setConstructionFinished(); } @Override public void realm$injectObjectContext() { if (this.proxyState != null) { return; } final BaseRealm.RealmObjectContext context = BaseRealm.objectContext.get(); this.columnInfo = (DogColumnInfo) context.getColumnInfo(); this.proxyState = new ProxyState<com.example.app.org.realm.demo.Dog>(this); proxyState.setRealm$realm(context.getRealm()); proxyState.setRow$realm(context.getRow()); proxyState.setAcceptDefaultValue$realm(context.getAcceptDefaultValue()); proxyState.setExcludeFields$realm(context.getExcludeFields()); } ... ... }
public interface RealmObjectProxy extends RealmModel { void realm$injectObjectContext(); ProxyState realmGet$proxyState(); /** * Tuple class for saving meta data about a cached RealmObject. */ class CacheData<E extends RealmModel> { public int minDepth; public final E object; public CacheData(int minDepth, E object) { this.minDepth = minDepth; this.object = object; } } }
public interface DogRealmProxyInterface { public String realmGet$name(); public void realmSet$name(String value); public int realmGet$age(); public void realmSet$age(int value); }
注意在上面的程式碼中,並沒有發現在java程式碼中直接呼叫realm$injectObjectContext(),依據個人經驗推測是在構建過程中注入的。通過搜尋,發現在realm-transformer模組下的BytecodeModifier.groovy中找到了線索,如下兩個方法:
/** * Modifies a class adding its RealmProxy interface. * * @param clazz The CtClass to modify * @param classPool the Javassist class pool */ public static void addRealmProxyInterface(CtClass clazz, ClassPool classPool) { def proxyInterface = classPool.get("io.realm.${clazz.getSimpleName()}RealmProxyInterface") clazz.addInterface(proxyInterface) } //這就是注入realm$injectObjectContext()方法的地方 public static void callInjectObjectContextFromConstructors(CtClass clazz) { clazz.getConstructors().each { it.insertBeforeBody('if ($0 instanceof io.realm.internal.RealmObjectProxy) {' + ' ((io.realm.internal.RealmObjectProxy) $0).realm$injectObjectContext();' + ' }') } }
上面的程式碼就是往Model的代理類新增RealmProxyInterface以及在構造方法中注入injectObjectContext方法的呼叫。比如Dog類,其代理類是DogRealmProxy,增加的介面是DogRealmProxyInterface。
至此就把6中的realm.createObject(Dog.class)的執行流程追蹤完成了。接著往下看dog的set與get方法,也被BytecodeModifier.groovy修改了。如下程式碼:
/** * Adds Realm specific accessors to a model class. * All the declared fields will be associated with a getter and a setter. * * @param clazz the CtClass to add accessors to. */ public static void addRealmAccessors(CtClass clazz) { logger.debug " Realm: Adding accessors to ${clazz.simpleName}" def methods = clazz.getDeclaredMethods()*.name clazz.declaredFields.each { CtField field -> if (isModelField(field)) { if (!methods.contains("realmGet\$${field.name}".toString())) { clazz.addMethod(CtNewMethod.getter("realmGet\$${field.name}", field)) } if (!methods.contains("realmSet\$${field.name}".toString())) { clazz.addMethod(CtNewMethod.setter("realmSet\$${field.name}", field)) } } } } /** * Modifies a class replacing field accesses with the appropriate Realm accessors. * * @param clazz The CtClass to modify * @param managedFields List of fields whose access should be replaced */ public static void useRealmAccessors(CtClass clazz, List<CtField> managedFields) { clazz.getDeclaredBehaviors().each { behavior -> logger.debug " Behavior: ${behavior.name}" if ( ( behavior instanceof CtMethod && !behavior.name.startsWith('realmGet$') && !behavior.name.startsWith('realmSet$') ) || ( behavior instanceof CtConstructor ) ) { behavior.instrument(new FieldAccessToAccessorConverter(managedFields, clazz, behavior)) } } }
在RealmTransformer.groovy中的transform方法中會被呼叫,程式碼如下:
@Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { ... ... // Add accessors to the model classes in the target project inputModelClasses.each { BytecodeModifier.addRealmAccessors(it) BytecodeModifier.addRealmProxyInterface(it, classPool) BytecodeModifier.callInjectObjectContextFromConstructors(it) } // Use accessors instead of direct field access inputClassNames.each { logger.debug " Modifying class ${it}" def ctClass = classPool.getCtClass(it) BytecodeModifier.useRealmAccessors(ctClass, allManagedFields) ctClass.writeFile(getOutputFile(outputProvider).canonicalPath) } ... ... }
由上述轉換程式碼,就把Dog的屬性訪問方式修改了。比如,6中的myDog.setName(“Fido”)方法在構建的時候會被Realm提供的轉換器轉成myDog.realmSet$name(“Fido”)。
至此在6中的Dog建立例項的三行程式碼就追蹤完畢了,這也意味著executeTransaction方法執行完畢執行了資料庫的Write。
總之,Realm的Write操作由事務來提供保證,而Write過程的突破口就是追蹤Model物件建立的過程,在此過程中就會接觸到RealmProxy、Realm的realm-annotations-processor和realm-transformer等工具。
相關推薦
Realm原始碼分析之Writes
前言 上篇是Realm原始碼分析的開篇,是關於Realm資料庫的初始化,關鍵一點是要抓住RealmProxyMediator這個代理中介者的例項化過程。在理解了Realm初始化之後,接下來就可以追蹤R
Realm原始碼分析之copyToRealm與copyToRealmOrUpdate
createObject 在Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。 createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者: createObjec
Spark原始碼分析之Spark Shell(上)
https://www.cnblogs.com/xing901022/p/6412619.html 文中分析的spark版本為apache的spark-2.1.0-bin-hadoop2.7。 bin目錄結構: -rwxr-xr-x. 1 bigdata bigdata 1089 Dec
Netty 原始碼分析之拆包器的奧祕
為什麼要粘包拆包 為什麼要粘包 首先你得了解一下TCP/IP協議,在使用者資料量非常小的情況下,極端情況下,一個位元組,該TCP資料包的有效載荷非常低,傳遞100位元組的資料,需要100次TCP傳送,100次ACK,在應用及時性要求不高的情況下,將這100個有效資料拼接成一個數據包,那會縮短到一個TCP資
Android原始碼分析之為什麼在onCreate() 和 onResume() 獲取不到 View 的寬高
轉載自:https://www.jianshu.com/p/d7ab114ac1f7 先來看一段很熟悉的程式碼,可能在最開始接觸安卓的時候,大部分人都寫過的一段程式碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個 View 的寬高資訊: 但是列印輸出後,我們會發
netty原始碼分析之服務端啟動
ServerBootstrap與Bootstrap分別是netty中服務端與客戶端的引導類,主要負責服務端與客戶端初始化、配置及啟動引導等工作,接下來我們就通過netty原始碼中的示例對ServerBootstrap與Bootstrap的原始碼進行一個簡單的分析。首先我們知道這兩個類都繼承自AbstractB
SNMP原始碼分析之(一)配置檔案部分
snmpd.conf想必不陌生。在程序啟動過程中會去讀取配置檔案中各個配置。其中幾個引數需要先知道是幹什麼的: token:配置檔案的每行的開頭,例如 group MyROGroup v1 readSec 這行token的引數是group。
【kubernetes/k8s原始碼分析】kubelet原始碼分析之cdvisor原始碼分析
資料流 UnsecuredDependencies -> run 1. cadvisor.New初始化 if kubeDeps.CAdvisorInterface == nil { imageFsInfoProvider := cadv
【kubernetes/k8s原始碼分析】kubelet原始碼分析之容器網路初始化原始碼分析
一. 網路基礎 1.1 網路名稱空間的操作 建立網路名稱空間: ip netns add 名稱空間內執行命令: ip netns exec 進入名稱空間: ip netns exec bash 1.2 bridge-nf-c
【kubernetes/k8s原始碼分析】kubelet原始碼分析之資源上報
0. 資料流 路徑: pkg/kubelet/kubelet.go Run函式() -> syncNodeStatus () -> registerWithAPIServer() ->
【kubernetes/k8s原始碼分析】kubelet原始碼分析之啟動容器
主要是呼叫runtime,這裡預設為docker 0. 資料流 NewMainKubelet(cmd/kubelet/app/server.go) -> NewKubeGenericRuntimeManager(pkg/kubelet/kuberuntime/kuberuntime
Android系統原始碼分析之-ContentProvider
距離上一次寫部落格已經半年多了,這半年發生了很多事情,也有了很多感觸,最主要是改變了忙碌了工作,更加重視身體的健康,為此也把工作地點從深圳這個一線城市換到了珠海,工作相對沒有那麼累,身體感覺也好了很多。所以在工作完成之餘,也有了更多的時間來自我學習和提高,後續會用更多時間來寫更多實用的東西,幫助我們理解
Vue 原始碼分析之proxy代理
Vue 原始碼分析之proxy代理 當我們在使用Vue進行資料設定時,通常初始化格式為: let data = { age: 12, name: 'yang' } // 例項化Vue物件 let vm = new Vue({ data })
Qt原始碼分析之事件分發器QEventDispatcherWin32
分析Qt原始碼一則想自己在開發學習中有積累,同時自己也一直有一種理念,使用她那麼就更深入的認識她。 如果有分析不正確的,還煩請各位看官指正。 事件分發器建立 在QCoreApplication建構函式中 if (!QCoreApplicationPrivate
lodash原始碼分析之isArguments
lodash原始碼分析之isArguments 有人命中註定要過平庸的生活,默默無聞,因為他們經歷了痛苦或不幸;有人卻故意這樣做,那是因為他們得到的幸福超過了他們的承受能力。 ——卡爾維諾《煙雲》 本文為讀 lodash 原始碼的第二十一篇,後續文章會更新到這個倉庫中,歡迎 star:poc
Netty原始碼分析之LengthFieldBasedFrameDecoder
拆包的原理 關於拆包原理的上一篇博文 netty原始碼分析之拆包器的奧祕 中已詳細闡述,這裡簡單總結下:netty的拆包過程和自己寫手工拆包並沒有什麼不同,都是將位元組累加到一個容器裡面,判斷當前累加的位元組資料是否達到了一個包的大小,達到一個包大小就拆開,進而傳遞到上層業務解碼handler 之所以ne
illuminate/routing 原始碼分析之註冊路由
我們知道,在 Laravel 世界裡,外界傳進來一個 Request 時,會被 Kernel 處理並返回給外界一個 Response。Kernel 在處理 Request 時,會呼叫 illuminate/routing 包提供的路由功能,來根據當前的 Request,轉發到對應的執行邏輯(執行邏輯的形式可以
Uboot啟動過程原始碼分析之第二階段
UBoot的最終目標是啟動核心 1.從Flash中讀出核心 2.啟動核心 通過呼叫lib_arm/board.c中的start_armboot函式進入uboot第二階段 第二階段總結圖 typedef struct global_data { bd_t *bd; unsigned
Uboot啟動過程原始碼分析之第一階段(硬體相關)
從上一個部落格知道uboot的入口點在 cpu/arm920t/start.s 開啟cpu/arm920t/start.s 跳轉到reset reset: /* * set the cpu to SVC32 mode// CUP設定為管理模式 */ mrs r0,cps
Java集合原始碼分析之LikedList
一、LinkedList結構 LinkedList是一種可以在任何位置進行高效地插入和移除操作的有序序列,它是基於雙向連結串列實現的。 LinkedList 是一個繼承於AbstractSequentialList的雙向連結串列。它也可以被當作堆疊、佇列或雙端佇列進行操作。