Prevent class loading for lazy native modules

Summary:
When native modules use `LazyReactPackage`, the modules themselves are not initialized. However, they still use the class names, causing the classes to load. This diff removes the need to perform any class loads. Any properties of the classes that are required are now populated in the `ReactModuleInfo` of that class.

Note that this diff itself does not prevent class loading since any references to `*.class` in `LazyReactpackage` needs to be removed in a consequent diff

Reviewed By: achen1

Differential Revision: D8950025

fbshipit-source-id: 80ddf7e1f33bf2af0db1bd262069795de77ec611
This commit is contained in:
Ram N
2018-07-27 23:30:39 -07:00
committed by Facebook Github Bot
parent d891ee1dee
commit c8e000b19a
9 changed files with 94 additions and 44 deletions

View File

@@ -74,7 +74,7 @@ public abstract class LazyReactPackage implements ReactPackage {
.flush();
ReactMarker.logMarker(
ReactMarkerConstants.CREATE_MODULE_START,
holder.getType().getSimpleName());
holder.getClassName());
try {
nativeModule = holder.getProvider().get();
} finally {

View File

@@ -32,8 +32,8 @@ public class NativeModuleRegistryBuilder {
private final ReactInstanceManager mReactInstanceManager;
private final boolean mLazyNativeModulesEnabled;
private final Map<Class<? extends NativeModule>, ModuleHolder> mModules = new HashMap<>();
private final Map<String, Class<? extends NativeModule>> namesToType = new HashMap<>();
private final Map<String, ModuleHolder> mModules = new HashMap<>();
private final Map<String,String> namesToType = new HashMap<>();
public NativeModuleRegistryBuilder(
ReactApplicationContext reactApplicationContext,
@@ -53,16 +53,10 @@ public class NativeModuleRegistryBuilder {
lazyReactPackage.getReactModuleInfoProvider().getReactModuleInfos();
for (ModuleSpec moduleSpec : moduleSpecs) {
Class<? extends NativeModule> type = moduleSpec.getType();
ReactModuleInfo reactModuleInfo = reactModuleInfoMap.get(type.getCanonicalName());
String className = moduleSpec.getClassName();
ReactModuleInfo reactModuleInfo = reactModuleInfoMap.get(className);
ModuleHolder moduleHolder;
if (reactModuleInfo == null) {
if (BaseJavaModule.class.isAssignableFrom(type)) {
throw new IllegalStateException(
"Native Java module "
+ type.getSimpleName()
+ " should be annotated with @ReactModule and added to a @ReactModuleList.");
}
NativeModule module;
ReactMarker.logMarker(
ReactMarkerConstants.CREATE_MODULE_START,
@@ -78,7 +72,7 @@ public class NativeModuleRegistryBuilder {
}
String name = moduleHolder.getName();
putModuleTypeAndHolderToModuleMaps(type, name, moduleHolder);
putModuleTypeAndHolderToModuleMaps(className, name, moduleHolder);
}
} else {
FLog.d(
@@ -103,20 +97,20 @@ public class NativeModuleRegistryBuilder {
public void addNativeModule(NativeModule nativeModule) {
String name = nativeModule.getName();
Class<? extends NativeModule> type = nativeModule.getClass();
putModuleTypeAndHolderToModuleMaps(type, name, new ModuleHolder(nativeModule));
putModuleTypeAndHolderToModuleMaps(type.getName(), name, new ModuleHolder(nativeModule));
}
private void putModuleTypeAndHolderToModuleMaps(
Class<? extends NativeModule> type, String underName, ModuleHolder moduleHolder)
String className, String underName, ModuleHolder moduleHolder)
throws IllegalStateException {
if (namesToType.containsKey(underName)) {
Class<? extends NativeModule> existingNativeModule = namesToType.get(underName);
String existingNativeModule = namesToType.get(underName);
if (!moduleHolder.getCanOverrideExistingModule()) {
throw new IllegalStateException(
"Native module "
+ type.getSimpleName()
+ className
+ " tried to override "
+ existingNativeModule.getSimpleName()
+ existingNativeModule
+ " for module name "
+ underName
+ ". Check the getPackages() method in MainApplication.java, it might be "
@@ -127,14 +121,14 @@ public class NativeModuleRegistryBuilder {
mModules.remove(existingNativeModule);
}
namesToType.put(underName, type);
mModules.put(type, moduleHolder);
namesToType.put(underName, className);
mModules.put(className, moduleHolder);
}
public NativeModuleRegistry build() {
ArrayList<ModuleHolder> batchCompleteListenerModules = new ArrayList<>();
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
if (OnBatchCompleteListener.class.isAssignableFrom(entry.getKey())) {
for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
if (entry.getValue().hasOnBatchCompleteListener()) {
batchCompleteListenerModules.add(entry.getValue());
}
}

View File

@@ -49,14 +49,14 @@ public class JavaModuleWrapper {
private final JSInstance mJSInstance;
private final ModuleHolder mModuleHolder;
private final Class<? extends NativeModule> mModuleClass;
private final String mClassName;
private final ArrayList<NativeModule.NativeMethod> mMethods;
private final ArrayList<MethodDescriptor> mDescs;
public JavaModuleWrapper(JSInstance jsInstance, Class<? extends NativeModule> moduleClass, ModuleHolder moduleHolder) {
public JavaModuleWrapper(JSInstance jsInstance, String className, ModuleHolder moduleHolder) {
mJSInstance = jsInstance;
mModuleHolder = moduleHolder;
mModuleClass = moduleClass;
mClassName = className;
mMethods = new ArrayList<>();
mDescs = new ArrayList();
}
@@ -76,9 +76,9 @@ public class JavaModuleWrapper {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "findMethods");
Set<String> methodNames = new HashSet<>();
Class<? extends NativeModule> classForMethods = mModuleClass;
Class<? extends NativeModule> classForMethods = mModuleHolder.getModule().getClass();
Class<? extends NativeModule> superClass =
(Class<? extends NativeModule>) mModuleClass.getSuperclass();
(Class<? extends NativeModule>) classForMethods.getSuperclass();
if (ReactModuleWithSpec.class.isAssignableFrom(superClass)) {
// For java module that is based on generated flow-type spec, inspect the
// spec abstract class instead, which is the super class of the given java

View File

@@ -40,6 +40,8 @@ public class ModuleHolder {
private final String mName;
private final boolean mCanOverrideExistingModule;
private final boolean mHasConstants;
private final boolean mIsCxxModule;
private final boolean mHasOnBatchCompleteListener;
private @Nullable Provider<? extends NativeModule> mProvider;
// Outside of the constructur, these should only be checked or set when synchronized on this
@@ -55,6 +57,8 @@ public class ModuleHolder {
mCanOverrideExistingModule = moduleInfo.canOverrideExistingModule();
mHasConstants = moduleInfo.hasConstants();
mProvider = provider;
mHasOnBatchCompleteListener = moduleInfo.hasOnBatchCompleteListener();
mIsCxxModule = moduleInfo.isCxxModule();
if (moduleInfo.needsEagerInit()) {
mModule = create();
}
@@ -64,6 +68,8 @@ public class ModuleHolder {
mName = nativeModule.getName();
mCanOverrideExistingModule = nativeModule.canOverrideExistingModule();
mHasConstants = true;
mIsCxxModule = CxxModuleWrapper.class.isAssignableFrom(nativeModule.getClass());
mHasOnBatchCompleteListener = OnBatchCompleteListener.class.isAssignableFrom(nativeModule.getClass());
mModule = nativeModule;
PrinterHolder.getPrinter()
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
@@ -113,6 +119,10 @@ public class ModuleHolder {
return mHasConstants;
}
public boolean isCxxModule() {return mIsCxxModule; }
public boolean hasOnBatchCompleteListener() {return mHasOnBatchCompleteListener; }
@DoNotStrip
public NativeModule getModule() {
NativeModule module;

View File

@@ -25,6 +25,7 @@ public class ModuleSpec {
private final @Nullable Class<? extends NativeModule> mType;
private final Provider<? extends NativeModule> mProvider;
private final String mClassName;
/**
* Simple spec for modules with a default constructor.
@@ -66,19 +67,28 @@ public class ModuleSpec {
public static ModuleSpec nativeModuleSpec(
Class<? extends NativeModule> type, Provider<? extends NativeModule> provider) {
return new ModuleSpec(type, provider);
return new ModuleSpec(provider, type.getName());
}
private ModuleSpec(
@Nullable Class<? extends NativeModule> type, Provider<? extends NativeModule> provider) {
mType = type;
mProvider = provider;
mClassName = type == null ? null : type.getName();
}
public ModuleSpec(Provider<? extends NativeModule> provider, String name) {
mType = null;
mProvider = provider;
mClassName = name;
}
public @Nullable Class<? extends NativeModule> getType() {
return mType;
}
public String getClassName(){return mClassName;}
public Provider<? extends NativeModule> getProvider() {
return mProvider;
}

View File

@@ -22,12 +22,12 @@ import com.facebook.systrace.Systrace;
public class NativeModuleRegistry {
private final ReactApplicationContext mReactApplicationContext;
private final Map<Class<? extends NativeModule>, ModuleHolder> mModules;
private final Map<String, ModuleHolder> mModules;
private final ArrayList<ModuleHolder> mBatchCompleteListenerModules;
public NativeModuleRegistry(
ReactApplicationContext reactApplicationContext,
Map<Class<? extends NativeModule>, ModuleHolder> modules,
Map<String, ModuleHolder> modules,
ArrayList<ModuleHolder> batchCompleteListenerModules) {
mReactApplicationContext = reactApplicationContext;
mModules = modules;
@@ -37,7 +37,7 @@ public class NativeModuleRegistry {
/**
* Private getters for combining NativeModuleRegistrys
*/
private Map<Class<? extends NativeModule>, ModuleHolder> getModuleMap() {
private Map<String, ModuleHolder> getModuleMap() {
return mModules;
}
@@ -52,9 +52,10 @@ public class NativeModuleRegistry {
/* package */ Collection<JavaModuleWrapper> getJavaModules(
JSInstance jsInstance) {
ArrayList<JavaModuleWrapper> javaModules = new ArrayList<>();
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
Class<? extends NativeModule> type = entry.getKey();
if (!CxxModuleWrapperBase.class.isAssignableFrom(type)) {
for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
String type = entry.getKey();
if (!entry.getValue().isCxxModule()) {
//if (!CxxModuleWrapperBase.class.isAssignableFrom(entry.getValue().getModule().getClass())) {
javaModules.add(new JavaModuleWrapper(jsInstance, type, entry.getValue()));
}
}
@@ -63,9 +64,8 @@ public class NativeModuleRegistry {
/* package */ Collection<ModuleHolder> getCxxModules() {
ArrayList<ModuleHolder> cxxModules = new ArrayList<>();
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : mModules.entrySet()) {
Class<?> type = entry.getKey();
if (CxxModuleWrapperBase.class.isAssignableFrom(type)) {
for (Map.Entry<String, ModuleHolder> entry : mModules.entrySet()) {
if (entry.getValue().isCxxModule()) {
cxxModules.add(entry.getValue());
}
}
@@ -80,11 +80,11 @@ public class NativeModuleRegistry {
Assertions.assertCondition(mReactApplicationContext.equals(newRegister.getReactApplicationContext()),
"Extending native modules with non-matching application contexts.");
Map<Class<? extends NativeModule>, ModuleHolder> newModules = newRegister.getModuleMap();
Map<String, ModuleHolder> newModules = newRegister.getModuleMap();
ArrayList<ModuleHolder> batchCompleteListeners = newRegister.getBatchCompleteListenerModules();
for (Map.Entry<Class<? extends NativeModule>, ModuleHolder> entry : newModules.entrySet()) {
Class<? extends NativeModule> key = entry.getKey();
for (Map.Entry<String, ModuleHolder> entry : newModules.entrySet()) {
String key = entry.getKey();
if (!mModules.containsKey(key)) {
ModuleHolder value = entry.getValue();
if (batchCompleteListeners.contains(value)) {
@@ -137,12 +137,12 @@ public class NativeModuleRegistry {
}
public <T extends NativeModule> boolean hasModule(Class<T> moduleInterface) {
return mModules.containsKey(moduleInterface);
return mModules.containsKey(moduleInterface.getName());
}
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(
mModules.get(moduleInterface), moduleInterface.getSimpleName()).getModule();
mModules.get(moduleInterface.getName()), moduleInterface.getSimpleName()).getModule();
}
public List<NativeModule> getAllModules() {

View File

@@ -15,16 +15,22 @@ public class ReactModuleInfo {
private final boolean mCanOverrideExistingModule;
private final boolean mNeedsEagerInit;
private final boolean mHasConstants;
private final boolean mIsCxxModule;
private final boolean mHasOnBatchCompleteListener;
public ReactModuleInfo(
String name,
boolean canOverrideExistingModule,
boolean needsEagerInit,
boolean hasConstants) {
boolean hasConstants,
boolean isCxxModule,
boolean hasOnBatchCompleteListener) {
mName = name;
mCanOverrideExistingModule = canOverrideExistingModule;
mNeedsEagerInit = needsEagerInit;
mHasConstants = hasConstants;
mIsCxxModule = isCxxModule;
mHasOnBatchCompleteListener = hasOnBatchCompleteListener;
}
public String name() {
@@ -42,4 +48,10 @@ public class ReactModuleInfo {
public boolean hasConstants() {
return mHasConstants;
}
public boolean isCxxModule() {return mIsCxxModule; }
public boolean hasOnBatchCompleteListener() {
return mHasOnBatchCompleteListener;
}
}

View File

@@ -5,6 +5,8 @@
package com.facebook.react.module.processing;
import com.facebook.react.bridge.CxxModuleWrapper;
import com.facebook.react.bridge.OnBatchCompleteListener;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
@@ -21,6 +23,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.io.IOException;
import java.util.ArrayList;
@@ -72,6 +75,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
private Elements mElements;
@SuppressFieldNotInitialized
private Messager mMessager;
private Types mTypes;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
@@ -80,6 +84,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
mFiler = processingEnv.getFiler();
mElements = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
mTypes = processingEnv.getTypeUtils();
}
@Override
@@ -154,6 +159,9 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
} else {
builder.addStatement("$T map = new $T()", MAP_TYPE, INSTANTIATED_MAP_TYPE);
TypeMirror cxxModuleWrapperTypeMirror = mElements.getTypeElement(CxxModuleWrapper.class.getName()).asType();
TypeMirror onBatchCompleteListenerTypeMirror = mElements.getTypeElement(OnBatchCompleteListener.class.getName()).asType();
for (String nativeModule : nativeModules) {
String keyString = nativeModule;
@@ -163,6 +171,7 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
keyString + " not found by ReactModuleSpecProcessor. " +
"Did you misspell the module?");
}
ReactModule reactModule = typeElement.getAnnotation(ReactModule.class);
if (reactModule == null) {
throw new ReactModuleSpecException(
@@ -182,12 +191,27 @@ public class ReactModuleSpecProcessor extends AbstractProcessor {
name -> name.contentEquals("getConstants") || name.contentEquals("getTypedExportedConstants"));
}
boolean isCxxModule = mTypes.isAssignable(typeElement.asType(), cxxModuleWrapperTypeMirror);
boolean hasOnBatchCompleteListener = false;
try {
hasOnBatchCompleteListener = mTypes.isAssignable(typeElement.asType(), onBatchCompleteListenerTypeMirror);
} catch (RuntimeException e) {
// This is SUPER ugly, but we need to do this, especially for AsyncStorageModule which implements ModuleDataCleaner
// In the case of that specific class, we get the exception
// com.sun.tools.javac.code.Symbol$CompletionFailure: class file for ModuleDataCleaner not found.
// The exception is caused because the class is not loaded the first time. However, catching it and
// running it again the second time loads the class and does what the following statement originally intended
hasOnBatchCompleteListener = mTypes.isAssignable(typeElement.asType(), onBatchCompleteListenerTypeMirror);
}
String valueString = new StringBuilder()
.append("new ReactModuleInfo(")
.append("\"").append(reactModule.name()).append("\"").append(", ")
.append(reactModule.canOverrideExistingModule()).append(", ")
.append(reactModule.needsEagerInit()).append(", ")
.append(hasConstants)
.append(hasConstants).append(", ")
.append(isCxxModule).append(", ")
.append(hasOnBatchCompleteListener)
.append(")")
.toString();