最近因为做一套SSO框架,需要解析SAML报文,研究了一下openSAML发现其需要1.5以上的jdk,于是决定自己做解析程序。在网上比较了一番dom和sax后,决定用sax来解析,因为我们更关注的是报文中某些个属性值,而不是报文的结构。闲话少叙,下面开始SAX的学习。
首先我们先了解一下SAX的基本概念。SAX是Simple Api for XML的缩写,相比与DOM,SAX是一种轻量级的XML访问方法。DOM在处理xml文档时,我们需要读入整个文档,在内存中创建dom树。在文档比较小的时候这没有什么问题,但随着文档的增大,dom的访问效率以及内存占用都会出现问题,这时,SAX就是比较好的替代方案。SAX不同于DOM的文档驱动,它是事件驱动的,在解析过程中,它不需要读入整个文档,文档读入的过程就是解析过程。所谓的事件驱动,是一种基于回调机制的程序运行方法。在解析之前,先注册一个ContentHandler,相当于一个事件监听器,其中定义了很多方法,用以处理在读取文档各个部分时要做的事情,比如startDocument(),它定义了解析过程中,遇到文档开始时所作的事情。当XMLReader读取到合适的内容,就会抛出相应的事件,把内容交给ContentHander去处理。在网上搜索SAX资料时,找到了一个简单的例子,很清晰的说明了SAX解析过程。我们来看这个简单的xml文件
SAX的解析过程:
SAX提供了一个DefaultHandler类,其中所有方法为空实现,我们要做的就是创建一个自己的Handeler类继承DefaultHandler,然后重载其中的startDocument()、startElement()、characters()、endElement()等方法来自己的处理。
现在我们来动手做个解析SAML AuthnRequest报文的例子,首先来看报文
我们要把其中的ID、IssueInstant、Issuer、AllowCreate、Format属性解析出来,放到一个java对象中。 首先我们定义一个AuthRequest对象,其中包含上面的属性以及get、set方法
然后我们创建自己的handler MyHandler
这里我们用了一个stack来保存当前解析的节点信息。因为标准的xml每个标签都一定有一个对应的结束标签,我们在startElement时把标签名称push到stack中,在endElement时pop出来,这样就可以获取当前解析的标签在文档中的位置。
最后我们来执行代码
得到输出: id=id_1,issueInstant=2007-12-05T09:21:59Z,issuer=https://www.chinamobile.com/SSO
这里我们完成了一个简单的将XML转成JAVA对象的例子。可以看出,SAX提供的是一套比DOM更为基础的XML解析API,通过自定义Handler,我们可以自己控制xml解析过程,只解析我们关心的信息。当然具体用DOM,还是SAX,还是要根据实际情况去选择。
首先我们先了解一下SAX的基本概念。SAX是Simple Api for XML的缩写,相比与DOM,SAX是一种轻量级的XML访问方法。DOM在处理xml文档时,我们需要读入整个文档,在内存中创建dom树。在文档比较小的时候这没有什么问题,但随着文档的增大,dom的访问效率以及内存占用都会出现问题,这时,SAX就是比较好的替代方案。SAX不同于DOM的文档驱动,它是事件驱动的,在解析过程中,它不需要读入整个文档,文档读入的过程就是解析过程。所谓的事件驱动,是一种基于回调机制的程序运行方法。在解析之前,先注册一个ContentHandler,相当于一个事件监听器,其中定义了很多方法,用以处理在读取文档各个部分时要做的事情,比如startDocument(),它定义了解析过程中,遇到文档开始时所作的事情。当XMLReader读取到合适的内容,就会抛出相应的事件,把内容交给ContentHander去处理。在网上搜索SAX资料时,找到了一个简单的例子,很清晰的说明了SAX解析过程。我们来看这个简单的xml文件
- <POEM>
- <AUTHOR>Ogden Nash</AUTHOR>
- <TITLE>Fleas</TITLE>
- <LINE>Adam</LINE>
- </POEM>
SAX的解析过程:
遇到的项目 | 方法回调 |
{文档开始} | startDocument() |
<POEM> | startElement(null,"POEM",null,{Attributes}) |
"/n" | characters("<POEM>/n...", 6, 1) |
<AUTHOR> | startElement(null,"AUTHOR",null,{Attributes}) |
"Ogden Nash" | characters("<POEM>/n...", 15, 10) |
</AUTHOR> | endElement(null,"AUTHOR",null) |
"/n" | characters("<POEM>/n...", 34, 1) |
<TITLE> | startElement(null,"TITLE",null,{Attributes}) |
"Fleas" | characters("<POEM>/n...", 42, 5) |
</TITLE> | endElement(null,"TITLE",null) |
"/n" | characters("<POEM>/n...", 55, 1) |
<LINE> | startElement(null,"LINE",null,{Attributes}) |
"Adam" | characters("<POEM>/n...", 62, 4) |
</LINE> | endElement(null,"LINE",null) |
"/n" | characters("<POEM>/n...", 67, 1) |
</POEM> | endElement(null,"POEM",null) |
{文档结束} | endDocument() |
SAX提供了一个DefaultHandler类,其中所有方法为空实现,我们要做的就是创建一个自己的Handeler类继承DefaultHandler,然后重载其中的startDocument()、startElement()、characters()、endElement()等方法来自己的处理。
现在我们来动手做个解析SAML AuthnRequest报文的例子,首先来看报文
- <samlp:AuthnRequest
- xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
- xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
- ID="id_1"
- Version="2.0"
- IssueInstant="2007-12-05T09:21:59Z">
- <saml:Issuer>https://www.chinamobile.com/SSO</saml:Issuer>
- <samlp:NameIDPolicy
- AllowCreate="true"
- Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
- </samlp:AuthnRequest>
我们要把其中的ID、IssueInstant、Issuer、AllowCreate、Format属性解析出来,放到一个java对象中。 首先我们定义一个AuthRequest对象,其中包含上面的属性以及get、set方法
- static class AuthRequest {
- private String id;
- private String issueInstant;
- private String issuer;
- private String allowCreate;
- private String format;
- public void setID(String id){
- this.id = id;
- }
- public void setIssueInstant(String issueInstant){
- this.issueInstant = issueInstant;
- }
- public void setIssuer(String issuer){
- this.issuer = issuer;
- }
- public String getId() {
- return id;
- }
- public void setId(String id) {
- this.id = id;
- }
- public String getAllowCreate() {
- return allowCreate;
- }
- public void setAllowCreate(String allowCreate) {
- this.allowCreate = allowCreate;
- }
- public String getFormat() {
- return format;
- }
- public void setFormat(String format) {
- this.format = format;
- }
- public String getIssueInstant() {
- return issueInstant;
- }
- public String getIssuer() {
- return issuer;
- }
- @Override
- public String toString() {
- return "id="+id+",issueInstant="+issueInstant+",issuer="+issuer;
- }
- }
static class AuthRequest {
private String id;
private String issueInstant;
private String issuer;
private String allowCreate;
private String format;
public void setID(String id){
this.id = id;
}
public void setIssueInstant(String issueInstant){
this.issueInstant = issueInstant;
}
public void setIssuer(String issuer){
this.issuer = issuer;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAllowCreate() {
return allowCreate;
}
public void setAllowCreate(String allowCreate) {
this.allowCreate = allowCreate;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getIssueInstant() {
return issueInstant;
}
public String getIssuer() {
return issuer;
}
@Override
public String toString() {
return "id="+id+",issueInstant="+issueInstant+",issuer="+issuer;
}
}
然后我们创建自己的handler MyHandler
- static class MyHandler extends DefaultHandler {
- private AuthRequest request;
- private Stack stack;
- public MyHandler(AuthRequest request){
- this.request = request;
- this.stack = new Stack();
- }
- @Override
- public void startElement(String uri, String localName, String name,
- Attributes attributes) throws SAXException {
- stack.push(name);
- if ("samlp:AuthnRequest".equalsIgnoreCase(name)){
- for (int i = 0;i < attributes.getLength();i ++){
- if ("ID".equalsIgnoreCase(attributes.getQName(i))){
- request.setID(attributes.getValue(i));
- }else if ("IssueInstant".equalsIgnoreCase(attributes.getQName(i))){
- request.setIssueInstant(attributes.getValue(i));
- }
- }
- }else if ("samlp:NameIDPolicy".equalsIgnoreCase(name)){
- for (int i = 0;i < attributes.getLength();i ++){
- if ("AllowCreate".equalsIgnoreCase(attributes.getQName(i))){
- request.setAllowCreate(attributes.getValue(i));
- }else if ("Format".equalsIgnoreCase(attributes.getQName(i))){
- request.setFormat(attributes.getValue(i));
- }
- }
- }
- }
- @Override
- public void endElement(String uri, String localName, String name)
- throws SAXException {
- stack.pop();
- }
- @Override
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- if ("saml:Issuer".equalsIgnoreCase((String)stack.peek())){
- request.setIssuer(new String(ch,start,length));
- }
- }
- }
static class MyHandler extends DefaultHandler {
private AuthRequest request;
private Stack stack;
public MyHandler(AuthRequest request){
this.request = request;
this.stack = new Stack();
}
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
stack.push(name);
if ("samlp:AuthnRequest".equalsIgnoreCase(name)){
for (int i = 0;i < attributes.getLength();i ++){
if ("ID".equalsIgnoreCase(attributes.getQName(i))){
request.setID(attributes.getValue(i));
}else if ("IssueInstant".equalsIgnoreCase(attributes.getQName(i))){
request.setIssueInstant(attributes.getValue(i));
}
}
}else if ("samlp:NameIDPolicy".equalsIgnoreCase(name)){
for (int i = 0;i < attributes.getLength();i ++){
if ("AllowCreate".equalsIgnoreCase(attributes.getQName(i))){
request.setAllowCreate(attributes.getValue(i));
}else if ("Format".equalsIgnoreCase(attributes.getQName(i))){
request.setFormat(attributes.getValue(i));
}
}
}
}
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
stack.pop();
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if ("saml:Issuer".equalsIgnoreCase((String)stack.peek())){
request.setIssuer(new String(ch,start,length));
}
}
}
这里我们用了一个stack来保存当前解析的节点信息。因为标准的xml每个标签都一定有一个对应的结束标签,我们在startElement时把标签名称push到stack中,在endElement时pop出来,这样就可以获取当前解析的标签在文档中的位置。
最后我们来执行代码
- public static void main(String[] args) throws Exception {
- SAXParserFactory factory = SAXParserFactory.newInstance();
- SAXParser parser = factory.newSAXParser();
- AuthRequest request = new AuthRequest();
- parser.parse(new File("src/xml/req.xml"), new MyHandler(request));
- System.out.println(request);
- }
public static void main(String[] args) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
AuthRequest request = new AuthRequest();
parser.parse(new File("src/xml/req.xml"), new MyHandler(request));
System.out.println(request);
}
得到输出: id=id_1,issueInstant=2007-12-05T09:21:59Z,issuer=https://www.chinamobile.com/SSO
这里我们完成了一个简单的将XML转成JAVA对象的例子。可以看出,SAX提供的是一套比DOM更为基础的XML解析API,通过自定义Handler,我们可以自己控制xml解析过程,只解析我们关心的信息。当然具体用DOM,还是SAX,还是要根据实际情况去选择。