Отображение динамического изображения из базы данных с помощью 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 :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@b0887b
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1d06a92
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@39a60
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@8c3daa
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1dbe05b
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@66a266
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1293976
INFO: Byte :[B@a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@17b7399
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1e245a5
INFO: Byte :[B@d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@4a7153
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1561bfd
INFO: Byte :[B@124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@47a8c2

4 ответа

<p:graphicImage> требует специального метода получения. Именно он будет вызываться дважды для каждого сгенерированного изображения, каждое в совершенно другом HTTP-запросе.

Первый HTTP-запрос, который запросил HTML-результат страницы JSF, впервые вызовет геттер для генерации HTML-кода. <img> элемент с правильным уникальным и автоматически сгенерированным URL в src Атрибут, который содержит информацию о том, какие именно bean и getter должны вызываться всякий раз, когда веб-браузер собирается запросить изображение. Обратите внимание, что в этот момент геттеру не нужно возвращать содержимое изображения. Он не будет использоваться никоим образом, так как HTML не работает (изображения не "встроены" в вывод HTML, но вместо этого они запрашиваются отдельно).

Как только веб-браузер извлекает результат HTML как ответ HTTP, он анализирует источник HTML, чтобы визуально представить результат конечному пользователю. Как только веб-браузер встречает <img> элемент во время синтаксического анализа источника HTML, то он отправит новый запрос HTTP на URL, как указано в его src атрибут для того, чтобы загрузить содержимое этого изображения и вставить его в визуальное представление. Это вызовет метод получения во второй раз, который, в свою очередь, должен вернуть фактическое содержимое изображения.

В вашем конкретном случае PrimeFaces, по-видимому, либо не смог идентифицировать и вызвать геттер для извлечения фактического содержимого изображения, либо получатель не возвратил ожидаемый контент изображения. Использование #{item} имя переменной и количество вызовов в журнале говорит о том, что вы использовали его в <ui:repeat> или <h:dataTable>, Скорее всего, компонент поддержки имеет область запроса, и модель данных не сохраняется должным образом во время запроса изображения, и JSF не сможет вызвать геттер во время правильного цикла итерации. Bean-объект области видимости также не будет работать, поскольку состояние просмотра JSF нигде не доступно, когда браузер фактически запрашивает изображение.


Чтобы решить эту проблему, лучше всего переписать метод get как таковой, чтобы его можно было вызывать для каждого запроса в отдельности, где вы передаете уникальный идентификатор изображения как <f:param> вместо того, чтобы полагаться на некоторые свойства bean-компонента, которые могут "не синхронизироваться" во время последующих HTTP-запросов. Для этого было бы вполне разумно использовать отдельный управляемый bean-компонент, который не имеет какого-либо состояния. Кроме того, InputStream может быть прочитан только один раз, а не несколько раз.

Другими словами: никогда не объявлять StreamedContent ни какой InputStream или даже UploadedFile как свойство бобов; только создать его совершенно новым в сборщике лиц без гражданства @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 боб может выглядеть так:

@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> работает под одеялом. Вызывает бизнес-логику в методах получения, как правило, неодобрительно, см. Также Почему JSF вызывает методы получения несколько раз. Не используйте этот особый случай в качестве оправдания для других стандартных (не специальных) случаев. Также обратите внимание, что вы не можете использовать функцию EL 2.2 для передачи аргументов метода, например, так #{studentImages.image(student.id)} потому что этот аргумент не будет в конечном итоге в URL изображения. Таким образом, вам действительно нужно передать их как <f:param>,


Если вам случится использовать OmniFaces 2.0 или новее, рассмотрите возможность использования его <o:graphicImage> вместо этого, который может быть использован более интуитивно, с помощью метода get в области приложения, непосредственно делегирующего методу сервиса и поддерживающего аргументы метода 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();
    }

}

Смотрите также блог на эту тему.

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

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

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

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

Я предполагаю, что student.getImage() имеет подпись byte[], поэтому сначала убедитесь, что эти данные на самом деле не повреждены и представляют изображение. Во-вторых, вы не указываете тип контента, который должен быть "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];

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

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

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

Так что если вы попытаетесь, например, получить доступ к phaseId (ведение журнала или любая другая причина)

context.getCurrentPhaseId().getName()

Это приведет к NullPointerException и как-то вводящее в заблуждение сообщение об ошибке:

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

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

Другие вопросы по тегам