SAX学习笔记

最近因为做一套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文件
<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;
		}
	}


然后我们创建自己的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));
	    	}
	    }
	}

这里我们用了一个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);
	}

得到输出: 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,还是要根据实际情况去选择。

你可能感兴趣的:(jdk,框架,xml,SSO)