Ответ 1
Для <p:graphicImage>
требуется специальный метод getter. Это будет вызвано дважды для каждого сгенерированного изображения, каждый из которых имеет совершенно другой HTTP-запрос.
Первый HTTP-запрос, который запросил результат HTML на странице JSF, впервые вызовет getter для генерации элемента HTML <img>
с правильным уникальным и автоматически сгенерированным URL-адресом в src
, который содержит информацию о том, какой bean и getter должен быть вызван, когда веб-браузер собирается запросить изображение. Обратите внимание, что в данный момент геттер не должен возвращать содержимое изображения. Он не будет использоваться каким-либо образом, поскольку это не то, как работает HTML (изображения не "встроены" в вывод HTML, а вместо этого запрашиваются отдельно).
Как только веб-браузер получит результат HTML в качестве ответа HTTP, он проанализирует исходный HTML-код, чтобы визуально представить результат enduser. После того, как веб-браузер обнаружит элемент <img>
во время разбора источника HTML, он отправит новый новый HTTP-запрос по URL-адресу, указанному в его атрибуте src
, чтобы загрузить содержимое этого изображения и вставить его в визуальный презентация. Это вызовет метод getter во второй раз, который, в свою очередь, должен вернуть фактическое содержимое изображения.
В вашем конкретном случае PrimeFaces, по-видимому, либо не смог идентифицировать и вызвать геттер для получения фактического содержимого изображения, либо геттер не возвращал ожидаемое содержимое изображения. Использование имени переменной #{item}
и количества вызовов в журнале предполагает, что вы использовали его в <ui:repeat>
или <h:dataTable>
. Скорее всего, резервная копия bean является областью действия запроса, а датамодель не сохраняется должным образом во время запроса на изображение, и JSF не сможет вызывать получателя во время правильной итерации раунда. Объект с видимым охватом bean также не будет работать, поскольку состояние представления JSF нигде не доступно, когда браузер действительно запрашивает изображение.
Чтобы решить эту проблему, лучше всего переписать метод getter как таковой, чтобы он мог быть вызван по запросу, в котором вы передаете уникальный идентификатор изображения как <f:param>
вместо того, чтобы полагаться на некоторую поддержку bean свойства, которые могут "не синхронизироваться" во время последующих HTTP-запросов. Было бы совершенно разумно использовать отдельное приложение с ограниченным управлением bean для этого, у которого нет никакого состояния. Более того, InputStream
можно читать только один раз, а не несколько раз.
Другими словами: никогда не объявлять StreamedContent
, а не InputStream
или даже UploadedFile
как свойство bean; только создайте его совершенно новым в getter безстоящего @ApplicationScoped
bean, когда веб-браузер действительно запрашивает содержимое изображения.
например.
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<p:graphicImage value="#{studentImages.image}">
<f:param name="studentId" value="#{student.id}" />
</p:graphicImage>
</p:column>
</p:dataTable>
Где поддержка StudentImages
bean может выглядеть так:
@Named // Or @ManagedBean
@ApplicationScoped
public class StudentImages {
@EJB
private StudentService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");
Student student = studentService.find(Long.valueOf(studentId));
return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));
}
}
}
Обратите внимание, что это очень особый случай, когда выполнение бизнес-логики в методе геттера является полностью законным, учитывая, как <p:graphicImage>
работает под обложками. Вызов бизнес-логики в getters, как правило, не одобряется, см. Также Почему JSF вызывает геттеры несколько раз. Не используйте этот специальный случай в качестве оправдания для других стандартных (неспецифических) случаев. Также обратите внимание, что вы не можете использовать функцию EL 2.2 для передачи аргументов метода таким образом #{studentImages.image(student.id)}
, потому что этот аргумент не будет указан в URL-адресе изображения. Таким образом, вам действительно нужно передать их как <f:param>
.
Если вы используете OmniFaces 2.0 или более новый, рассмотрите возможность использования <o:graphicImage>
, которые могут быть использованы более интуитивно, с использованием метода getter с использованием метода, непосредственно передающего метод сервиса, и поддержки аргументов метода EL 2.2.
Таким образом:
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<o:graphicImage value="#{studentImages.getImage(student.id)}" />
</p:column>
</p:dataTable>
С
@Named // Or @ManagedBean
@ApplicationScoped
public class StudentImages {
@EJB
private StudentService service;
public byte[] getImage(Long studentId) {
return studentService.find(studentId).getImage();
}
}
См. также блог по этому вопросу.