Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Модульная надстройка для JasperReports: подключение и что делает процессор при сборке. Часть 3

Дата публикации: 25-09-2026 18:24:29

Третья часть серии — практическая. Как подключить библиотеку к Maven-проекту, написать первый модульный отчёт и что после этого происходит с шаблоном при сборке.Субрепорт — это класс, отчёт — тоже класс, а поля класса и есть содержимое отчёта. Имена параметров шаблона берутся из имён полей, поэтому один и тот же модуль можно поставить в отчёт дважды и получить два независимых субрепорта со своими данными. Аннотационный процессор при компиляции находит существующий JRXML, дописывает в него недостающие параметры, датасеты и банды субрепортов и кладёт результат в target/generated-sources. Шаблон в src он не трогает никогда.Отдельно — про то, кто владеет шаблоном, когда в один файл пишут и кодогенератор, и человек в Jaspersoft Studio, и где у этой схемы слабое место.Первая часть — откуда взялся Jasper и почему он так устроен, вторая — один стандарт и три идеи. Читать далее

Основное содержимое страницы с новостью.

Уровень сложностиСредний

Время на прочтение6 мин

Охват и читатели8.4K

Обзор

Оглавление серии

  1. Откуда взялся Jasper и почему он так устроен

  2. Один стандарт и три идеи

  3. Подключение и что делает процессор при сборке — эта статья

  4. Коллекции, проверки и рантайм — скоро

  5. Прекомпиляция, версии движка и ограничения — скоро

Это третья часть. В первой — откуда взялся JasperReports и почему ему не хватало модульности, во второй — один стандарт и три идеи, на которых держится библиотека. Дальше идёт практика, по одной теме на часть. Здесь — как подключить библиотеку, написать первый модульный отчёт и что после этого происходит с шаблоном при сборке.

Всё ниже относится к версии 3.0.0. По сравнению с 2.0.x в ней появились повторяющиеся субрепорты из списков модулей, пропуск пустых модулей, датасеты для коллекций простых значений и сверка шаблона с классом при сборке, а имена параметров теперь берутся из полей. Про переход с 2.0.x — в последней части серии.

Требования: Java 17+ и JasperReports 6.x или 7.x — версию движка задаёте вы. Автоконфигурация и прекомпиляция приезжают со стартером, собранным под Spring Boot 3.3; ядро библиотеки от Spring не зависит вовсе и работает и без него.

Подключение

Стартер приносит ядро, процессор и автоконфигурацию, а заодно и сам JasperReports: в версии 3.0.0 движок приезжает транзитивно, версии 7.0.6. Поэтому объявляйте его явно и той версии, которая нужна вам — прямая зависимость перебивает транзитивную. Это не формальность: диалекты JRXML у шестой и седьмой версий несовместимы, и версия движка определяет, в каком из них процессор напишет ваш шаблон.

<dependency>
    <groupId>io.github.hhdevr</groupId>
    <artifactId>jasper-modular-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>${jasperreports.version}</version>
</dependency>

