介绍
我和我的 friend 正在开发JavaFX应用程序,该应用程序充当我们学校的计划者。我们有任务(类作业), Activity ,类(class)和学生信息。为了尝试将数据持久存储在用户的硬盘上,我们正在使用JAXB。
我们已经为类添加了注释,并且可以在包装器中成功编码(marshal)Task类。问题是从tasks.xml
文件中解码。
以下是相关的代码行:
Task.java
@XmlRootElement
public class Task {
//constructors
//complete constructor
public Task(String className, String assignment, String description, LocalDate dueDate) {
this.className = new SimpleStringProperty(className);
this.assignment = new SimpleStringProperty(assignment);
this.description = new SimpleStringProperty(description);
this.dueDate = new SimpleObjectProperty<LocalDate>(dueDate);
}
/**
* Sets a model data into the task, sets the
* due date to be tomorrow.
*/
public Task() {
this("", "", "", LocalDate.now().plusDays(1));
setClassName("English");
setAssignment("Read");
setDescription("1984");
//setDueDate(LocalDate.now());
}
//Instance variables
private final SimpleStringProperty className;
private final SimpleStringProperty assignment;
private final SimpleStringProperty description;
private final ObjectProperty<LocalDate> dueDate;
// //Getters and setters
//... Other getters and setters
@XmlJavaTypeAdapter(LocalDateAdapter.class)
public final java.time.LocalDate getDueDate() {
return this.dueDateProperty().get();
}
public final void setDueDate(final java.time.LocalDate dueDate) {
this.dueDateProperty().set(dueDate);
}
}
TaskListWrapper.java:
//used in saving the objects to XML
@XmlRootElement(name="tasks")
public class TaskListWrapper {
private ObservableList<Task> task;
@XmlElement(name="task")
public ObservableList<Task> getTasks() {
return task;
}
public void setTasks(ObservableList<Task> tasks) {
this.task = tasks;
}
}
AppData.java中的方法
它涉及到文件的保存和从中解码。
/**
* Save to XML using JAXB
* @throws JAXBException
* @throws FileNotFoundException
*/
public static void save() throws JAXBException, FileNotFoundException {
//saving other objects
//...
TaskListWrapper tl = new TaskListWrapper();
//MasterTaskList is the entire list of tasks written to memory
tl.setTasks(AppData.getMasterTaskList());
saveObject(tl, new File(System.getProperty("user.dir") + "/resources/xml/tasks.xml"));
saveObject(masterStudentInfo, new File(System.getProperty("user.dir") + "/resources/xml/student_info.xml"));
}
同一类中的saveObject()方法:
/**
* Saves a specific Object {@code obj} to an xml file {@code xml} using JAXB.
* @param obj
* @param xml
* @throws FileNotFoundException
* @throws JAXBException
*/
private static void saveObject(Object obj, File xml) throws FileNotFoundException, JAXBException {
//context is used to determine what kind of class is going to be marshalled or unmarshalled
JAXBContext context = JAXBContext.newInstance(obj.getClass());
//loads to the XML file
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
//loads the current list of courses to the courses.xml file
m.marshal(obj, new FileOutputStream(xml));
}
App.java中的InitFiles()
请注意注释指出空指针异常
/**
* Initial setup for all the files for the program. Contains all the
* persistent data for the planner, such as courses, tasks, and events.
* <p>
* All data is saved in {@code [place of installment]/resources/xml/...}.
* @throws IOException
*/
public void initFiles() throws IOException{
//... other files for other objects
File tasks = new File(System.getProperty("user.dir") + "/resources/xml/tasks.xml");
//check if each file exists, if so unmarshall
if(tasks.exists()){
try {
JAXBContext context = JAXBContext.newInstance(TaskListWrapper.class);
//the file location is correct
System.out.println(tasks.toString());
//The context knows that both the Task and TaskListWrapper classes exist
System.out.println(context.toString());
Unmarshaller um = context.createUnmarshaller();
//TODO: null pointer exception
TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
//System.out.println(umObject.getClass());
} catch (JAXBException e) {
e.printStackTrace();
}
} else {
tasks.createNewFile();
}
//... other checks for files
}
编码良好的XML文档:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tasks>
<task>
<assignment>Book</assignment>
<className>Math</className>
<description>problems</description>
<dueDate>2015-01-17</dueDate>
</task>
<task>
<assignment>Textbook</assignment>
<className>Religion</className>
<description>problems</description>
<dueDate>2015-01-17</dueDate>
</task>
<task>
<assignment>Read</assignment>
<className>English</className>
<description>1984</description>
<dueDate>2015-03-05</dueDate>
</task>
</tasks>
异常(exception):
java.lang.NullPointerException
at com.sun.xml.internal.bind.v2.ClassFactory.create0(Unknown Source)
at com.sun.xml.internal.bind.v2.ClassFactory.create(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.Scope.add(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(Unknown Source)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(Unknown Source)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(Unknown Source)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
at org.sjcadets.planner.App.initFiles(App.java:136)
at org.sjcadets.planner.App.start(App.java:68)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$153(Unknown Source)
at com.sun.javafx.application.LauncherImpl$$Lambda$51/1390460753.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$166(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda$45/1051754451.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl.lambda$null$164(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda$47/231444107.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$165(Unknown Source)
at com.sun.javafx.application.PlatformImpl$$Lambda$46/1775282465.run(Unknown Source)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null$141(Unknown Source)
at com.sun.glass.ui.win.WinApplication$$Lambda$37/1109371569.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
空指针位于
//TODO
方法中声明的initFiles()
处:JAXBContext context = JAXBContext.newInstance(TaskListWrapper.class);
//the file location is correct
System.out.println(tasks.toString());
//The context knows that both the Task and TaskListWrapper classes exist
System.out.println(context.toString());
Unmarshaller um = context.createUnmarshaller();
//TODO: null pointer exception
TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
我们尝试过的事情:
JAXBContext
知道的类进行系统配置。它可以识别Task
和TaskListWrapper
类。 um.toString()
。它在内存中显示了一个有效地址,因此um
对象本身不是引发nullpointer异常的对象。 TaskListWrapper.java
的位置更改为与Task.java
相同的软件包。 <task>
作为根元素来解码单个Task,当我进行更改时TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
至
Task taskList = (Task) um.unmarshal(tasks);
我们寻找答案的地方:
@XMLAttribute
批注的错误有关。由于我们不使用那些与bug无关的错误JAXBContext context = JAXBContext.newInstance(Inventory.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Inventory inventory = (Inventory) unmarshaller.unmarshall(
new File("zooinventory.xml") );
问题
为什么
TaskListWrapper taskList = (TaskListWrapper) um.unmarshal(tasks);
抛出空指针异常? 最佳答案
JAXB与包装器中的ObservableList等FXCollection不兼容。您必须编写一个XmlAdapter才能将其与常规列表解缠。因此,编码(marshal)将起作用,但编码(marshal)将不起作用,正如您在stacktrace的行中看到的那样:
at com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Unknown Source)
有一个 Lister $ CollectionLister ,它不知道如何处理未知源。因此,Adpater应该使用这样的ListWrapper:
public class TaskList {
@XmlElement(name = "task")
List<Task> entries = new ArrayList<>();
public List<Task> getEntries() {
return entries;
}
}
相应的适配器如下所示:
public class TaskListAdapter extends XmlAdapter<TaskList, ObservableList<Task>> {
@Override
public ObservableList<Task> unmarshal(TaskList v) throws Exception {
ObservableList<Task> list = FXCollections.observableArrayList(v.entries);
return list;
}
@Override
public TaskList marshal(ObservableList<Task> v) throws Exception {
TaskList taskList = new TaskList();
v.stream().forEach((item) -> {
taskList.entries.add(item);
});
return taskList;
}
}
这样您的TaskListWrapper最终应该看起来像这样:
//used in saving the objects to XML
@XmlRootElement(name="tasks")
public class TaskListWrapper {
private ObservableList<Task> task;
@XmlJavaTypeAdapter(TaskListAdapter.class)
public ObservableList<Task> getTasks() {
return task;
}
public void setTasks(ObservableList<Task> tasks) {
this.task = tasks;
}
}
顺便说一句,您使用了很多FX属性,所以也许您最好用
@XmlAccessorType(XmlAccessType.PROPERTY)
注释您的Task类,并确保对于每个要设置的字段都存在一个getter/setter方法。就像FXProperties约定说的那样:private final StringProperty description = new SimpleStringProperty();
public String getDescription() {
return description.get();
}
public void setDescription(String description) {
this.description.set(description);
}
public StringProperty descriptionProperty(){
return description;
}
注释
@XmlAccessType(XmlAccessorType.PROPERTY)
在此处进行了详细描述:JAXB JavaDoc。如果在包或类上没有任何注释,则默认值将是@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
,其中JavaDoc说:因此,在FX类(特殊模型)中,您尝试在专用字段中隐藏使用的属性。但是,如果您需要一个不应编码的公共(public)场所,该怎么办?然后,我建议进行
@XmlAccessorType(XmlAccessType.PROPERTY)
批注。它的JavaDoc说:注意一个单词
public
的微小差异,因此,如果使用@XmlAccessorType(XmlAccessType.PROPERTY)
进行注释,则甚至会考虑使用私有(private)getter/setter。但是我认为大多数人在JavaDoc所说的地方使用
@XmlAccessorType(XmlAccessType.FIELD)
:在具有FX属性的FX类中,这可能会有些棘手。我不会推荐给您。