Я был не совсем доволен ответом lkdg, потому что хотел отделить задачу загрузки правильного файла от дизайна, так как, по моему мнению, я не должен быть вынужден организовывать из того места, где отчеты загружаются во время разработки файлов JRXML.
К сожалению, код библиотеки Jasper полон статических ссылок, которые затрудняют поиск правильного места для загрузки пользовательского загрузчика подотчета, а также отстой некоторых документов (например, интерфейс RepositoryService
полностью отсутствует документация по контракту, поэтому мне нужно было угадать контракт, прочитав код вызова), но это возможно:
private static void fillReport() throws IOException, JRException {
// The master report can be loaded just like that, because the
// subreports will not be loaded at this point, but later when
// report is filled.
final JasperReport report = loadReport("masterReport.jasper");
// The SimpleJasperReportsContext allows us to easily specify some
// own extensions that can be injected into the fill manager. This
// class will also delegate to the DefaultJasperReportsContext and
// combine results. Thus all the default extensions will still be available
SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
jasperReportsContext.setExtensions(
RepositoryService.class, singletonList(new SubReportFindingRepository())
);
final byte[] pdf = JasperExportManager.exportReportToPdf(
JasperFillManager
.getInstance(jasperReportsContext)
// carefully select the correct `fill` method here and don't
// accidentally select one of the static ones!:
.fill(report, YOUR_PARAMS, YOUR_CONNECTION)
);
}
private static JasperReport loadReport(final String fileName) throws IOException, JRException {
try(InputStream in = loadReportAsStream(fileName)) {
return (JasperReport) JRLoader.loadObject(in);
}
}
private static InputStream loadReportAsStream(final String fileName) {
final String resourceName = "/package/path/to/reports/" + fileName;
final InputStream report = CurrentClass.class.getResourceAsStream(resourceName);
if (report == null) {
throw new RuntimeException("Report not found: " + resourceName);
}
return report;
}
private static class SubReportFindingRepository implements RepositoryService {
@Override
public Resource getResource(final String uri) {
return null; // Means "not found". The next RepositoryService will be tried
}
@Override
public void saveResource(final String uri, final Resource resource) {
throw new UnsupportedOperationException();
}
@Override
public <K extends Resource> K getResource(final String uri, final Class<K> resourceType) {
if (!isKnownSubReport(uri)) {
return null; // Means "not found". The next RepositoryService will be tried
}
final ReportResource reportResource = new ReportResource();
try {
reportResource.setReport(loadReport(uri));
} catch (IOException | JRException e) {
throw new Error(e);
}
return resourceType.cast(reportResource);
}
private static boolean isKnownSubReport(final String uri) {
return "subReport1.jasper".equals(uri) || "subReport2.jasper".equals(uri);
}
}
В качестве альтернативы локальному внедрению вы также можете написать глобальное расширение .Насколько я понял (я не пробовал), это требует создания файла jasperreports_extension.properties
с именами классов, которые должны быть загружены, которые могут включать в себя собственный репозиторий для загрузки отчетов.Однако в этом случае вы полностью теряете способность работать с конфликтующими конфигурациями, необходимыми в разных случаях использования.