XML спасет мир!
… По крайней мере, так мне сказали. Но каким-то образом, спустя несколько лет, большинство из нас не использует расширяемый язык разметки (XML) в нашем повседневном программировании.
Тем не менее, время от времени вас могут попросить предоставить услугу, которая будет потреблять или выводить XML, поэтому рекомендуется освоить эту технологию.
Два примера, вероятно, доминируют в опыте работы с XML. Первый — это сервис цен на акции, который ряд компаний предоставляют компаниям, которые хотят размещать информацию о цене своих акций на своих веб-сайтах. Второй — это выпуск пресс-релизов компаний в виде XML-канала для заинтересованных сторон.
Здесь я опишу методы, которые мы используем для использования XML в среде JSP, и кратко прокомментирую вывод XML в среде JSP. Для этого я объясню, как XML обрабатывается в JSP, а затем мы можем попробовать его в коде.
Общий документ XML
В рамках своей работы мне часто приходится предоставлять информацию о цене акций для компаний, которые хотят добавить услугу на свои веб-сайты.
Вот типичный общий информационный документ в формате XML. Служба обычно предоставляется по HTTP — то есть вы можете найти ее, просто указав браузеру URL-адрес, например http://www.myinvestorinfo.com/sharefeed.jsp?company=XXX.
Документ о цене акции может выглядеть так:
<shares> <share> <code>CSW</code> <price>502.25</price> <change>2.25</change> <percentageChange>0.45</ percentageChange> <open>500</open> <high>506.32</high> <low>499.85</low> <yearLow>423.45</yearLow> <yearHigh>586.92</yearHigh> <volume>12486123</volume> <date>12.45 12/06/2003</date> </share> </shares>
Если вы подписаны на более чем одно предложение, предоставляется несколько элементов. А пока давайте предположим, что нам нужна статистика только для одной акции, и что нам конкретно нужны цифры «Цена», «Изменение» и «Изменение в процентах» вместе с датой, когда была установлена цена для этой акции.
Эти статистические данные представляют в порядке:
- текущая стоимость каждой акции
- сумма, на которую это значение изменилось с момента последней стоимости акции в долларах
- сумма, на которую это значение изменилось с момента последней стоимости акции в процентах
- дата и время, когда это значение было установлено
Как правило, при представлении актуальных обновлений общего ресурса требуется высокая стоимость, поэтому они часто устаревают на 20 и более минут.
Java и SAX
Для обработки XML большого объема, критичной ко времени, рекомендуется Simple API for XML (SAX). По сути, это означает, что каждый элемент и фрагмент текста передаются в том порядке, в котором они встречаются, процессору или обработчику, который запрограммирован так, чтобы предопределенным образом реагировать на элементы в документе.
В этом случае у нас есть потенциально большой объем данных, представленных на сайте. Любой контент критичен ко времени в Интернете, и хотя люди обычно прощают владельцев сайтов за небольшие задержки с обновлением финансовых данных, нет смысла бесполезно проверять свое терпение. По иронии судьбы, ожидание актуальной информации, кажется, выше в случае бесплатного контента. Поскольку использование бесплатных услуг бесплатное, пользователи быстро откажутся от этих услуг в пользу более актуальной информации, которая так широко доступна в Интернете.
Другое преимущество, которое не следует упускать из виду, заключается в том, что объем памяти, необходимый для обработки документов в SAX, довольно мал. Кроме того, наша задача очень проста (мы просто хотим извлечь информацию, чтобы показать ее), поэтому нет необходимости обрабатывать или иным образом манипулировать данными.
Класс данных
Как мы уже обсуждали, информация, которая нас интересует:
- имя
- текущая цена
- изменение доллара
- время
- Дата
- процентное изменение
Цена, время и все другие значения, кроме даты, будут сохранены в виде строк.
Поэтому класс данных для этого примера будет таким, как показано ниже. Обратите внимание, что я пропустил очевидные фрагменты кода, такие как конструкторы и прямые методы получения и установки:
package com.clearlysomethingwrong; import java.util.Date; import java.text.SimpleDateFormat; import java.text.ParseException; public class InvestorInformation { String name; String currentPrice; String change; String time; Date date; String percentage; public String getFormattedDate() { SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy"); if(date!=null) { return formatter.format(date); } else { return formatter.format(new Date()); } } public void setDate(String strdate) { SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yy"); try { date = formatter.parse(strdate); } catch (ParseException e) { date = new Date(); } } }
Строковые форматы для входящих и исходящих дат здесь жестко запрограммированы, но это не составит труда изменить. Проверка на NULL в получателе гарантирует, что мы не получим исключение NullPointerException, передав NULL в средство форматирования.
Загрузка XML-документа
Ну, это было просто, как должны быть классы данных! Следующий класс, который мы напишем, — это своего рода фабрика. Мы даем ему java.io.InputStream источника XML, и он возвращает заполненный класс InvestorInformation.
SAX-разбор обычно включает использование идентичного базового кода; вы создаете синтаксический анализатор для чтения входящего XML и передаете этому анализатору обработчик, который обрабатывает каждый из типов содержимого в XML-файле.
Поскольку объединенный код (который создает синтаксический анализатор и обрабатывает элементы XML) довольно прост, следующий класс, который мы создадим, выполнит обе задачи. Он реализует класс Handler и предоставит метод для обработки источника XML.
Для начала расширим класс org.xml.sax.helpers.DefaultHandler
:
package com.clearlysomethingwrong; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.InputStream; import java.io.IOException; public class InvestorInfoLoader extends DefaultHandler { static InvestorInformation info; static String code; static String name; private boolean foundCode; private boolean setName; private boolean setCurrentPrice; private boolean setChange; private boolean setPercentage; private boolean setTime; private boolean setDate;
Каждый из этих флагов указывает состояние документа XML, в котором мы заинтересованы. Когда программа вводит каждый из соответствующих элементов, будет установлен соответствующий флаг. Затем, когда символьный контент отправляется программе, он может назначить этот контент соответствующему свойству класса InvestorInformation.
Свойство foundCode
отмечает, что общий ресурс с данным кодом был найден. Свойство name позволяет пользователю класса подставлять удобное для компании имя компании, поскольку обычные имена общих ресурсов (такие как IBM, SUNW, XOM и FNM) могут быть загадочными для тех, кто не в курсе. Если значение равно нулю, мы можем просто вернуть код в качестве имени информационного объекта общего ресурса.
Далее мы определяем метод статического процессора loadInvestorInfo()
. Этот метод создает синтаксический анализатор с помощью метода фабрики, создает собственный экземпляр, который будет действовать как обработчик, а затем передает входной поток и сам себя. Результат сохраняется в статическом свойстве InvestorInformation для этого класса, которое затем возвращается:
public static InvestorInformation loadInvestorInfo(InputStream is, String code, String name) throws SAXException, IOException, ParserConfigurationException { // detect invalid code, invalid input stream will be detected by parser. if(code==null || code.length() == 0) { throw new IllegalArgumentException("Code is null. Please provide a " + "share code."); } InvestorInfoLoader.code = code; InvestorInfoLoader.name = name; InvestorInfoLoader iil = new InvestorInfoLoader(); // load the XML file // create a new sax parser factory javax.xml.parsers.SAXParserFactory factory = javax.xml.parsers.SAXParserFactory.newInstance(); // turn off namespaces for now factory.setNamespaceAware(false); factory.setValidating(true); javax.xml.parsers.SAXParser parser = factory.newSAXParser(); parser.parse(is, iil); // load each bit of info for font return info; }
Довольно стандартные вещи. Методы-обработчики также довольно просты. Обратите внимание, что здесь я отключил осведомленность о пространстве имен — обычно предоставляется DTD, но для приложений с уровнем простоты, показанным здесь (и я считаю, что это довольно типично), может не быть жизненно важным иметь осведомленность о пространстве имен.
Вознаграждение, как правило, зависит от производительности и безопасности, и это ваше решение. Без сомнения, ваш выбор будет зависеть от уровня ваших отношений с поставщиком услуги (многие изменяют свою конфигурацию без предварительного уведомления) и от того, насколько критично приложение.
Обнаружение Элементов
Полное имя каждого элемента — это имя, определенное выше, <price>
для цены и т. Д. Элемент share обнаружен для создания нового экземпляра InvestorInformation
.
public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes) { if(qName.equals("share") && info==null) { info=new InvestorInformation(); } else if (qName.equals("code")) { setName=true; } else if(qName.equals("price")) { setCurrentPrice = true; } else if(qName.equals("change")) { setChange = true; } else if(qName.equals("time")) { setTime = true; } else if(qName.equals("date")) { setDate = true; } else if(qName.equals("percentageChange")) { setPercentage = true; } } public void endElement(String uri, String localName, String qName) { if(qName.equals("share")) { foundCode = false; } else if(qName.equals("code")) { setName = false; } else if(qName.equals("price")) { setCurrentPrice = false; } else if(qName.equals("change")) { setChange = false; } else if(qName.equals("time")) { setTime = false; } else if(qName.equals("date")) { setDate = false; } else if(qName.equals("percentageChange")) { setPercentage = false; } }
Установка общих значений
Затем метод characters()
собирает информацию об общем ресурсе.
public void characters(char[] ch, int start, int length){ if(setName) { String localName = new String(ch, start, length); if(localName.equals(code)) { this.foundCode = true; if(name==null) { info.setName(localName); } else { info.setName(name); } } else { // reset the found code class in case there are multiple share entries foundCode = false; } } else if(foundCode && setCurrentPrice) { info.setCurrentPrice(new String(ch, start, length)); } else if(foundCode && setChange) { info.setChange(new String(ch, start, length)); } else if(foundCode && setPercentage) { info.setPercentage(new String(ch, start, length)); } else if(foundCode && setTime) { info.setTime(new String(ch, start, length)); } else if(foundCode && setDate) { info.setDate(new String(ch, start, length)); } }
Единственное, что здесь особенно заметно, это обработка элемента кода. Поскольку документ может содержать несколько предложений акций, нам нужно определить правильное предложение акций, но мы можем сделать это только после создания нового класса ShareInformation
и нахождения внутри объекта предложения акций. Вот где foundCode
флаг foundCode
.
Самое смешное в этом коде состоит в том, что почти весь код обработки SAX выглядит точно так же, как и выше. Это на самом деле довольно просто, так что большая часть программирования, необходимого для использования информации, собранной сервисом, происходит в другом месте.
На этом этапе я настоятельно рекомендую протестировать этот класс, поскольку его простота может заставить вас предположить, что он будет работать впервые. Попробуйте провести тестирование диапазона, чтобы убедиться, что вы охватили все возможные значения.
Разработка веб-компонента
До этого момента кодирование было довольно простым. Было принято относительно немного решений, а именно, будем ли мы включать различные дополнительные функции синтаксического анализатора.
Если после тестирования класс работает не так, как ожидалось, вам может быть проще вывести обработчик из рабочего класса. Хотя классы крошечные, разделение ролей обычно помогает понять, в чем вы ошиблись. Это зависит от того, насколько вы уверены в жонглировании статическими ролями и ролями экземпляров.
Кроме того, не было абсолютно необходимости делать метод loadInvestorInfo()
статическим. С таким же успехом можно было бы сделать обязательным создание экземпляра класса InvestorInfoLoader. Думаю, в глубине души я где-то рассматривал возможность кэширования результата — статический класс, подобный фабричному классу, является одним из немногих способов, которыми мы могли бы достичь этого.
Я рассмотрел 10 различных дизайнов, прежде чем остановился на этом как на решении, которое я бы использовал — иногда вам просто нужно продолжить и написать код, или ничего не будет сделано. Другие соображения включали в себя сохранение Hashtable результатов, которые можно кэшировать (и очищать) и индексировать в соответствии с кодом на этом уровне, в отличие от хранения данных в контексте и т. Д.
В конце концов я решил реализовать основной функционал в виде тега. Это позволяет мне учить дизайнеров (не программистов), как использовать тег, и обеспечивает разумную производительность. Кроме того, этот подход не требует никакого вмешательства с моей стороны, когда он объединен в Ant со стандартным сценарием сборки, который копирует необходимый файл jar и конфигурацию для работы тега. Но даже без этого дизайнеры на работе были уверены, что в сценарии присутствуют необходимые ресурсы.
Альтернативные решения использования Servlet или даже класса Struts, по моему мнению, вводят слишком много конфигурации для того, что часто является единственной динамической функцией на сайте. Они также требуют от меня фактического программирования при каждом использовании сервиса, что было неприемлемо для этого приложения.
Написание тега
Написание тега теперь должно быть довольно простым. Нам нужно подобрать следующие атрибуты от дизайнера:
- URL к источнику XML
- код для компании (давайте используем CSW, понятное название для моей мифической компании — Clearly Something Wrong Company Ltd)
- имя атрибута, под которым мы хотели бы сохранить результат
Тег не должен иметь никакого содержимого тела, поэтому я реализую TagSupport
.
Сразу же, это дает нам:
package com.clearlysomethingwrong; import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.tagext.TagSupport; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.JspException; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.net.URL; public class ShowSharePrices extends TagSupport { public String investorURL; public String code; public String friendlyName; public String attName;
Сначала мы получаем запрос. Мы собираемся сохранить атрибут здесь, поэтому он нам понадобится. В реальном приложении мы также могли бы проверить, что все значения действительны и были предоставлены значимым образом, чтобы предоставить пользователю (тегу и системе) полезные сообщения об ошибках. Мы создаем объект URL с заданной строкой URL. Этот объект URL просто дает нам доступ к URL, включая предоставление ряда подробностей о нем и открытие для него InputStream.
При условии, что содержимое URL — это то, что нам нужно, это значение затем может быть передано нашему загрузчику информации для инвесторов вместе с кодом, который извлечет соответствующую информацию об акциях и необязательное удобное для пользователя имя для нашей компании.
Если результат не равен NULL, мы устанавливаем атрибут, используя заданное имя атрибута, и завершаем работу:
public int doStartTag() throws JspException { HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); InvestorInformation info = null; try{ URL url = new URL(investorURL); info = InvestorInfoLoader.loadInvestorInfo(url.openStream(), code, friendlyName); } catch(IOException e) { throw new JspException("Error reading XML source"); } catch (SAXException e) { throw new JspException("Sax exception occurred: " + e.getMessage()); } catch (ParserConfigurationException e) { throw new JspException("Configuration error:" + e.getMessage()); } if(info!=null) { request.setAttribute(attName, info); } return Tag.SKIP_BODY; }
За исключением самоочевидных мутаторов и аксессоров, теперь мы можем создать дескриптор библиотеки тегов:
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>xmlInvestor</short-name> <uri>/xmlInvestor</uri> <description> Investor Information Loader Tag </description> <tag> <name>loader</name> <tag-class>InvestorInfoLoader</tag-class> <body-content>empty</body-content> <description> Loads investor information given a URL of the XML source, a company investor code, and an attribute name to store the resultant InvestorInformation class. Optionally, a friendly name can be provided for the company. </description> <attribute> <name>investorURL</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>code</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>attName</name> <required>true</required> <rtexprvalue>false</rtexprvalue> </attribute> <attribute> <name>friendlyName</name> <required>false</required> <rtexprvalue>false</rtexprvalue> </attribute> </tag> </taglib>
При упаковке в соответствии со стандартными требованиями к тегу JSP использовать тег так же просто, как предоставить следующий файл JSP:
<%@ page import="com.clearlysomethingwrong.InvestorInformation"%> <%@ taglib uri="/xmlInvestor" prefix="xmlInvestor"%> <xmlInvestor:loader friendlyName="Something Clearly Wrong" code="CSW" attName="InvestInfo" investorURL="http://localhost:8080/source.xml"/> <% InvestorInformation info = (InvestorInformation)request.getAttribute("InvestInfo"); if(info!=null) { %><%=info.getName()%><br> <%=info.getCurrentPrice()%><br> <%=info.getChange()%><br> <%=info.getTime()%><br> <%=info.getFormattedDate()%><br> <%=info.getPercentage()%><br> <% } %>
Единственной возможной жалобой может быть отсутствие использования другого тега или набора тегов для вывода данных, однако я обнаружил, что теги по-прежнему путают такие программы, как Dreamweaver.
Сценарии в основном предоставляются, потому что Dreamweaver имеет дело с ними более изящно, чем с тегами, о которых он не знает. Для написания плагина требуется значительная поддержка, которая предотвратит разрушение кода обычным дизайнером, когда потребуется редизайн.
Замечание о выводе XML в JSP
По моему мнению, если проект не требует серьезных инвестиций в разработку программного обеспечения, который не является основным для обычного веб-разработчика, написание сложного кода для вывода XML с использованием DOM в приложении на основе JSP неэффективно и вводит дорогостоящие требования к обслуживанию.
В целом, когда достаточно плоская структура — это все, что нужно, интуитивно понятный подход к предоставлению вывода XML заключается в простом копировании формата документа на страницу JSP и вставке скриптлетов, которые будут вставлять необходимые данные.
Документ, который мы только что разработали для предоставления общей информации, будет выглядеть следующим образом:
<shares> <share> <code><%=info.getName()%></code> <price><%=info.getPrice()%></price> ... etc </share> </shares>
Во многих случаях это сэкономило много времени, его очень легко проверить (поскольку загрузка его в Internet Explorer также проверит его действительность), и оно отлично справляется со своей задачей.
Запустить этот код
Моя основная среда разработки — J2SDK1.4, в которой обработка XML включена в качестве стандарта. Пользователям JDK1.3 потребуется загрузить пакет XML, доступный по адресу http://java.sun.com/ . В противном случае код включает только стандартные классы Java.
Для облегчения запуска кода я упаковал тег в файл jar в соответствии со спецификациями тега JSP. Это позволяет очень просто включить функциональность, предоставляемую тегом, в файл JSP. Просто поместите jar-файл в каталог lib в WEB-INF
. Я рекомендую вам потратить некоторое время на изучение этого простого метода, так как он может сохранить обычный скачок конфигурации в конце проекта и более пригоден для повторного использования.
Вывод
Код довольно прост, но, по возможности, так и должно быть. Каждый класс довольно прост и красиво инкапсулирует свою роль без особого совпадения ролей.
Самая важная вещь — это обработка SAX — используя шаблон, приведенный выше, просто изменить программу, чтобы она соответствовала вашим требованиям. Одно из применений, к которым мы добавили XML, — это надежная связь между ASP и JSP, и, поскольку мы делаем информацию доступной через XML, мы обычно находим ее в другом месте.
Загрузите образцы файлов для этого урока здесь .