В этой статье мы рассмотрим использование области беседы, определенной в JSR 299 (контексты Java и внедрение зависимостей) и выпущенной как часть Java EE 6. На данный момент мы будем придерживаться примеров, не основанных на данных, при рассмотрении входов и выходов области разговора. Мы закончим, создав менеджера рабочего пространства, чтобы мы могли перечислить все активные разговоры и переключаться между ними.
Разговор подобен нумерованному сегменту в сеансе, который существует до тех пор, пока не завершится сеанс пользователя, не истечет время разговора или код на стороне сервера не умышленно не завершит беседу. Преимущества диалоговой области действия многочисленны, что позволяет нам легко создавать страницы, которые можно открывать на нескольких вкладках, без необходимости манипулировать состоянием на клиенте, не заполняя сеанс данными, которые будут длиться до тех пор, пока пользовательский сеанс мы забудем удалить. Это.
Мы начнем с создания проекта из архетипа knappsack jee6-servlet-basic. Это дает нам все возможности Java EE 6 без слишком большого количества кода, чтобы мешать. Мы начнем с создания вспомогательного компонента, который находится в области запроса, и рассмотрим, как он используется и как влияет область действия запроса.
import java.io.Serializable; import javax.enterprise.context.RequestScoped; import javax.inject.Named; @Named("bean") @RequestScoped public class BackingBean implements Serializable { private Long value = new Long(0); public Long getValue() { return value; } public void setValue(Long value) { this.value = value; } public String getMessage() { return "The value is : "+value; } }
Достаточно просто, теперь давайте добавим страницу с именем listEdit.xhtml, которая позволит нам ввести значение, опубликовать его обратно и посмотреть, что это за сообщение.
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h:form id="form"> Value : <h:inputText value="#{bean.value}" /> <h:commandButton value="Submit Value" action="submit" /> <br /> <br /> Message : #{bean.message} </h:form> </ui:define> </ui:composition>
Отредактируйте home.xhtml, чтобы добавить ссылку на эту страницу:
<a href="listEdit.jsf">Start New List</a>
Откройте домашнюю страницу и нажмите на ссылку, чтобы перейти на страницу listEdit. Если мы введем значение в поле ввода и, как и ожидалось, нажмем кнопку отправки, в нашем сообщении будет указано, что значение равно тому, что мы ввели.
Здесь происходит следующее: когда форма отправляется обратно, значение в поле ввода отправляется обратно в атрибут bean.value. Когда страница отображается обратно пользователю и отображается сообщение, оно обрабатывается с использованием этого значения, которое мы передали обратно на сервер. Мы передаем состояние атрибута с сервера на клиент, а затем обратно на сервер, и мы можем делать это весь день, используя только объем запроса без каких-либо проблем. Это страница без сохранения состояния, которая не требует сохранения состояния на сервере.
Давайте добавим на страницу что-нибудь более динамичное, например список значений, которые мы добавили ранее. В нашем компоненте поддержки добавьте следующее поле, метод getter и метод для добавления значения.
private List<Long> values = new ArrayList<Long>(); .. .. .. public List<Long> getValues() { return values; } public void addToList() { values.add(value); }
Теперь мы добавим список значений на нашу страницу для отображения.
<ui:define name="content"> <h:form id="form"> Value : <h:inputText value="#{bean.value}" /> <h:commandButton value="Submit Value" action="#{bean.addToList}" /> <br /> <br /> Message : #{bean.message} <h2>Added Values</h2> <ui:repeat var="v_value" value="#{bean.values}"> #{v_value}<br /> </ui:repeat> </h:form> </ui:define>
Если мы обновим нашу страницу, введем номер и нажмем кнопку «Отправить», вы увидите, что номер появляется внизу страницы, где и должен. Когда мы нажимаем кнопку отправки в форме, значение присваивается атрибуту значения. Когда вызывается метод addToList, атрибут значения добавляется в список. Когда страница отображается, для генерации сообщения используется последнее значение, а для списка элементов мы возвращаем список, содержащий один элемент, новое значение.
Все идет нормально. Давайте введем новый номер и добавим его в список. Когда мы нажимаем кнопку второй раз, единственным номером в списке чисел является тот, который мы только что ввели. Первое значение ушло! Проблема в том, что во второй раз, когда мы опубликовали значение, бин был с нуля реконструирован и не имел представления о первом значении, которое мы добавили в список. В форме не было ничего, что могло бы сообщить бину о первом числе, хотя мы отображали его на странице. Поддерживающий компонент продолжает забывать наш список чисел от одного запроса к другому, или, другими словами, в нашем приложении отсутствует какое-либо состояние.
Согласно части 1, есть несколько способов обойти эту проблему. Мы могли бы сохранять список в базе данных каждый раз, когда мы публикуем, что слишком сложно для этой простой задачи. Мы могли бы передать состояние клиенту, скажем, используя список разделенных запятыми существующих чисел, помещенных в скрытое поле. Когда форма публикуется, список с разделителями-запятыми отправляется обратно на сервер, где номера распаковываются, а список перестраивается на стороне сервера и восстанавливается состояние.
Оба эти двух метода выполнимы, но что произойдет, когда у нас будет больше информации о сервере, которая нам нужна для хранения? Должны ли мы добавлять и добавлять каждую часть информации в состояние клиента вручную, в том числе передавать ее с одной страницы на другую для многостраничных процессов? Что если это список объектов сущностей, которые мы хотим сохранить? Мы просто сохраняем идентификаторы на клиенте и продолжаем перезагружать их из базы данных каждый раз?
Эти решения нецелесообразны с точки зрения быстрого создания поддерживаемого приложения. Тем не менее, обратите внимание, что эти решения полностью возможны с JSF и CDI. Он просто предлагает лучшие альтернативы, но всегда есть возможность взять под капот и использовать более низкоуровневые решения в интересах оптимизации приложения. Это важный фактор, так как если у вас есть широко используемая диалоговая страница, которая создает бутылочное горлышко, вы можете реорганизовать ее, чтобы использовать подход без сохранения состояния.
Поскольку эта статья посвящена разговорам, очевидно, что мы собираемся решить наши проблемы с помощью области разговоров. Мы изменим наш компонент на @ConversationScoped и @Inject — экземпляр беседы, который мы будем использовать. Затем мы начнем разговор для страницы, когда будет создан наш компонент. Это пример, и обычно вы не начинаете разговоры о построении бина, но это служит нашей цели здесь.
Совет : Обычно диалоги запускаются в определенных точках определенных методов инициализации, поэтому запускать их при построении bean-компонента плохо, потому что вы никогда не знаете, когда другая задача может использовать экземпляр этого bean-компонента.
@Named("bean") @ConversationScoped public class BackingBean implements Serializable { ... ... ... @Inject private Conversation conversation; ... ... @PostConstruct public void postConstruct() { conversation.begin(); } public Conversation getConversation() { return conversation; } ... ... }
Перезагрузите нашу страницу, и вы сможете увидеть, как вы можете ввести и отправить столько номеров, сколько вы хотите, на страницу и управлять состоянием списка.
Разговоры — это просто естественное продолжение существующих областей в современных веб-приложениях. Они также становятся очень естественными в использовании. Давайте добавим еще одну страницу, которая позволит нам просмотреть наш список номеров, прежде чем «подтвердить» их. Добавьте следующую новую кнопку внизу нашей страницы:
<h:commandButton action="review" value="Review Numbers"/>
Поскольку для атрибута action установлено значение review, мы создадим новую страницу с именем review.xhtml, которая будет отображать числа. Наше первое беспокойство, очевидно, заключается в том, «откуда он получает список чисел?», Хорошо, ответ: там же, где он получил числа в прошлый раз, используя выражение # {bean.values}.
review.xhtml
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" template="/WEB-INF/templates/template.xhtml"> <ui:define name="content"> <h:form id="form"> <h1>Please Confirm The Values : </h1> <h2>Added Values</h2> <ui:repeat var="v_value" value="#{bean.values}"> #{v_value}<br /> </ui:repeat> <h:commandButton action="#{bean.confirm}" value="Confirm"/> </h:form> </ui:define> </ui:composition>
The way to think about this is to imagine what happens when the page is rendered. JSF will look for the value beans.values which the CDI implementation can provide. Since this page is being rendered in an active conversation, CDI will look in the active conversation ‘bucket’ for a bean called bean. Since we are coming from the page that adds the numbers, this bean will already exist in the conversation and so the same instance is returned. Should we render this page without an active conversation, by typing in the URL manually, the page will render, but this time, the instance of beans.xml will not already exist and so CDI will create a new instance of it that doesn’t contain any items.
Tip : There are ways to prevent pages rendering without an active conversation which can also help with duplicate form submissions
Возвращаясь к странице, мы просто хотим добавить наш метод подтверждения к нашему компоненту поддержки. Этот метод завершает разговор и перенаправляет на домашнюю страницу, которая возвращается на страницу ввода номера.
public void confirm() { conversation.end(); try { FacesContext.getCurrentInstance().getExternalContext().redirect("home.jsf?faces-redirect=true"); } catch (IOException e) { e.printStackTrace(); } }
Добавьте следующее в верхней части страницы редактирования или списка редактирования, чтобы увидеть идентификатор разговора, в котором вы сейчас находитесь:
Conversation : #{bean.conversation.id}
Если вы запустите приложение, вы можете добавить номера, а когда вы нажмете «Просмотр», вы получите список всех номеров, которые вы только что ввели на отдельной странице.
If we were managing state ourselves, we would have to pass something to the review page to let is know what the list of numbers was, either a database row key, or the comma delimited values. Again, the more state you have to pass around the less verbose and maintainable the application becomes. CDI is automatically passing our key (the conversation id) around for us. Under the review button the list edit page, add the following GET link :
<h:link outcome="review" value="Review"/>
If you look at the link URL it is http://localhost:8080/conversationdemo/review.jsf?cid=xxx where xxx is some number. CDI automatically creates a URL that propagates the conversation for us. If we didn’t want to propagate the conversation, we can just add a blank cid parameter and CDI will leave it alone. This can be useful when you want to launch a page without a conversation from a page that is in a conversation. Add the following link :
<h:link value="Start New List" target="_blank"> <f:param name="cid" /> </h:link>
Это позволяет начать новый новый список в отдельном окне (поскольку мы не указали результат, по умолчанию используется текущая страница). Если вы создадите новый список и добавите несколько чисел, вы увидите, что вы можете управлять отдельными списками независимо, ничего не делая для обработки нескольких окон браузера.
Когда у вас открыто несколько окон, установите URL-адрес одного окна на URL-адрес другого (т. Е. Http: // localhost: 8080 / разговорdemo / listEdit.jsf? Cid = xxx, где xxx — другой идентификатор разговора), когда вы загружаете эту страницу Вы видите список из другого разговора, так что вы можете просто переключать разговоры, используя запросы GET, указав другой идентификатор разговора.
Управление рабочей областью
One cool feature of Seam that had a lot of potential was workspace management which lets you see a list of active conversations and select one. We are going to implement a version of that here by providing the user with a set of links that takes us to the active conversation. Create a new bean that is session scoped that will keep a track of our conversation ids.
import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.enterprise.context.SessionScoped; import javax.inject.Named; @Named @SessionScoped public class WorkspaceBean implements Serializable { private List<String> conversations = new ArrayList<String>(); public List<String> getConversations() { return conversations; } }
This is just a bean that keeps a list of the conversation Ids currently active. When we start a conversation, we need to add that id to the list and when we end a conversation, we need to remove it from the list.
We will inject this session scoped bean into our backing bean and change the postConstruct and confirm methods in the BackingBean class to add and remove the conversation from the list.
@Inject private WorkspaceBean workspace; ... ... @PostConstruct public void postConstruct() { conversation.begin(); workspace.getConversations().add(conversation.getId()); } public void confirm() { workspace.getConversations().remove(conversation.getId()); conversation.end(); try { FacesContext ctx = FacesContext.getCurrentInstance(); ctx.getExternalContext().redirect("home.jsf?faces-redirect=true"); } catch (IOException e) { e.printStackTrace(); } }
Now we need some view code to display the list of conversations and provide links to them. Simply add this to the home.xhtml page, and even the listEdit.xhtml or review.xhtml pages if you want :
<h1>Workspaces</h1> <ui:repeat var="v_conv" value="#{workspaceBean.conversations}"> <h:link outcome="listEdit" value="Goto Conversation #{v_conv}"> <f:param name="cid" value="#{v_conv}" /> </h:link> <br /> </ui:repeat>
This just loops through the conversations and creates a link to the listEdit page and passes the conversation Id as a parameter. Because we pass a value for cid CDI will not try to add the current conversation as the cid value.
That’s all you need for a workspace demonstration. You can create new lists and if you jump back to the home page, you will see the available conversations and be able to click the link and jump back into the conversation. When you review the list and confirm it and the conversation ends, when you end up back on the front page, that conversation is longer listed.
This is a fairly powerful mechanism that is built upon the simplicity of CDI Conversations. There is very little work that is required to use conversations, just inject the Conversation instance and call the begin() and end() methods when you need to start and end the conversation. Conversations also have a minimal impact on our code, mainly just an annotation since the getters and settings and other methods don’t change. We can also easily revert back to a stateless request scoped solution should we have to which would cause additional code to be required to read/write the list to the client.
Conversations should be used with care, especially in terms of making sure you have strict demarcation boundaries, and careful consideration should be given to determining what you include in a conversation.
You can download the maven project source code for this demo (Conversation Demo ), just unzip it and enter mvn jetty:run and navigate to http://localhost:8080/conversationdemo/home.jsf.
From http://www.andygibson.net/blog/tutorial/cdi-conversations-part-2/