Создание контекстов приложений
Конструктор контекста приложения (для определенного типа контекста приложения) обычно принимает строку или массив строк в качестве путей расположения ресурсов, таких как XML-файлы, которые составляют определение контекста.
Если такой путь расположения не имеет префикса, то конкретный тип Resource
, построенный на основе этого пути и используемый для загрузки определений бинов, будет зависеть от конкретного контекста приложения и соответствовать ему. Например, рассмотрим следующий пример, который создает ClassPathXmlApplicationContext
:
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("conf/appContext.xml")
Определения бинов загружаются из пути классов, поскольку используется ClassPathResource
. Однако рассмотрим следующий пример, который создает FileSystemXmlApplicationContext
:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("conf/appContext.xml")
Теперь определения бинов загружаются из расположения файловой системы (в данном случае относительно текущего рабочего каталога).
Обратите внимание, что использование специального префикса classpath
или стандартного URL-префикса в пути расположения переопределяет тип URL
по умолчанию, созданный для загрузки определений бинов. Рассмотрим следующий пример:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
val ctx = FileSystemXmlApplicationContext("classpath:conf/appContext.xml")
Использование FileSystemXmlApplicationContext
загружает определения бинов из пути классов. Однако это все еще FileSystemXmlApplicationContext
. Если этот контекст впоследствии использовать как ResourceLoader
, все пути без префиксов будут считаться путями к файловой системе.
Построение экземпляров ClassPathXmlApplicationContext
— сокращения
ClassPathXmlApplicationContext
открывает ряд конструкторов для удобного создания экземпляра. Основная идея заключается в том, что можно просто указать строковый массив, содержащий только имена файлов XML (без информации о ведущем пути), а также предоставить Class
. Затем ClassPathXmlApplicationContext
извлечет информацию о пути из предоставленного класса.
Рассмотрим следующую схему каталога:
com/ example/ services.xml repositories.xml MessengerService.class
В следующем примере показано, как можно создать экземпляр ClassPathXmlApplicationContext
, состоящий из бинов, определенных в файлах services.xml
и repositories.xml
(которые находятся в пути классов):
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[] {"services.xml", "repositories.xml"}, MessengerService.class);
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "repositories.xml"), MessengerService::class.java)
См. javadoc по ClassPathXmlApplicationContext
для получения подробной информации о различных конструкторах.
Подстановочные знаки (wildcards) в путях ресурсов конструктора контекста приложения
Пути к ресурсам в значениях конструктора контекста приложения могут быть простыми путями (как было показано ранее), каждый из которых имеет отображение "один к одному" с целевым Resource
, или, как вариант, могут содержать специальный префикс classpath*:
или внутренние шаблоны в стиле Ant (сопоставленные с помощью служебной программы PathMatcher
в Spring). Оба последних варианта являются фактически подстановочными знаками (wildcards).
Один из вариантов использования этого механизма - необходимость выполнить сборку приложения в стиле компонента. Все компоненты могут публиковать фрагменты определения контекста по известному пути расположения, и когда окончательный контекст приложения создается по тому же пути с префиксом classpath*:
, все фрагменты компонентов автоматически перехватываются.
Обратите внимание, что эта подстановка специфична для использования путей ресурсов в конструкторах контекста приложения (или если используется иерархия служебного класса PathMatcher
напрямую) и разрешается во время построения. Данная подстановка не имеет никакого отношения к самому типу Resource
. Нельзя использовать префикс classpath*:
для создания фактического Resource
, так как ресурс указывает только на один ресурс за раз.
Шаблоны в стиле Ant
Расположение путей может содержать шаблоны в стиле Ant, как показано в следующем примере:
/WEB-INF/*-context.xml com/mycompany/**/applicationContext.xml file:C:/some/path/*-context.xml classpath:com/mycompany/**/applicationContext.xml
Если путь содержит шаблон в стиле Ant, распознаватель выполняет более сложную процедуру, пытаясь разрешить подстановочный знак. Он создает Resource
для пути до последнего сегмента, не содержащего подстановочные знаки, и получает из него URL. Если этот URL не является URL типа jar:
или специфичным для контейнера вариантом (например, zip:
в WebLogic, wsjar
в WebSphere и так далее), то из него можно получить java.io.File
, который используется для разрешения подстановочного знака путем обхода файловой системы. В случае URL типа jar распознаватель либо получает из него java.net.JarURLConnection
, либо вручную осуществляет синтаксический анализ URL-адреса jar, а затем обходит содержимое jar-файла, чтобы разрешить подстановочные знаки.
Влияние на переносимость
Если указанный путь уже является URL файла
(либо неявно, поскольку базовый ResourceLoader
является файловым, либо явно), подстановочные знаки гарантированно будут работать полностью переносимым образом.
Если указанный путь является местоположением classpath
, то распознавателю необходимо получить последний URL сегмента пути, не содержащий подстановочных знаков, выполнив вызов Classloader.getResource()
. Поскольку это просто узел пути (а не файл в конце), на самом деле не определено (в javadoc по ClassLoader
), какой именно URL возвращается в таком случае. На практике это всегда java.io.File
, представляющий каталог (где ресурс пути классов разрешается в расположение файловой системы) или какой-либо URL-адрес jar (где ресурс пути классов разрешается в расположение jar). Тем не менее, при этой операции возникает проблема переносимости.
Если URL-адрес jar получен для последнего сегмента, не содержащего подстановочных знаков, распознаватель должен мочь получить из него java.net.JarURLConnection
или вручную провести синтаксический анализ URL-адреса jar, чтобы иметь возможность просмотреть содержимое jar и разрешить подстановочный знак. Это работает в большинстве сред, но не работает в некоторых других, и мы настоятельно рекомендуем тщательно тестировать разрешение подстановочных знаков для ресурсов, поступающих из jar-файлов, в вашей конкретной среде, прежде чем полагаться на него.
classpath*:
Префикс
При построении контекста приложения на основе XML строка расположения может задействовать специальный префикс classpath*:
, как показано в следующем примере:
ApplicationContext ctx =
new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");
val ctx = ClassPathXmlApplicationContext("classpath*:conf/appContext.xml")
Этот специальный префикс указывает, что все ресурсы пути классов, которые соответствуют данному имени, должны быть получены (внутренне это происходит через вызов ClassLoader.getResources(…)
и затем объединены для формирования окончательного определения контекста приложения.
getResources()
базового ClassLoader
. Поскольку большинство серверов приложений в настоящее время предоставляют собственную реализацию ClassLoader
, логика работы может отличаться, особенно при работе с jar-файлами. Простой тест для проверки работы classpath*
заключается в использовании ClassLoader
для загрузки файла из jar в classpath : getClass().getClassLoader().getResources("<someFileInsideTheJar>")
. Попробуйте провести этот тест с файлами, которые имеют одинаковое имя, но находятся в двух разных местах – например, файлы с одинаковым именем и одинаковым путем, но в разных jar-файлах в пути классов. В случае, если возвращается несоответствующий результат, проверьте документацию сервера приложений на предмет настроек, которые могут влиять на логику работы ClassLoader
.Также можно комбинировать префикс classpath*:
с шаблоном PathMatcher
в остальной части пути расположения (например, classpath*:META-INF/*-beans.xml
). В этом случае стратегия разрешения довольно проста: Вызов ClassLoader.getResources()
используется на последнем сегменте пути, не содержащего подстановочных знаков, чтобы получить все согласованные ресурсы в иерархии загрузчика классов, а затем для каждого ресурса используется та же стратегия разрешения PathMatcher
, которая была описана ранее для подпути, содержащего подстановочные знаки.
Другие примечания, связанные с подстановочными знаками
Обратите внимание, что classpath*:
в сочетании с шаблонами в стиле Ant надежно работает только при наличии хотя бы одного корневого каталога перед запуском шаблона, если только фактические целевые файлы не находятся в файловой системе. Это означает, что такой шаблон, как classpath*:*.xml
, может получать файлы не из корня jar-файлов, а только из корня расширенных каталогов.
Способность Spring извлекать записи из пути классов берет свое начало от метода ClassLoader.getResources()
в JDK, который возвращает местоположение файловой системы только для пустой строки (указывающей на потенциальные корни для поиска). Spring также оценивает конфигурацию времени выполнения URLClassLoader
и манифест java.class.path
в jar-файлах, но это не гарантирует платформонезависимой логики работы.
Сканирование пакетов пути классов требует наличия соответствующих записей каталога в пути классов. Если вы собираете JAR с помощью Ant, не активируйте переключатель files-only
в задаче JAR. Кроме того, каталоги пути классов могут не открываться на основании политик безопасности в некоторых средах – например, это касается автономных приложений на JDK 1.7.0_45 и выше (что требует установки доверенной библиотеки ('Trusted-Library') в ваших манифестах. См. https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).
По пути модулей JDK 9 (Jigsaw) сканирование пути классов Spring в целом работает так, как ожидается. В этом случае также настоятельно рекомендуется размещать ресурсы в специальном каталоге, чтобы избежать вышеупомянутых проблем с переносимостью при поиске на уровне корня jar-файла.
Шаблоны в стиле Ant с classpath:
ресурсов не гарантируют поиск совпадающих ресурсов, если корневой пакет для поиска доступен в нескольких местах пути классов. Рассмотрим следующий пример расположения ресурса:
com/mycompany/package1/service-context.xml
Теперь рассмотрим путь в стиле Ant, который кто-то может использовать, чтобы попытаться найти этот файл:
classpath:com/mycompany/**/service-context.xml
Такой ресурс может существовать только в одном месте в пути классов, но когда для его разрешения используется такой путь, как в предыдущем примере, распознаватель работает с (первым) URL, возвращаемым getResource("com/mycompany");
. Если этот узел базового пакета существует в нескольких местах ClassLoader
, нужного ресурса может не быть в первом найденном расположении. Поэтому в таких случаях лучше использовать classpath*:
с тем же шаблоном в стиле Ant, который ищет все расположения пути классов, содержащие базовый пакет com.mycompany
: classpath*:com/mycompany/**/service-context.xml
.
Предостережения касательно FileSystemResource
FileSystemResource
, не присоединенный к FileSystemApplicationContext
(то есть если FileSystemApplicationContext
не является фактическим ResourceLoader
), обрабатывает абсолютные и относительные пути так, как вы того ожидаете. Относительные пути относятся к текущему рабочему каталогу, а абсолютные - к корню файловой системы.
Однако по причинам обратной совместимости (историческим) это не меняется, если FileSystemApplicationContext
является ResourceLoader
. FileSystemApplicationContext
принудительно делает так, что все подключенные экземпляры FileSystemResource
считают все пути расположения относительными, независимо от того, начинаются ли они с косой черты или нет. На практике это означает, что следующие примеры эквивалентны:
ApplicationContext ctx =
new FileSystemXmlApplicationContext("conf/context.xml");
val ctx = FileSystemXmlApplicationContext("conf/context.xml")
ApplicationContext ctx =
new FileSystemXmlApplicationContext("/conf/context.xml");
val ctx = FileSystemXmlApplicationContext("/conf/context.xml")
Следующие примеры также эквивалентны (хотя было бы логично, если бы они отличались, так как в одном случае они относительные, а в другом - абсолютные):
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("some/resource/path/myTemplate.txt")
FileSystemXmlApplicationContext ctx = ...;
ctx.getResource("/some/resource/path/myTemplate.txt");
val ctx: FileSystemXmlApplicationContext = ...
ctx.getResource("/some/resource/path/myTemplate.txt")
На деле, если нужны истинные абсолютные пути к файловой системе, следует избегать использования абсолютных путей с FileSystemResource
или FileSystemXmlApplicationContext
и принудительно использовать UrlResource
с помощью URL-префикса file:
В следующих примерах показано, как это сделать:
// фактический тип контекста не имеет значения, ресурс всегда будет UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt");
// фактический тип контекста не имеет значения, ресурс всегда будет UrlResource
ctx.getResource("file:///some/resource/path/myTemplate.txt")
// пусть в принудительном порядке этот FileSystemXmlApplicationContext загружает свое определение через UrlResource
ApplicationContext ctx =
new FileSystemXmlApplicationContext("file:///conf/context.xml");
// пусть в принудительном порядке этот FileSystemXmlApplicationContext загружает свое определение через UrlResource
val ctx = FileSystemXmlApplicationContext("file:///conf/context.xml")
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