MongoDB 3.x驱动程序 Android兼容性

4

我正在开发一款Android应用程序,使用Java MongoDB驱动程序3.0.3连接到每个MongoDB实例。

连接通用实例的连接器代码如下:

try{
    MongoCredential credential = MongoCredential.createCredential(user, dbname, pass.toCharArray());
    MongoClient mongoClient = new MongoClient( new ServerAddress(server , port ), Arrays.asList(credential));
    MongoDatabase db = mongoClient.getDatabase(dbname);
    System.out.println("Connect to database successfully ");
    Iterator i= mongoClient.listDatabaseNames().iterator();
    while (i.hasNext()){
        Log.d("DATABASE", (String) i.next());
    }
    Iterator ic= db.listCollectionNames().iterator();
    while (ic.hasNext()){
        Log.d("COLLECTION", (String) ic.next());
    }
}catch(Exception e){
    System.err.println( e.getClass().getName() + ": " + e.getMessage() );
}

在运行时,我遇到了一些错误:

09-11 19:13:50.898 7418-7418/it.mysite.mongodbviewer W/org.bson.ObjectId﹕ 无法从JMX获取进程标识符,改用随机数代替 java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;

com.mongodb.MongoException: java.lang.NoClassDefFoundError: com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient

这会导致以下循环:

I/art﹕ Rejecting re-init on previously-failed class java.lang.Class

有没有解决方案?如果这是问题的中心,那么 java/lang/management/ManagementFactory 在 Android 中似乎不存在,如何解决?

提前感谢,Matteo

附:完整日志如下:

09-11 19:13:50.898    7418-7418/it.mysite.mongodbviewer W/org.bson.ObjectId﹕ Failed to get process identifier from JMX, using random number instead
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;
at org.bson.types.ObjectId.createProcessIdentifier(ObjectId.java:502)
at org.bson.types.ObjectId.<clinit>(ObjectId.java:460)
at com.mongodb.connection.ClusterId.<init>(ClusterId.java:47)
at com.mongodb.connection.DefaultClusterFactory.create(DefaultClusterFactory.java:40)
at com.mongodb.Mongo.createCluster(Mongo.java:660)
at com.mongodb.Mongo.createCluster(Mongo.java:646)
at com.mongodb.Mongo.<init>(Mongo.java:275)
at com.mongodb.MongoClient.<init>(MongoClient.java:184)
at com.mongodb.MongoClient.<init>(MongoClient.java:160)
at it.mysite.mongodbmanager.data.MongoDBDriver.connect(MongoDBDriver.java:102)
at it.mysite.mongodbmanager.fragments.MongoDBAccountDetailFragment.onClick(MongoDBAccountDetailFragment.java:101)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19865)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
 Caused by: java.lang.ClassNotFoundException: Didn't find class "java.lang.management.ManagementFactory" on path: DexPathList[[zip file "/data/app/it.mysite.mongodbviewer-2/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
    at org.bson.types.ObjectId.createProcessIdentifier(ObjectId.java:502)
    at org.bson.types.ObjectId.<clinit>(ObjectId.java:460)
    at com.mongodb.connection.ClusterId.<init>(ClusterId.java:47)
    at com.mongodb.connection.DefaultClusterFactory.create(DefaultClusterFactory.java:40)
    at com.mongodb.Mongo.createCluster(Mongo.java:660)
    at com.mongodb.Mongo.createCluster(Mongo.java:646)
    at com.mongodb.Mongo.<init>(Mongo.java:275)
    at com.mongodb.MongoClient.<init>(MongoClient.java:184)
    at com.mongodb.MongoClient.<init>(MongoClient.java:160)
    at it.mysite.mongodbmanager.data.MongoDBDriver.connect(MongoDBDriver.java:102)
    at it.mysite.mongodbmanager.fragments.MongoDBAccountDetailFragment.onClick(MongoDBAccountDetailFragment.java:101)
    at android.view.View.performClick(View.java:4780)
    at android.view.View$PerformClick.run(View.java:19865)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5254)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Suppressed: java.lang.ClassNotFoundException: java.lang.management.ManagementFactory
at java.lang.Class.classForName(Native Method)
at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
... 22 more
 Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack available
