GUIのないサーバで、javaのプログラムをデバッグしていた時のおはなし。
Eclipseしか使ったことのない方には馴染みがないと思いますが、javaにはjdbというgdbみたいなデバッガがあります。
jdbは、画面のない環境でも(gdbを使い慣れた人なら)お手軽にデバッグできるツールなのですが、あるプログラムをデバッグしようとしたら
Exception in thread "JDI Internal Event Handler" java.lang.IllegalArgumentException: Invalid JNI signature character ';' at com.sun.tools.jdi.JNITypeParser.nextTypeName(JNITypeParser.java:236) at com.sun.tools.jdi.JNITypeParser.typeNameList(JNITypeParser.java:140)
のようなExceptionが出てデバッグができませんでした。
jdbを使わなければ、普通に動きます。jdbでデバッグしようとするとこのようにExceptionが出ます。
ちなみに、Javaのバージョンは、1.6.0_18(OpenJDK)。OSは、Ubuntu 10.04です。
よくわからないので、いろいろ調べてみました。
Google先生で見つけたのが、このサイト。
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6547438
それらしいSolutionが載っているのですが、試してみても状況は変わりませんでした。
結局試行錯誤の上、プログラムを問題が起こる最小限のものにしてみて、やっと問題の回避策がわかりました。
問題が起こるソースコード
MarshalTest.java
import java.io.File; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; public class MarshalTest { @XmlRootElement static class Sample { private String[] messages; public String[] getMessages() { return messages; } public void setMessages(String[] messages) { this.messages = messages; } } public static void main(String[] args) { try { Marshaller marshaller = JAXBContext.newInstance(Sample.class).createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); Sample smpl = new Sample(); smpl.setMessages(new String[]{"abc","def"}); marshaller.marshal(smpl, new File("sample.xml")); System.out.println("marshal end"); } catch (JAXBException e) { e.printStackTrace(); } } }
このサイトなどに書いてあるように、Java6では簡単にPOJOをxmlファイルにできます。
しかし、どうも配列の扱いがよろしくないようで、getterとsetterがある配列を含むJava Objectをmarshalすると、jdbでは上記のようなExceptionがでます。
(配列でなければ発生しません。)
JAXBがmarshallする際に、オブジェクトのメンバー名を使うか、getter/setterを使うかわからなくなっている気がしたので、”@XmlAccessorType(XmlAccessType.FIELD)”(メンバー名を使え)というアノテーションをつけたら回避できるようになりました。
対策済みのソースコード
※XmlAccessorTypeのアノテーション(13行目)を1行足しただけです。
MarshalTest.java
import java.io.File; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; public class MarshalTest { @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) static class Sample { private String[] messages; public String[] getMessages() { return messages; } public void setMessages(String[] messages) { this.messages = messages; } } public static void main(String[] args) { try { Marshaller marshaller = JAXBContext.newInstance(Sample.class).createMarshaller(); marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); Sample smpl = new Sample(); smpl.setMessages(new String[]{"abc","def"}); marshaller.marshal(smpl, new File("sample.xml")); System.out.println("marshal end"); } catch (JAXBException e) { e.printStackTrace(); } } }
ちなみに、この現象はWindowsでも再現しました。
あと、Windows上のEclipseでも試してみたところ、createMarshallerを呼ぶあたりで、
「行番号属性が見つからないため、ブレークポイントをxxxxにインストールできません。」
みたいに言われたので、内部で同じ問題が発生しているかもしれません。
もし私と同じ問題で困っている方がいたら、
配列をやめるとか、getter/setterを削除するとか、上記のアノテーションを追加するとかの対策を講じてみてください。