MongoDB BSON编解码器在编码对象时不被使用 [英] MongoDB BSON codec not being used while encoding object
问题描述
找不到类java.time.LocalDate的编解码器。
我已经为 LocalDate
对象。
我试图存储的对象 DutyBlock
具有这些成员变量: p>
public class DutyBlock {
private LocalDate startDate;
private LocalDate endDate; //包含
private int blockLength;
private double pointValue;
private ArrayList< Ra>分配;
}
我写了以下编解码器来编码 DutyBlock
数据库中的对象:
public class DutyBlockCodec implements Codec&DutyBlock> {
@Override
public void encode(BsonWriter writer,DutyBlock t,EncoderContext ec){
Document document = new Document();
document.append(startDate,t.getStartDate());
document.append(endDate,t.getEndDate());
document.append(blockLength,t.getBlockLength());
document.append(pointValue,t.getPointValue());
document.append(assigned,t.getRasOnDuty());
writer.writeString(document.toJson()); //第27行错误信息。
}
@Override
public Class&DutyBlock> getEncoderClass(){
return DutyBlock.class;
}
@Override
public DutyBlock decode(BsonReader reader,DecoderContext dc){
String json = reader.readString();
返回新的DutyBlock(Document.parse(json));
}
}
由于MongoDB目前不支持 java.time.LocalDate class
,我已经编写了以下编解码器来对数据库中的 LocalDate
对象进行编码: / p>
public class LocalDateCodec implements Codec< LocalDate> {
@Override
public void encode(BsonWriter writer,LocalDate t,EncoderContext ec){
writer.writeString(t.toString());
}
@Override
public Class< LocalDate> getEncoderClass(){
return LocalDate.class;
}
@Override
public LocalDate decode(BsonReader reader,DecoderContext dc){
String date = reader.readString();
return LocalDate.parse(date);
}
}
我添加了两个 Codec
(以及 Ra
类型中的一个)到 CodecRegistry
MongoClient级别,同时实例化MongoClient。
public class DutyScheduleDB {
private MongoClient mongoClient;
private MongoDatabase db;
public DutyScheduleDB(){
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new LocalDateCodec(),new DutyBlockCodec(),new RaCodec ),
MongoClient.getDefaultCodecRegistry());
MongoClientOptions options = MongoClientOptions.builder()
.codecRegistry(codecRegistry).build();
mongoClient = new MongoClient(new ServerAddress(),options);
db = mongoClient.getDatabase(DutySchedulerDB);
}
。 (更多代码未显示)
。
。
}
我尝试存储一个 ArrayList
DutyBlock
对象作为MongoDB数据库中 org.bson.Document
的一部分。
public void storeScheduledCalendar(String id,
String calendarName,
ArrayList&DutyBlock&cal; cal){
/ /访问预定日历的收集。
MongoCollection collection = db.getCollection(ScheduledCalendars);
//查询参数是uuid + calendarName。
文档doc = new Document(name,id + calendarName);
doc.append(dutyBlocks,cal);
//将文档插入到集合中。
collection.insertOne(doc); //行59在错误信息。
}
但是,我遇到这个错误信息:
线程main中的异常org.bson.codecs.configuration.CodecConfigurationException:找不到类java.time.LocalDate的编解码器。
在org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46)
在org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63)
在org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
在org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:174)
在org.bson.codecs .documentCodec.writeMap(DocumentCodec.java:189)
在org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
在org.bson.codecs.DocumentCodec.encode(DocumentCodec.java :$)
在org.bson.Document.toJson(Document.java:294)
在org.bson.Document.toJson(Document.java:268)
在org.bson。 Document.toJson(Document.java:255)
在SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:27)
在SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:16)
在org .bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
在org.bson.codecs.DocumentCodec.writeValue(Docume ntCodec.java:175)
在org.bson.codecs.DocumentCodec.writeIterable(DocumentCodec.java:197)
在org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:170)
在org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:189)
在org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
在org.bson.codecs .cn文件编号:29)
在com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:99)
在com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:43)
在com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:112)
在com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:35)
在com.mongodb.connection.Request Message.encode(RequestMessage.java:132)
在com.mongodb.connection.BaseWriteCommandMessage.encode(BaseWriteCommandMessage.java:89)
在com.mongodb.connection.WriteCommandProtocol.sendMessage(WriteCommandProtocol.java: 170)
在com.mongodb.connection.WriteCommandProtocol.execute(WriteCommandProtocol.java:73)
在com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:66)
在com .mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:37)
在com.mongodb.connection.DefaultServer $ DefaultServerProtocolExecutor.execute(DefaultServer.java:155)
在com.mongodb.connection.DefaultServerConnection在.com中java:416)
在com.mongodb.operation.MixedBulkWriteOperation $运行$ R unExecutor.execute(MixedBulkWriteOperation.java:604)
在com.mongodb.operation.MixedBulkWriteOperation $ Run.execute(MixedBulkWriteOperation.java:363)
在com.mongodb.operation.MixedBulkWriteOperation $ 1.call(MixedBulkWriteOperation .java:148)
at com.mongodb.operation.MixedBulkWriteOperation $ 1.call(MixedBulkWriteOperation.java:141)
at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:186)
at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:177)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:141)
at com.mongodb.operation .MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:72)
在com.mongodb.Mongo.execute(Mongo.java:747)
在com.mongodb.Mongo $ 2.execute(Mongo.java:730)
在com.mongodb.MongoCollectionImpl.executeSingleWriteRequest(MongoCollectionImpl.java:482)
在com.mongodb.MongoCollectionImpl.insertOne(MongoCollectionImpl.java:277)
at (DutyScheduleDB.java:10)
b 虽然尝试编码 DutyBlock
对象时,似乎没有使用我的 LocalDate
的编解码器,我已经验证了我试图存储 org.bson.Document
的集合确实包含 LocalDateCodec
通过
System.out.println(collection.getCodecRegistry()。get(LocalDate.class));
任何人都可以提供一些洞察为什么会发生这种情况?
解决方案经过几天的研究,我已经找出了一个解决方案。
DutyBlockCodec
取决于 LocalDateCodec
(我创建的)以进行编码/解码。通过将两个编解码器添加到同一个编解码器注册表中,这种依赖性不能满足要求。解决方案是传递一个包含 DutyBlockCodec
依赖的编解码器的 CodecRegistry
对象(例如一个 CodecRegistry
在其中包含 LocalDateCodec
)到 DutyBlockCodec
的构造函数,它被存储作为成员变量。为了使用 LocalDateCodec
进行编码,我使用 EncoderContext.encodeWithChildContext()
方法,传入编解码器,作家和要编码的元素。另外,我写个别字段,而不是写一个文档
作为一个 String
(如我的原始代码)。因此, DutyBlock
编解码器最终看起来像这样:
public class DutyBlockCodec实现编解码器< DutyBlock> CodecRegistry codecRegistry {
private final
public DutyBlockCodec(final CodecRegistry codecRegistry){
this.codecRegistry = codecRegistry;
}
@Override
public void encode(BsonWriter writer,DutyBlock t,EncoderContext ec){
writer.writeStartDocument();
Codec dateCodec = codecRegistry.get(LocalDate.class);
writer.writeName(startDate);
ec.encodeWithChildContext(dateCodec,writer,t.getStartDate());
writer.writeName(endDate);
ec.encodeWithChildContext(dateCodec,writer,t.getEndDate());
writer.writeName(blockLength);
writer.writeInt32(t.getBlockLength());
writer.writeName(pointValue);
writer.writeDouble(t.getPointValue());
//写入RA的ArrayList
writer.writeName(assigned);
writer.writeStartArray(); (Ra ra:t.getRasOnDuty())
{
Codec raCodec = codecRegistry.get(Ra.class);
ec.encodeWithChildContext(raCodec,writer,ra);
}
writer.writeEndArray();
writer.writeEndDocument();
}
@Override
public Class&DutyBlock> getEncoderClass(){
return DutyBlock.class;
}
@Override
public DutyBlock decode(BsonReader reader,DecoderContext dc){
reader.readStartDocument();
编解码器< LocalDate> dateCodec = codecRegistry.get(LocalDate.class);
reader.readName();
LocalDate startDate = dateCodec.decode(reader,dc);
reader.readName();
LocalDate endDate = dateCodec.decode(reader,dc);
reader.readName();
int blockLength = reader.readInt32();
reader.readName();
double pointValue = reader.readDouble();
//读取RA的ArrayList
reader.readName();
Codec< Ra> raCodec = codecRegistry.get(Ra.class);
ArrayList< Ra> rasOnDuty = new ArrayList<();
reader.readStartArray();
while(reader.readBsonType()!= BsonType.END_OF_DOCUMENT){
rasOnDuty.add(raCodec.decode(reader,dc));
}
reader.readEndArray();
reader.readEndDocument();
返回新的DutyBlock(startDate,endDate,blockLength,pointValue,rasOnDuty);
}
}
DutyBlockCodec
取决于另一个编解码器,因此需要在其构造函数中传递一个 CodecRegistry
。虽然我相信可以使用 LocalDateCodec
创建一个 CodecRegistry
,然后将其作为参数传递给 DutyBlockCodec
的构造函数,然后创建另一个 CodecRegistry
,其中包含 LocalDateCodec
和 DutyBlockCodec
,这很令人困惑,MongoDB提供了一个功能, CodecProvider
来促进这个过程。
使用 CodecProvider
界面,我写了一个 DutyBlockCodecProvider
public class DutyBlockCodecProvider implements CodecProvider {
@Override
public< T>编解码器< T> get(Class< T> type,CodecRegistry cr){
if(type == DutyBlock.class){
return(Codec )new DutyBlockCodec(cr);
}
返回null;
}
}
我添加了这些 CodecProviders
到MongoDB客户端使用 CodecRegistries.fromProviders()
方法。
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new LocalDateCodec()),
CodecRegistries.fromProviders(
new RaCodecProvider(),
new DutyBlockCodecProvider (),
new ScheduledDutyCodecProvider()),
MongoClient.getDefaultCodecRegistry());
MongoClientOptions options = MongoClientOptions.builder()
.codecRegistry(codecRegistry).build();
mongoClient = new MongoClient(new ServerAddress(),options);
db = mongoClient.getDatabase(DutySchedulerDB);
我可以在 https://github.com/desrepair/DutyScheduler
我可以回答人们可能遇到的任何问题。
I'm attempting to store an object in a MongoDB database (using MongoDB 3.0.2) and am getting a CodecConfigurationException
when attempting to encode the object with error message
Can't find a codec for class java.time.LocalDate.
I have written and included a codec for the LocalDate
objects. Details follow.
The object, DutyBlock
, that I'm attempting to store has these member variables:
public class DutyBlock {
private LocalDate startDate;
private LocalDate endDate; //Inclusive
private int blockLength;
private double pointValue;
private ArrayList<Ra> assigned;
}
I wrote the following codec to encode the DutyBlock
objects within the database:
public class DutyBlockCodec implements Codec<DutyBlock> {
@Override
public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
Document document = new Document();
document.append("startDate", t.getStartDate());
document.append("endDate", t.getEndDate());
document.append("blockLength", t.getBlockLength());
document.append("pointValue", t.getPointValue());
document.append("assigned", t.getRasOnDuty());
writer.writeString(document.toJson()); //Line 27 in the error message.
}
@Override
public Class<DutyBlock> getEncoderClass() {
return DutyBlock.class;
}
@Override
public DutyBlock decode(BsonReader reader, DecoderContext dc) {
String json = reader.readString();
return new DutyBlock(Document.parse(json));
}
}
Since MongoDB currently does not support the java.time.LocalDate class
, I've written the following codec to encode the LocalDate
objects within the database:
public class LocalDateCodec implements Codec<LocalDate> {
@Override
public void encode(BsonWriter writer, LocalDate t, EncoderContext ec) {
writer.writeString(t.toString());
}
@Override
public Class<LocalDate> getEncoderClass() {
return LocalDate.class;
}
@Override
public LocalDate decode(BsonReader reader, DecoderContext dc) {
String date = reader.readString();
return LocalDate.parse(date);
}
}
I've added the two Codec
's (along with one for the Ra
type) to the CodecRegistry
at the MongoClient level while instantiating the MongoClient.
public class DutyScheduleDB {
private MongoClient mongoClient;
private MongoDatabase db;
public DutyScheduleDB() {
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new LocalDateCodec(), new DutyBlockCodec(), new RaCodec()),
MongoClient.getDefaultCodecRegistry());
MongoClientOptions options = MongoClientOptions.builder()
.codecRegistry(codecRegistry).build();
mongoClient = new MongoClient(new ServerAddress(), options);
db = mongoClient.getDatabase("DutySchedulerDB");
}
. (More code not shown)
.
.
}
I attempt to store an ArrayList
of DutyBlock
objects as part of a org.bson.Document
within the MongoDB database.
public void storeScheduledCalendar(String id,
String calendarName,
ArrayList<DutyBlock> cal) {
//Access collection of scheduled calendars.
MongoCollection collection = db.getCollection("ScheduledCalendars");
//Query parameter is uuid + calendarName.
Document doc = new Document("name", id + calendarName);
doc.append("dutyBlocks", cal);
//Insert doc to collection.
collection.insertOne(doc); //Line 59 in the error message.
}
However, I'm running into this error message:
Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.time.LocalDate.
at org.bson.codecs.configuration.CodecCache.getOrThrow(CodecCache.java:46)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:63)
at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:174)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.Document.toJson(Document.java:294)
at org.bson.Document.toJson(Document.java:268)
at org.bson.Document.toJson(Document.java:255)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:27)
at SchedulingHeuristic.DutyBlockCodec.encode(DutyBlockCodec.java:16)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:175)
at org.bson.codecs.DocumentCodec.writeIterable(DocumentCodec.java:197)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:170)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:189)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:131)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:99)
at com.mongodb.connection.InsertCommandMessage.writeTheWrites(InsertCommandMessage.java:43)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:112)
at com.mongodb.connection.BaseWriteCommandMessage.encodeMessageBody(BaseWriteCommandMessage.java:35)
at com.mongodb.connection.RequestMessage.encode(RequestMessage.java:132)
at com.mongodb.connection.BaseWriteCommandMessage.encode(BaseWriteCommandMessage.java:89)
at com.mongodb.connection.WriteCommandProtocol.sendMessage(WriteCommandProtocol.java:170)
at com.mongodb.connection.WriteCommandProtocol.execute(WriteCommandProtocol.java:73)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:66)
at com.mongodb.connection.InsertCommandProtocol.execute(InsertCommandProtocol.java:37)
at com.mongodb.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:155)
at com.mongodb.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:219)
at com.mongodb.connection.DefaultServerConnection.insertCommand(DefaultServerConnection.java:108)
at com.mongodb.operation.MixedBulkWriteOperation$Run$2.executeWriteCommandProtocol(MixedBulkWriteOperation.java:416)
at com.mongodb.operation.MixedBulkWriteOperation$Run$RunExecutor.execute(MixedBulkWriteOperation.java:604)
at com.mongodb.operation.MixedBulkWriteOperation$Run.execute(MixedBulkWriteOperation.java:363)
at com.mongodb.operation.MixedBulkWriteOperation$1.call(MixedBulkWriteOperation.java:148)
at com.mongodb.operation.MixedBulkWriteOperation$1.call(MixedBulkWriteOperation.java:141)
at com.mongodb.operation.OperationHelper.withConnectionSource(OperationHelper.java:186)
at com.mongodb.operation.OperationHelper.withConnection(OperationHelper.java:177)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:141)
at com.mongodb.operation.MixedBulkWriteOperation.execute(MixedBulkWriteOperation.java:72)
at com.mongodb.Mongo.execute(Mongo.java:747)
at com.mongodb.Mongo$2.execute(Mongo.java:730)
at com.mongodb.MongoCollectionImpl.executeSingleWriteRequest(MongoCollectionImpl.java:482)
at com.mongodb.MongoCollectionImpl.insertOne(MongoCollectionImpl.java:277)
at DutyScheduleDB.storeScheduledCalendar(DutyScheduleDB.java:59)
at DutyScheduleDB.main(DutyScheduleDB.java:106)
It seems that my codec for LocalDate
isn't being used when attempting to encode DutyBlock
objects, though I've verified that the collection that I am attempting to store the org.bson.Document
in does indeed contain the LocalDateCodec
via a
System.out.println(collection.getCodecRegistry().get(LocalDate.class));
Can anyone can provide some insight on why this is happening?
After several days of research, I've figured out a solution.
The DutyBlockCodec
depends on the LocalDateCodec
(which I created) in order to encode/decode. This dependency isn't satisfied just by adding the two codecs into the same codec registry. The solution is to pass a CodecRegistry
object containing the codecs that DutyBlockCodec
depends on (e.g. a CodecRegistry
containing within it the LocalDateCodec
) to the DutyBlockCodec
's constructor, which is stored as a member variable. In order to use the LocalDateCodec
to encode, I use the EncoderContext.encodeWithChildContext()
method, passing in the codec, writer, and element to encode. Additionally, I write individual fields rather than writing a Document
as a String
(as in my original code). Thus the DutyBlock
codec ends up looking like this:
public class DutyBlockCodec implements Codec<DutyBlock> {
private final CodecRegistry codecRegistry;
public DutyBlockCodec(final CodecRegistry codecRegistry) {
this.codecRegistry = codecRegistry;
}
@Override
public void encode(BsonWriter writer, DutyBlock t, EncoderContext ec) {
writer.writeStartDocument();
Codec dateCodec = codecRegistry.get(LocalDate.class);
writer.writeName("startDate");
ec.encodeWithChildContext(dateCodec, writer, t.getStartDate());
writer.writeName("endDate");
ec.encodeWithChildContext(dateCodec, writer, t.getEndDate());
writer.writeName("blockLength");
writer.writeInt32(t.getBlockLength());
writer.writeName("pointValue");
writer.writeDouble(t.getPointValue());
//Writing ArrayList of RAs
writer.writeName("assigned");
writer.writeStartArray();
for (Ra ra : t.getRasOnDuty()) {
Codec raCodec = codecRegistry.get(Ra.class);
ec.encodeWithChildContext(raCodec, writer, ra);
}
writer.writeEndArray();
writer.writeEndDocument();
}
@Override
public Class<DutyBlock> getEncoderClass() {
return DutyBlock.class;
}
@Override
public DutyBlock decode(BsonReader reader, DecoderContext dc) {
reader.readStartDocument();
Codec<LocalDate> dateCodec = codecRegistry.get(LocalDate.class);
reader.readName();
LocalDate startDate = dateCodec.decode(reader, dc);
reader.readName();
LocalDate endDate = dateCodec.decode(reader, dc);
reader.readName();
int blockLength = reader.readInt32();
reader.readName();
double pointValue = reader.readDouble();
//Reading ArrayList of RAs
reader.readName();
Codec<Ra> raCodec = codecRegistry.get(Ra.class);
ArrayList<Ra> rasOnDuty = new ArrayList<>();
reader.readStartArray();
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
rasOnDuty.add(raCodec.decode(reader, dc));
}
reader.readEndArray();
reader.readEndDocument();
return new DutyBlock(startDate, endDate, blockLength, pointValue, rasOnDuty);
}
}
DutyBlockCodec
depends on another codec, and so requires a CodecRegistry
to be passed in on its constructor. While I believe it is possible to create a CodecRegistry
with the LocalDateCodec
, then pass this as an argument to DutyBlockCodec
's constructor, then create another CodecRegistry
containing both LocalDateCodec
and DutyBlockCodec
, this is rather confusing, and MongoDB provides a functionality, the CodecProvider
to facilitate this process.
Using the CodecProvider
interface, I wrote a DutyBlockCodecProvider
public class DutyBlockCodecProvider implements CodecProvider {
@Override
public <T> Codec<T> get(Class<T> type, CodecRegistry cr) {
if (type == DutyBlock.class) {
return (Codec<T>) new DutyBlockCodec(cr);
}
return null;
}
}
I added these CodecProviders
to the MongoDB Client using the CodecRegistries.fromProviders()
method.
CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new LocalDateCodec()),
CodecRegistries.fromProviders(
new RaCodecProvider(),
new DutyBlockCodecProvider(),
new ScheduledDutyCodecProvider()),
MongoClient.getDefaultCodecRegistry());
MongoClientOptions options = MongoClientOptions.builder()
.codecRegistry(codecRegistry).build();
mongoClient = new MongoClient(new ServerAddress(), options);
db = mongoClient.getDatabase("DutySchedulerDB");
My source code for this project can be found at https://github.com/desrepair/DutyScheduler I'm open to answering any questions people may have.
这篇关于MongoDB BSON编解码器在编码对象时不被使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!