Отображение динамического изображения из базы данных с помощью p: graphicImage и StreamedContent

Я пытаюсь отобразить байты изображений, которые сохраняются в базе данных как StreamedContent в <p:graphicImage> следующим образом:

<p:graphicImage  value="#{item.imageF}" width="50"  id="grpImage" height="80"/>
private StreamedContent content; // getter and setter

public StreamedContent getImageF() {

    if (student.getImage() != null) {
        InputStream is = new ByteArrayInputStream(student.getImage());
        System.out.println("Byte :"+student.getImage());
        content = new DefaultStreamedContent(is, "", student.getStuID());
        System.out.println("ddd ------------------------------- " + content);
        return content;
    }

    return content;
}

Это возвращает пустое изображение. Как это вызвано и как я могу его решить?

Команда stdout печатает следующее:

INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]
INFO: Byte :[[email protected]
INFO: ddd ------------------------------- [email protected]

Ответы

Ответ 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();
    }

}

См. также блог по этому вопросу.

Ответ 2

Попробуйте включить тип mime. В вашем опубликованном примере у вас есть это как "". Пустое изображение может быть связано с тем, что он не распознает поток как файл изображения, так как вы сделали это поле пустой строкой. Поэтому добавьте тип mime image/png или image/jpg и посмотрите, работает ли это:

String mimeType = "image/jpg";
StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);  

Ответ 3

Здесь есть пара возможностей (и, пожалуйста, опубликуйте весь класс, если это не так).

1) Вы не инициализируете изображение правильно 2) Ваш поток пуст, поэтому вы ничего не получаете

Я предполагаю, что student.getImage() имеет подпись байта [], поэтому сначала убедитесь, что эти данные фактически нетронуты и представляют изображение. Во-вторых - вы не указываете тип контента, который должен быть "image/jpg" или все, что вы используете.

Вот какой шаблонный код для его проверки, я использую для этого Primefaces 2.

/** 'test' package with 'test/test.png' on the path */
@RequestScoped
@ManagedBean(name="imageBean")
public class ImageBean
{
    private DefaultStreamedContent content;

    public StreamedContent getContent()
    {
        if(content == null)
        {
            /* use your database call here */
            BufferedInputStream in = new BufferedInputStream(ImageBean.class.getClassLoader().getResourceAsStream("test/test.png"));
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            int val = -1;
            /* this is a simple test method to double check values from the stream */
            try
            {
                while((val = in.read()) != -1)
                    out.write(val);
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }

            byte[] bytes = out.toByteArray();
            System.out.println("Bytes -> " + bytes.length);
            content = new DefaultStreamedContent(new ByteArrayInputStream(bytes), "image/png", "test.png");
        }

        return content;
    }
}

и некоторая разметка...

<html 
    xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:p="http://primefaces.prime.com.tr/ui"
>

    <h:head>

    </h:head>

    <h:body>
        <p:graphicImage value="#{imageBean.content}" />
    </h:body>
</html>

Если этот код работает, тогда вы настроены правильно. Несмотря на то, что это код мусора для потоков (не используйте его на производстве), он должен дать вам возможность устранить неполадки. Я предполагаю, что вы можете что-то происходить в вашей JPA или другой базе данных, где вы байт [] пуст или он отформатирован неправильно. Кроме того, у вас может быть проблема с контентом.

Наконец, я бы клонировал данные из bean, чтобы student.getImage() был только скопирован в новый массив, а затем использован. Таким образом, если у вас что-то неизвестное происходит (что-то еще перемещает объект или меняет байт [], вы не возитесь с вашими потоками.

Сделайте что-то вроде:

byte[] data = new byte[student.getImage().length]
for(int i = 0; i < data.length; i++)
  data[i] = student.getImage()[i];

чтобы ваш bean имел копию (или Arrays.copy() - все, что плавает ваша лодка). Я не могу достаточно подчеркнуть, что что-то простое, как этот/тип контента, как правило, неверно. Удачи вам.

Ответ 4

Ответ от BalusC (как обычно) правильный.

Но сохраните одно (как уже было сказано им). Окончательный запрос выполняется из браузера, чтобы получить URL-адрес из созданного тега <img>. Это не выполняется в контексте jsf.

Итак, если вы попытаетесь, например, доступ к фазеId (протоколирование или любая другая причина)

context.getCurrentPhaseId().getName()

Это приведет к NullPointerException, и вы получите неверное сообщение об ошибке:

org.primefaces.application.resource.StreamedContentHandler () - Error in streaming dynamic resource. Error reading 'image' on type a.b.SomeBean

Мне потребовалось некоторое время, чтобы выяснить, в чем проблема.