09-11 19:13:50.901    7418-7418/it.mysite.mongodbviewer I/cluster﹕ Cluster created with settings {hosts=[192.168.1.74:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
09-11 19:13:50.918    7418-7418/it.mysite.mongodbviewer I/System.out﹕ Connect to database successfully
09-11 19:13:50.924    7418-7418/it.mysite.mongodbviewer I/cluster﹕ No server chosen by ReadPreferenceServerSelector{readPreference=primary} from cluster description ClusterDescription{type=UNKNOWN, connectionMode=SINGLE, all=[ServerDescription{address=192.168.1.74:27017, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
09-11 19:13:50.954    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
09-11 19:13:50.954    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
09-11 19:13:50.955    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
09-11 19:13:50.956    7418-7487/it.mysite.mongodbviewer I/cluster﹕ Exception in monitor thread while connecting to server 192.168.1.74:27017
com.mongodb.MongoException: java.lang.NoClassDefFoundError: com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient
at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:125)
at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:127)
at java.lang.Thread.run(Thread.java:818)
 Caused by: java.lang.NoClassDefFoundError: com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient
at com.mongodb.connection.ScramSha1Authenticator.createSaslClient(ScramSha1Authenticator.java:61)
at com.mongodb.connection.SaslAuthenticator.authenticate(SaslAuthenticator.java:42)
at com.mongodb.connection.DefaultAuthenticator.authenticate(DefaultAuthenticator.java:32)
at com.mongodb.connection.InternalStreamConnectionInitializer.authenticateAll(InternalStreamConnectionInitializer.java:99)
at com.mongodb.connection.InternalStreamConnectionInitializer.initialize(InternalStreamConnectionInitializer.java:44)
at com.mongodb.connection.InternalStreamConnection.open(InternalStreamConnection.java:115)
at com.mongodb.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:127)
at java.lang.Thread.run(Thread.java:818)
09-11 19:13:51.479    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
09-11 19:13:52.106    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
09-11 19:13:52.637    7418-7487/it.mysite.mongodbviewer I/art﹕ Rejecting re-init on previously-failed class java.lang.Class<com.mongodb.connection.ScramSha1Authenticator$ScramSha1SaslClient>
3个回答

7

如果有人想在Android上使用github.com/mongodb/mongo-java-driver,我有一个解决方案!

我从官方的github.com/mongodb/mongo-java-driver中进行了fork,并整合了github.com/koterpillar/android-sasl的类来修复Android上的javax.security.sasl问题。

重要提示:因为Android上不存在java.nio.channels.AsynchronousSocketChannel并且没有任何移植,所以没有移植driver-async。

结果是获得了一个可以在Android(Java)上工作的同步MongoDB Android驱动程序,您可以从https://github.com/matfur92/mongo-java-driver下载。

最后更新时间(2015年11月5日):3.2.0-SNAPSHOT --> https://github.com/matfur92/mongo-java-driver/blob/gh-pages/JARs/mongo-java-driver-3.2.0-SNAPSHOT.jar?raw=true


当我尝试使用明文认证器时,Sasl.createSaslClient返回null,有什么想法吗? - Greg Ennis
很抱歉,我无法处理这种身份验证。我可能错过了库https://github.com/koterpillar/android-sasl的特定实现。 问题可能是: java.security.Security.addProvider(new gnu.javax.crypto.jce.GnuSasl()); 在SASL身份验证方法生效之前。``` 我必须调查这个可能的疏忽。任何想法/帮助都受欢迎 ;) 受影响的文件包括: - GSSAPIAuthenticator.java - PlainAuthenticator.java - SaslAuthenticator.java(在authenticate和authenticateAsync方法中) - ScramSha1Authenticator.java - matfur92
现在你的分支版本和MongoAndroid.init()一起工作了吗? 我认为MongoAndroid.init()必须从驱动程序中使用,而不是从客户端中使用。 - matfur92
它能工作,但我不知道我原本遇到的问题是缺少addProvider调用还是其他问题(这就是MongoAndroid.init所做的全部)。 - Greg Ennis
我已经在SaslAuthenticator.java中添加了java.security.Security.addProvider(new gnu.javax.crypto.jce.GnuSasl());并重新编译了JAR。如果您想要@Greg,您能否告诉我我的版本是否可行?在我的旧测试中,没有这一行也可以工作。您可以从https://github.com/matfur92/mongo-java-driver/blob/gh-pages/JARs/mongo-java-driver-3.2.0-SNAPSHOT.jar?raw=true下载它。 - matfur92
显示剩余2条评论

7

在我看来,ManagementFactory是一个误导,因为驱动程序捕获了该异常并回退到使用随机数。实际的问题似乎在于驱动程序需要对SCRAM-SHA-1进行身份验证,其实现导入以下类:

ScramSha1Authenticator

MongoCredential

MongoCredentialHelper

ServerAddress

这些类可能需要正确的配置或导入才能解决身份验证问题。

import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;

我怀疑这些在Android平台上不可用,因为它不是Java Runtime Environment的完整实现。
你可以尝试使用MongoDB 2.6运行,该驱动程序的身份验证实现仅依赖于java.security.MessageDigest。
另一件需要考虑的事情是在移动应用程序和MongoDB之间放置一个REST服务,负责代理与数据库的所有交互。 REST应用程序可以在MongoDB驱动程序完全可用的环境中运行。

2

绕过REST服务不是解决问题的方法,我必须直接连接到mongodb实例。

编辑:

使用此移植:https://github.com/matfur92/mongo-java-driver,同步mongo-java-driver已在Android上正确移植。

没有移植driver-async,因为在Android上不存在java.nio.channels.AsynchronousSocketChannel,也没有任何移植。

如果有人有解决办法使driver-async工作,请告诉我。问题出在java.nio.channels.AsynchronousSocketChannel在Android上不存在。


请参考:http://programmers.stackexchange.com/questions/170463/why-to-use-web-services-instead-of-direct-access-to-a-relational-database-for-an,了解为什么直接连接数据库对于移动应用程序是一种不好的模式。 - Ross
我同意,但这不是我的选择。 - matfur92

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接