Аннотационный процессор подключается в компилятор вместе с той же версией JasperReports — он читает и пишет шаблоны её API:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>io.github.hhdevr</groupId>
                <artifactId>jasper-modular-processor</artifactId>
                <version>3.0.0</version>
            </path>
            <path>
                <groupId>net.sf.jasperreports</groupId>
                <artifactId>jasperreports</artifactId>
                <version>${jasperreports.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

В седьмой версии движка экспорт в PDF вынесен в отдельный артефакт jasperreports-pdf — в стартер он намеренно не включён, добавьте его сами. И укажите пакет с отчётами для прекомпиляции на старте:

jasper:
  modular:
    base-package: com.example.reports
Модуль и отчёт

Субрепорт — класс, унаследованный от SubreportModule и помеченный @JasperSubreport:

@Getter
@Setter
@AllArgsConstructor
@JasperSubreport(templatePath = "/reports/sub_items.jrxml")
public class ItemsModule extends SubreportModule {

    private List<LineItem> items;
    private BigDecimal subtotal;

    @Override
    public boolean isEmpty() {
        return items == null || items.isEmpty();
    }
}

isEmpty() говорит, что рендерить нечего: такой модуль пропускается целиком — в отчёт не уходят ни его шаблон, ни его данные.

Корневой отчёт — класс от ModularReport с @JasperModularReport. Его поля и есть содержимое отчёта:

@Getter
@Setter
@JasperModularReport(templatePath = "/reports/invoice.jrxml")
public class InvoiceReport extends ModularReport {

    private String customerName;
    private String invoiceNumber;
    private BigDecimal total;
    private ItemsModule items;
}

Геттеры и сеттеры здесь только для удобства: поля отчётов и модулей рантайм читает рефлексией напрямую. Геттеры обязательны в другом месте — у классов-элементов коллекций, об этом в следующей части.

Имена параметров — от имени поля

Скаляры становятся параметрами шаблона с теми же именами: customerName, invoiceNumber, total. Для поля items типа ItemsModule в шаблоне отчёта появятся два параметра: itemsReport — скомпилированный шаблон модуля и itemsMapParameter — карта его данных.

Имя берётся из поля, а не из класса модуля. Это важно, когда один модуль стоит в отчёте дважды:

private AddressModule billTo;   // billToReport, billToMapParameter
private AddressModule shipTo;   // shipToReport, shipToMapParameter

Два поля одного типа — два независимых субрепорта со своими данными. Если двум полям всё-таки достанется одно имя — например, поле с тем же именем объявлено и в родительском классе, — сборка упадёт и попросит переименовать одно из них.

Рендер
InvoiceReport report = new InvoiceReport();
report.setCustomerName("Acme Corp");
report.setInvoiceNumber("INV-001");
report.setTotal(new BigDecimal("1500.00"));
report.setItems(new ItemsModule(lineItems, subtotal));

JasperPrint print = new JasperModularRenderer().render(report);
byte[] pdf = JasperExportManager.exportReportToPdf(print);

render() возвращает стандартный JasperPrint, дальше — любой экспортёр JasperReports: PDF, XLSX, HTML.

Что происходит при сборке

По умолчанию процессор работает в режиме INJECT:

  1. Находит существующий шаблон — в target/classes, куда Maven кладёт ресурсы до компиляции, или на своём classpath.

  2. Сравнивает его с классом и дописывает то, чего не хватает: параметры, датасеты, компоненты коллекций, банды субрепортов. Всё, что уже есть, находится по имени и не трогается.

  3. Сверяет шаблон с классом — об этом отдельная часть серии.

  4. Кладёт результат в target/generated-sources/annotations по тому же пути. Шаблон в src процессор не меняет никогда.

Дальше вы открываете сгенерированный файл в Jaspersoft Studio, расставляете новые элементы и копируете его обратно в src/main/resources. Если шаблона ещё нет, процессор предупредит и сгенерирует заготовку с нуля.

Для поля items в шаблон отчёта добавится вот что (сокращённо, без uuid):

<parameter name="itemsReport" class="net.sf.jasperreports.engine.JasperReport"/>
<parameter name="itemsMapParameter" class="java.util.Map"/>

<band height="100" splitType="Stretch">
    <subreport>
        <reportElement positionType="Float" x="0" y="0" width="555" height="100" isRemoveLineWhenBlank="true"/>
        <parametersMapExpression><![CDATA[$P{itemsMapParameter}]]></parametersMapExpression>
        <dataSourceExpression><![CDATA[new net.sf.jasperreports.engine.JREmptyDataSource()]]></dataSourceExpression>
        <subreportExpression><![CDATA[$P{itemsReport}]]></subreportExpression>
    </subreport>
</band>

Здесь и работает механизм, ради которого всё затевалось. Субрепорт получает ровно два значения — свой скомпилированный шаблон и одну карту, а JasperReports сам раскладывает карту в параметры субрепорта: это встроенный механизм REPORT_PARAMETERS_MAP, о котором мало кто знает. В sub_items.jrxml при этом объявлены обычные параметры items и subtotal — их процессор тоже сгенерировал, из полей ItemsModule. Никаких <subreportParameter> по одному на каждое поле.

Режим задаётся в аннотации: INJECT — по умолчанию, как описано выше; CREATE — новая заготовка с чистого листа, существующий шаблон игнорируется, ориентация страницы берётся из orientation; NONE — процессор класс не трогает и не проверяет.

Кто владеет шаблоном

Если процессор пишет в шаблон, а человек правит тот же шаблон в Studio, чей это файл? Это классическая проблема кодогенерации в артефакт, который редактируют руками, так что стоит сказать, как библиотека делит работу.

Шаблон в src/main/resources принадлежит людям: разработчику и тому, кто верстает отчёт. Процессор туда не пишет никогда. При сборке он берёт шаблон, дописывает только недостающий вайринг и кладёт результат в target/generated-sources. Всё, что в шаблоне уже есть, он находит по имени и оставляет как было, так что вёрстка, стили и расставленные руками элементы не трогаются. Класс владеет именами и вайрингом, человек в Studio — вёрсткой.

Слабое место — обратный путь. Скопировать сгенерированный шаблон в ресурсы нужно руками, и сборка этот шаг не проверяет: забыли — новое поле просто не появится в отчёте. Плюс процессор переписывает файл целиком в своём форматировании, поэтому диф после копирования больше, чем само изменение. Следующее, что я планирую в библиотеке, — проверка, которая роняет сборку, если в шаблоне из src не хватает вайринга для поля.

В следующей части — коллекции, повторяющиеся субрепорты, что роняет сборку и как устроен рантайм.

Код — github.com/hhdevr/jasper-modular-library, пример — github.com/hhdevr/jasper-modular-sample.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Особенности агентской разработки ПО, обеспечивающей соответствие замыслу и требованиям07.3926-09-2026
2Ты не найдёшь эту ошибку. Потому что её нет в твоём коде. Как Self-describing API спасает от чужих рефакторингов5807-07-2026
3SQL Server Reporting Services — дюжина советов5718-04-2014
4Свободные функции вместо методов, но с полиморфизмом. Что это даёт на самом деле?07.5624-07-2026
5Формула «идеального enterprise» для open-source018.4712-08-2026
6Почему LLM нельзя просто подключить к базе данных и получить GenBI?07.724-07-2026
7[Перевод] Раньше ПО работало шустро, потому что иначе было никак0728-06-2026
8proxy_pass и fastcgi_pass — одна машина08.5306-08-2026
9Harness engineering: как за год собрать фабрику из десятка конвейеров05.8224-07-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 8.95. Источник: habr.com.