Saved searches
Use saved searches to filter your results more quickly
Cancel Create saved search
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
YuraFedorov-tech/PreProject6
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch branches/tags
Branches Tags
Could not load branches
Nothing to show
Could not load tags
Nothing to show
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Cancel Create
- Local
- Codespaces
HTTPS GitHub CLI
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
Latest commit message
Commit time
README.md
PreProject6
Spring. Первые шаги
Spring Framework (сокр. Spring) — фреймворк для разработки энтерпрайз приложений. Спринг позволяет создавать масштабируемые и легко поддерживаемые решения, что сильно ускоряет разработку.
Данный фреймворк имеет высокую модульность, каждый модуль способен запускаться и выполнять свои задачи как самостоятельное приложение, либо же может использоваться в группе с другими модулями (фреймворки в фреймворке). Функционал спринга постоянно обновляется и дополняется, наиболее популярные модули реализуют функционал MVC, работы с базой данных, секьюрности приложений, обмена сообщений, шаблонизаторов и многого другого.
Как это работает
Bean — объект, управляемый спрингом. В спринге все будет бинами: и сервисы, и ДАО, и контроллеры (группы сервлетов).
DI-контейнер — технология, держащая в себе бины и отвечающая за их связь. Например дао подтягивать к сервису будет именно этот контейнер. Но вашим кодом это не ограничится, контейнер позволит запускать различные модули без жесткой связи между собой, так в модуль Security не потребуется передавать все контроллеры из модуля MVC, контейнер свяжет их неявно.
Контекст — окружение, в котором запускается приложение. Их реализовано более десятка, но чаще всего используется следующие 3 версии
— FileSystemXmlApplicationContext; — xml конфиг, раньше был распространен, сейчас устарел после ввода аннотаций в java 1.6 — AnnotationConfigApplicationContext; — современный контекст, основывается на аннотациях, изучать будем его. — StaticApplicationContext — переходный вариант, прямо в коде связываем бины, используется для тестирования
И в обратную сторону: Контейнер + метаданные (настройки) позволяют запустить образуют ваше приложение с контейнером, который управляет бинами.
Первая задача. HelloWorld.
Всего 3 класса и одна зависимость.
Класс App содержит метод мейн, с которого запускается спринг при создании контекста. В контекст мы передаем класс AppConfig, который помечен аннотацией @Configuration, так спринг понимает, что это настроечный класс.
В классе AppConfig содержится метод, помеченный аннотацией как бин, этот метод будет выполнен при запуске и его результат станет объектом управляемым спрингом.
Класс HelloWorld является обычным джава-классом, мы его используем без каких-либо сложностей.
В методе мейн мы можем получить бин ХеллоВорлд по имени и использовать в любом месте программы.
Спринг позволяет использовать различные типы бинов, одни будут жить от старта до завершения программы, другие будут создаваться при каждом реквесте или открытии новой сессии, третьи при каждом вызове будут создаваться новые. За это отвечает аннотация @Scope.
- Прочитайте про виды бинов
- Создайте POJO-class Cat
- в кассе AppConfig по аналогии создайте бин с именем “cat”.
- Настройте этот бин так, чтобы он создавался новым при каждом запросе.
- В классе App по аналогии вызовите еще раз бин HelloWorld, затем 2 раза вызовите бин cat. Сравните 2 пары бинов по ссылке и выведите результат в консоль. Для пары хэлловорлдов должно вернуться тру, для кэт — фоллзс. Так вы можете увидеть результат того, как работает наш контейнер.
Задача 2. Spring Beans
Теперь поработаем со связыванием бинов. Вызывать бины из контекста хорошо, но не так удобно, как связывать их автоматически.
Для работы аннотаций связывания, я добавил в мавен ядро спринга, так мы имеем совершенно рабочее спринг приложение, хоть и без дополнительных модулей.
В компоненте AnimalCage есть поле типа Animal, которое связано с помощью аннотации @Autowired. При выполнении программы DI-контейнер потянет сюда бин, подходящий по типу.
- Создайте бин Dog, унаследуйте его от Animal. Создайте на основе этого класса компонент.
- Запустите приложение и проверьте, что было выброшено исключение NoUniqueBeanDefinitionException. Это произошло из-за того, что по типу Animal существует 2 бина и спринг выбросит исключение. Прочитайте по связыванию бинов по имени и свяжите AnimalCage c бином Dog через интерфейс Animal.
- На основе класса Timer создайте бин. Свяжите с AnimalCage. Проверьте, что при выполнении метода мейн время, которое пишет таймер, одно и тоже.
Задача 3. Виды связывания бинов.
«На море на океане есть остров, на том острове дуб стоит, под дубом сундук зарыт, в сундуке — заяц, в зайце — утка, в утке — яйцо, в яйце — игла, — смерть Кощея»
Зависимости остались прежние. В упражнении появился компонент Кощей Бессмертный (KoscheiTheDeathless), который расскажет, где находится его смерть, если вызвать метод getRulesByDeth() . Для описания поиска смерти использованы классы в папке models. Для вашего удобства они были пронумерованы: Ocean1, Island2, Wood3 и так далее.
Чтобы спринг видел все бины, в классе AppConfig была добавлена аннотация @ComponentScan.
В спринге связать бины можно различными способами, рассмотрим их на примере:
- KoscheiTheDeathless связывается с Ocean1 через сеттер setOcean и аннотации аутовайрд над ней.
- Ocean1 связан с Island2 через поле и аннотацию, которая подтянет бин Island2 через метод getIsland() в классе AppConfig. Обратите внимание, что данный метод обозначен аннотацией @Bean, что автоматически подтягивает аргументы в метод. В качестве аргумента выступает бин Wood3
- бин Wood3 помечен компонентом, который конструируется через связывание по @Autowired конструктору.
Собрать цепочку до 8го элемента, использовав все вышеперечисленные методы связывания. После выполнения вы должны получить полную фразу.
Вопросы для самопроверки:
- Что такое бин
- Виды бинов
- Чем бин отличается от POJO класса?
- Что такое Inversion of Control и как Spring реализует этот принцип.
- Для чего существует такое количество ApplicationContext.
- Как можно связать бины
- Что такое Dependency Injection
- Какие бины будут использоваться для настройки приложения?
Как настроить класс как Spring Bean?
Термин «бин» используется в Spring для ссылки на любой компонент, который управляется контейнером. То есть бины – это объекты, являющиеся основой приложения и управляемые Spring IoC-контейнером. Как правило, на определенном уровне бины придерживаются спецификации JavaBean, однако это необязательно, особенно когда для связывания бинов друг с другом разработчик планирует использовать Constructor Injection.
Чтобы получить экземпляр бина, применяют ApplicationContext. При этом IoC-контейнер управляет жизненным циклом бина, а также областью видимости и внедрением.
Если говорить о конфигурационном файле Spring Bean, то он имеет особое значение. В частности, он определяет все бины, которые будут инициализированы в Spring Context. При этом в процессе создания экземпляра Spring ApplicationContext конфигурационный xml-файл будет прочитан, следовательно, будут выполнены необходимые инициализации, указанные в этом файле. Также в этом файле, отдельно от базовой конфигурации, могут быть настройки локализации, описания перехватчиков (interceptors) и прочее.
Итак, существует ряд способов работы с классами в Spring Framework:
- XML-конфигурация,
- Java based-конфигурация
- конфигурация Annotation based.
Давайте рассмотрим их чуть подробнее.
XML-конфигурация
Java based-конфигурация
В данном случае все настройки и указания бинов прописывают в Java-коде:
При этом для извлечения бина из контекста применяют следующий подход:
Annotation based-конфигурация
Ее можно использовать внутри кода аннотации @Repository, @Component, @Service, @Controller, что делается для указания классов в качестве Spring-бинов. А для их поиска и управления контейнером в xml-файл прописывают следующую настройку:
Spring
Какие методы реализации принципа Инверсия управления (IoC) вы знаете?
- Шаблон «Фабрика» (англ. Factory pattern)
- Локатор служб
- Внедрение зависимости (англ. Dependency injection)
- Контекстный поиск (англ. contextualized lookup)
Какими способами можно реализовать «внедрение зависимостей» в Java?
Что такое Spring?
Spring Framework (или коротко Spring) — универсальный фреймворк с открытым исходным кодом для Java-платформы. Центральной частью Spring является контейнер Inversion of Control, который предоставляет средства конфигурирования и управления объектами Java с помощью рефлексии. Контейнер отвечает за управление жизненным циклом объекта: создание объектов, вызов методов инициализации и конфигурирование объектов путём связывания их между собой.
Spring имеет множество дочерних под проектов, в том числе:
- Data — для работы с хранилищами данных.
- MVC — для создания веб приложений.
- Boot — для быстрой компоновки и создания приложения на основе других Spring проектов.
- Cloud — для создания распределённой приложений.
Какие основные отличия в версиях Spring?
- Версия 3 (2009) — Поддержка Java 5 (annotations, generics, varargs, …).
- Версия 4 (2016) — Поддержка Java 8 (lambda, stream api, …).
- Версия 5 (2017) — Построен на основе Reactive Streams .
В чем разница между Inversion of Control и Application Context?
IoC — инверсия управления. Вместо ручного внедрения зависимостей, фреймворк забирает ответственность за это. ApplicationContext — реализация IoC в Spring. Bean Factory — это базовая версия IoC контейнера Application Context также включает дополнительные функции, которые обычно нужны для разработки корпоративных приложений
В чем различие между web.xml и the Spring Context — servlet.xml?
web.xml — Метаданные и конфигурация любого веб-приложения, совместимого с Java EE. Java EE стандарт для веб-приложений. servlet.xml — файл конфигурации, специфичный для Spring Framework.
Сравните Application Context, IoC Container, vs Web Container и EJB Container. Нужен ли Web Container для запуска Spring Boot приложения?
Web Container и EJB Containers являются частью приложения/веб-сервера, таких как Tomcat, Websphere, Weblogic. Они добавляют свою дополнительную функциональность к ним. Java EE определяет контракт для веб-приложений, эти контейнеры являются реализацией этих контрактов.
Spring контейнер может являться частью любого приложения, которое вы делаете на java. Spring может работать внутри веб-контейнера, ejb контейнера или даже без них.
Как происходит запуск IoC-контейнера Spring?
- Парсинг конфигурации и создание BeanDefinition — конфигурация с помощью XNL, аннотаций, JavaConfig.
- Настройка созданных BeanDefinition — на данном этапе происходит настройка еще не созданных бинов через классы, реализующие BeanFactoryPostProcessor . Например, PropertySourcesPlaceholderConfigurer
- Создание кастомных FactoryBean — FactoryBean — это generic интерфейс, которому можно делегировать процесс создания бинов.
- Создание экземпляров бинов — созданием экземпляров бинов занимается BeanFactory при этом, если нужно, делегирует это кастомным FactoryBean . Экземпляры бинов создаются на основе ранее созданных BeanDefinition .
- Настройка созданных бинов — Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки ваших бинов до того, как они попадут в контейнер. Интерфейс несет в себе несколько методов.
Какие способы конфигурирование Spring существуют?
- XML конфигурация — ClassPathXmlApplicationContext(“context.xml”) . Используется класс — XmlBeanDefinitionReader , который реализует интерфейс BeanDefinitionReader . Тут все достаточно прозрачно. XmlBeanDefinitionReader получает InputStream и загружает Document через DefaultDocumentLoader . Далее обрабатывается каждый элемент документа и если он является бином, то создается BeanDefinition на основе заполненных данных (id, name, class, alias, init-method, destroy-method и др.). Каждый BeanDefinition помещается в Mindmap. Mindmap хранится в классе DefaultListableBeanFactory .
- Аннотация/JavaConfig — с указанием пакета для сканирования — AnnotationConfigApplicationContext(“package.name”) или через аннотации с указанием класса (или массива классов) помеченного аннотацией @Configuration — AnnotationConfigApplicationContext(JavaConfig.class) . Внутри AnnotationConfigApplicationContext , то можно увидеть два поля.
private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner;
ClassPathBeanDefinitionScanner сканирует указанный пакет на наличие классов помеченных аннотацией @Component (или любой другой аннотацией которая включает в себя @Component ). Найденные классы разбираются и для них создаются BeanDefinition. Чтобы сканирование было запущено, в конфигурации должен быть указан пакет для сканирования. @ComponentScan() или
AnnotatedBeanDefinitionReader работает в несколько этапов. Первый этап — это регистрация всех @Configuration для дальнейшего разбора. Если в конфигурации используются Conditional, то будут зарегистрированы только те конфигурации, для которых Condition вернет true. Аннотация Conditional появилась в четвертой версии Spring. Она используется в случае, когда на момент поднятия контекста нужно решить, создавать бин/конфигурацию или нет. Причем решение принимает специальный класс, который обязан реализовать интерфейс Condition. Второй этап — это регистрация специального BeanFactoryPostProcessor , а именно BeanDefinitionRegistryPostProcessor , который при помощи класса ConfigurationClassParser разбирает JavaConfig и создает BeanDefinition.
- Groovy конфигурация — GenericGroovyApplicationContext(“context.groovy”) . Данная конфигурация очень похожа на конфигурацию через Xml, за исключением того, что в файле не XML, а Groovy. Чтением и анализом groovy конфигурации занимается класс GroovyBeanDefinitionReader.
Что предпочитаете использовать для конфигурации Spring?
Предпочитаю аннотации, если кодовая база хорошо описывается такими элементами, как @Service , @Component , @Autowired Однако когда дело доходит до конфигурации, у меня нет каких-либо предпочтений. Я бы оставил этот вопрос команде.
Что такое BeanPostProcessor ?
Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки ваших бинов до того, как они попадут в контейнер. Интерфейс несет в себе несколько методов.
public interface BeanPostProcessor Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; >
Оба метода вызываются для каждого бина. У обоих методов параметры абсолютно одинаковые. Разница только в порядке их вызова. Первый вызывается до init-метода, второй, после. Важно понимать, что на данном этапе экземпляр бина уже создан и идет его дополнительная настройка. Тут есть два важных момента:
- Оба метода в итоге должны вернуть бин. Если в методе вы вернете null, то при получении этого бина из контекста вы получите null, а поскольку через BeanPostProcessor проходят все бины, после поднятия контекста, при запросе любого бина вы будете получать фиг, в смысле null.
- Если вы хотите сделать прокси над вашим объектом, то имейте ввиду, что это принято делать после вызова init метода, иначе говоря это нужно делать в методе postProcessAfterInitialization .
Процесс дополнительной настройки показан на рисунке ниже. Порядок в котором будут вызваны BeanPostProcessor не известен, но мы точно знаем что выполнены они будут последовательно.
Для чего нужен Component Scan?
Первый шаг для описания Spring Beans это добавление аннотации — @Component , или @Service , или @Repository . Однако Spring ничего не знает об этих бинах, если он не знает где искать их. То, что скажет Spring где искать эти бины и называется Component Scan. В @ComponentScan вы указываете пакеты, которые должны сканироваться. Spring будет искать бины не только в пакетах для сканирования, но и в их подпакетах.
В чём отличие между @Component и @ComponentScan ?
@Component помечает класс в качестве кандидата для создания Spring бина. @ComponentScan указывает где Spring искать классы, помеченные аннотацией @Component или его производной
Для чего используется аннотация @Bean ?
В классах конфигурации Spring, @Bean используется для определения компонентов с кастомной логикой.
В чём разница между @Bean и @Component ?
@Bean используется в конфигурационных классах Spring. Он используется для непосредственного создания бина. @Component используется со всеми классами, которыми должен управлять Spring. Когда Spring видит класс с @Component , Spring определяет этот класс как кандидата для создания bean.
В чём разница между @Component , @Service и @Repository аннотациями?
Все они определяют бины Spring. Однако между ними всё же есть разница.
@Component — универсальный компонент @Repository — компонент, который предназначен для хранения, извлечения и поиска. Как правило, используется для работы с базами данных. @Service — фасад для некоторой бизнес логики
Пользовательские аннотации, производные от @Component, могут добавлять специальную логику в бинах. Например, бины, получившиеся при помощи @Repository, дополнительно имеют обработку для JDBC Exception
Можем ли мы использовать @Component вместо @Service для бизнес логики?
Если @Component является универсальным стереотипом для любого Spring компонента, то @Service в настоящее время является его псевдонимом. Однако в официальной документации Spring рекомендуется использовать именно @Service для бизнес логики. Вполне возможно, что в будущих версиях фреймворка, для данного стереотипа добавится дополнительная семантика, и его бины станут обладать дополнительной логикой.
Можем ли мы применить @Autowired с не сеттерами и не конструкторами методами?
@Autowired может использоваться вместе с конструкторами, сеттерами или любым другими методами. Когда Spring находит @Autowired на методе, Spring автоматически вызовет этот метод, после создания экземпляра бина. В качестве аргументов, будут подобраны подходящие объекты из контекста Spring.
В чем разница между Сквозной Функциональностью (Cross Cutting Concerns) и АОП (аспектно ориентированное программирование)?
Сквозная Функциональность — функциональность, которая может потребоваться вам на нескольких различных уровнях — логирование, управление производительностью, безопасность и т.д. АОП — один из подходов к реализации данной проблемы
Почему возвращаемое значение при применении аспекта @Around может потеряться? Назовите причины
Метод, помеченный аннотацией @Around, должен возвращать значение, которое он (метод) получил из joinpoint.proceed()
@Around("trackTimeAnnotation()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable long startTime = System.currentTimeMillis(); Object retVal = joinPoint.proceed(); long timeTaken = System.currentTimeMillis() - startTime; logger.info("Time taken by <> is equal to <>",joinPoint, timeTaken); return retVal; >
Как вы решаете какой бин внедрить, если у вас несколько подходящих бинов. Расскажите о @Primary и @Qualifier ?
Если есть бин, который вы предпочитаете большую часть времени по сравнению с другими, то используйте @Primary , и используйте @Qualifier для нестандартных сценариев.
Если все бины имеют одинаковый приоритет, мы всегда будем использовать @Qualifier
Если бин надо выбрать во время исполнения программы, то эти аннотации вам не подойдут. Вам надо в конфигурационном классе создать метод, пометить его аннотацией @Bean , и вернуть им требуемый бин.
Как вы добавите Component Scan в Spring Boot?
@SpringBootApplication public class Application public static void main(String[] args) SpringApplication.run(Application.class, args); > >
@SpringBootApplication определяет автоматическое сканирование пакета, где находится класс Application.
Какие возможности предоставляет аннотация @Controller ?
@Controller — уточнение интерфейса @Component . Экземпляр класса, помеченного такой аннотация, также становиться бином в Spring. Используется для указание точек взаимодействия с приложением в Spring MVC. Обычно используется с аннотацией @RequestMapping .
В чём разница между @Controller и @RestController ?
@RestController = @Controller + @ResponseBody
@RestController превращает помеченный класс в Spring-бин. Этот бин для конвертации входящих/исходящих данных использует Jackson message converter. Как правило целевые данные представлены в json или xml.
Как мы можем выбрать подходящий бин при помощи application.properties?
interface GreetingService public String sayHello(); >
и два компонента
@Component(value="real") class RealGreetingService implements GreetingService public String sayHello() return "I'm real"; > >
@Component(value="mock") class MockGreetingService implements GreetingService public String sayHello() return "I'm mock"; > >
Тогда в application.properties добавим свойство application.greeting: real
Воспользуемся данным решением:
@RestController public class WelcomeController @Resource(name="$") private GreeterService service1; >
Почему иногда мы используем @ResponseBody , а иногда ResponseEntity ?
ResponseEntity необходим, только если мы хотим кастомизировать ответ, добавив к нему статус ответа. Во всех остальных случаях будем использовать @ResponseBody .
@GetMapping(value=”/resource”) @ResponseBody public Resource sayHello() return resource; > @PostMapping(value=”/resource”) public ResponseEntity createResource() …. return ResponseEntity.created(resource).build(); >
Стандартные HTTP коды статусов ответов, которые можно использовать. 200 — SUCCESS 201 — CREATED 404 — RESOURCE NOT FOUND 400 — BAD REQUEST 401 — UNAUTHORIZED 500 — SERVER ERROR
Для @ResponseBody единственные состояния статуса это SUCCESS(200), если всё хорошо и SERVER ERROR(500), если произошла какая-либо ошибка.
Допустим мы что-то создали и хотим отправить статус CREATED(201). В этом случае мы используем ResponseEntity .
В чем разница между Filters, Listeners and Interceptors?
Концептуально всё просто, фильтры сервлетов могут перехватывать только HTTPServlets. Listeners могут перехватывать специфические события. Как перехватить события которые относятся ни к тем не другим?
Фильтры и перехватчики делают по сути одно и тоже: они перехватывают какое-то событие, и делают что-то до или после. Java EE использует термин Filter, Spring называет их Interceptors. Именно здесь AOP используется в полную силу, благодаря чему возможно перехватывание вызовов любых объектов.
В чем разница между ModelMap и ModelAndView ?
Model — интерфейс, ModelMap его реализация. ModelAndView является контейнером для пары, как ModelMap и View. Обычно я люблю использовать ModelAndView. Однако есть так же способ когда мы задаем необходимые атрибуты в ModelMap, и возвращаем название View обычной строкой из метода контроллера.
В чем разница между model.put() и model.addAttribute() ?
Метод addAttribute отделяет нас от работы с базовой структурой hashmap. По сути addAttribute это обертка над put, где делается дополнительная проверка на null. Метод addAttribute в отличие от put возвращает modelmap.
model.addAttribute(“attribute1”,”value1”).addAttribute(“attribute2”,”value2”);
Что можете рассказать про Form Binding?
Нам это может понадобиться, если мы, например, захотим взять некоторое значение с HTML страницы и сохранить его в БД. Для этого нам надо это значение переместить в контроллер Spring. Если мы будем использовать Spring MVC form tags, Spring автоматически свяжет переменные на HTML странице с бином Spring.
Почему мы используем Hibernate Validator?
Hibernate Validator никак не связан с БД. Это просто библиотека для валидации. Hibernate Validator версии 5.x является эталонной реализацией Bean Validation 1.1
Так же если взглянуть по адресу http://beanvalidation.org/2.0, то Hibernate Validator является единственным, который сертифицирован.
Где должны располагаться статические (css, js, html) ресурсы в Spring MVC приложении?
Расположение статических ресурсов можно настроить. В документации Spring Boot рекомендуется использовать /static, или /public, или /resources, или /META-INF/resources.
Можно ли передать в запросе один и тот же параметр несколько раз?
Пример: http://localhost:8080/login?name=Ranga&name=Ravi&name=Sathish Да, можно принять все значения, используя массив в методе контроллера
public String method(@RequestParam(value="name") String[] names) >
Урок 2: Введение в Spring IoC контейнер
Этот урок освещает работу с Spring Framework IoC контейнером и основан на оригинальной документации §5. The IoC container.
Что вы создадите
Вы создадите некоторое количество классов, в которых будет рассмотрена функциональность Spring Framework IoC контейнера.
Что вам потребуется
- Любимый текстовый редактор или IDE
- JDK 7 и выше
- Maven 3.0+
- Исходный код предыдущего урока
Настройка проекта
Прежде чем вы начнете изучать этот урок, вам необходимо внести некоторые изменения в проект. Для начала создайте структуру папок src/main/resources и переместите в него файл настроек логгирования log4j.properties , тем самым поместив его в classpath проекта. Теперь немного измените файл сборки pom.xml , добавив и изменив в нем следующее:
. 1.7 1.5.8 . org.slf4j jcl-over-slf4j $ org.slf4j slf4j-api $ org.slf4j slf4j-log4j12 $ . org.apache.maven.plugins maven-compiler-plugin 3.2 $ $ $ -Xlint:all true true .
И наконец, создайте структуру папок src/main/java/lessons/starter/ . В данном пакете вы будете создавать классы с методами public static void main(String[] args) , которые вы будете запускать для того, чтобы можно было видеть результаты действий в процессе изучения данного материала.
Введение
Inversion of Control (IoC), также известное как Dependency Injection (DI), является процессом, согласно которому объекты определяют свои зависимости, т.е. объекты, с которыми они работают, через аргументы конструктора/фабричного метода или свойства, которые были установлены или возвращены фабричным методом. Затем контейнер inject(далее «внедряет») эти зависимости при создании бина. Этот процесс принципиально противоположен, поэтому и назван Inversion of Control, т.к. бин сам контролирует реализацию и расположение своих зависимостей, используя прямое создание классов или такой механизм, как шаблон Service Locator.
Основными пакетами Spring Framework IoC контейнера являются org.springframework.beans и org.springframework.context . Интерфейс BeanFactory предоставляет механизм конфигурации по управлению любым типом объектов. ApplicationContext — наследует нитерфейс BeanFactory и добавляет более специфичную функциональность. Ниже в таблице представлены различия между ними:
Функционал
BeanFactory
ApplicationContext
Инициализация/автоматическое связывание бина
Автоматическая регистрация BeanPostProcessor
Автоматическая регистрация BeanFactoryPostProcessor
Удобный доступ к MessageSource (для i18n)
ApplicationEvent публикация
В большинстве случаев предпочтительно использовать ApplicationContext , поэтому в дальнейшем будет использоваться только он и его реализации. Поскольку он включает в себя всю функциональность BeanFactory , его можно и нужно использовать, за исключением случаев, когда приложение запускается на устройствах с ограниченными ресурсами, в которых объем потребляемой памяти может быть критичным, даже в пределах нескольких килобайт, либо когда вы разрабатываете приложение, в котором необходима поддержка совместимости со сторонними библиотеками, использующими JDK 1.4 или не поддерживают JSR-250. Spring Framework активно использует BeanPostProcessor для проксирования и др., поэтому, если вам необходима поддержка такой функциональности, как AOP и транзакций, то при использовании BeanFactory необходимо добавить вручную регистрацию BeanPostProcessor и BeanFactoryPostProcessor , как показано ниже:
ConfigurableBeanFactory factory = new XmlBeanFactory(. ); // теперь зарегистрируем необходимый BeanPostProcessor экземпляр MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // запускаем, используя factory
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // получаем какое-то значения свойства из Properties-файла PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // теперь заменяем значение свойства на новое cfg.postProcessBeanFactory(factory);
Аннотации @Autowired , @Inject , @Resource и @Value обрабатываются Spring реализацией BeanPostProcessor , поэтому вы не можете их применять в своих собственных BeanPostProcessor и BeanFactoryPostProcessor , а только лишь явной инициализацией через XML или @Bean метод.
Описание работы IoC контейнера
Ниже представлена диаграмма, отражающая, как работает Spring. Ваши классы приложения совмещаются с метаданными конфигурации, в результате чего будет создан и инициализирован ApplicationContext , а на выходе вы получите полностью настроенное и готовое к выполнению приложение.
ApplicationContext представляет собой Spring IoC контейнер и необходим для инициализации, настройки и сборки бинов для построения приложения.
В метаданных конфигурации разработчик описывает как инициализировать, настроить IoC контейнер и собрать объекты в вашем приложении. В данном и других уроках этого цикла везде, где возможно, будет использоваться подход на основе аннотаций и Java-конфигурации. Если вы сторонник XML-конфигурации, либо хотите посмотреть как делать тоже самое через XML, обратитесь к оригинальной документации по Spring Framework или соответствующего модуля/проекта.
Настройка IoC контейнера
Основными признаками и частями Java-конфигурации IoC контейнера являются классы с аннотацией @Configuration и методы с аннотацией @Bean . Аннотация @Bean используется для указания того, что метод создает, настраивает и инициализирует новый объект, управляемый Spring IoC контейнером. Такие методы можно использовать как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Класс с аннотацией @Configuration говорит о том, что он является источником определения бинов. Самая простейшая из возможных конфигураций выглядит следующим образом:
package lessons; import org.springframework.context.annotation.Configuration; /** * Конфигурационный класс Spring IoC контейнера */ @Configuration public class LessonsConfiguration
Полный @Configuration vs легкий @Bean режимы
Когда методы с аннотацией @Bean определены в классах, не имеющих аннотацию @Configuration , то относятся к обработке в легком режиме, то же относится и к классам с аннотацией @Component . Иначе, такие методы относятся к полному режиму обработки.
В отличие от полного, в легком режиме @Bean методы не могут просто так объявлять внутренние зависимости. Поэтому, в основном предпочтительно работать в полном режиме, во избежание трудноуловимых ошибок.
Для того, чтобы приступить к настройке и изучению Spring IoC контейнера, вы должны инициализировать ApplicationContext , который поможет также с разрешением зависимостей. Для обычной Java-конфигурации применяется AnnotationConfigApplicationContext , в качестве аргумента к которому передается класс, либо список классов с аннотацией @Configuration , либо с любой другой аннотацией JSR-330, в том числе и @Component :
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); >>
Как вариант, можно инициализировать контекст(ы) таким образом:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(LessonsConfiguration.class); context.refresh(); >>
Использование @Bean аннотации
Как упоминалось выше, для того, чтобы объявить Bean-объект(далее просто бин), достаточно указать аннотацию @Bean тому методу, который возвращает тип бина как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Например, определим интерфейс какого-нибудь сервиса и его реализацию:
package lessons.services; public interface GreetingService
package lessons.services; public class GreetingServiceImpl implements GreetingService < @Override public String sayGreeting() < return "Greeting, user!"; >>
Теперь, для того, чтобы объект с типом GreetingService был доступен для использования, необходимо описать его в конфигурации следующим образом:
@Configuration public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
А для того, чтобы использовать его, достаточно выполнить следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" >>
Метод getBean() может принимать в качестве аргумента как класс(как показано выше), так и названия бина(подробнее будет рассмотрено ниже), либо другие варианты, с которыми вы можете ознакомится в документации. Однако такой подход не рекомендуется использовать в production-конфигурациях, т.к. для подобных целей существует механизм Dependency Injection (DI), собственно говоря, для чего и предназначен Spring IoC контейнер. Использование DI будет рассмотрено ниже в отдельной главе.
Именовать бины принято в соответствии со стандартным соглашением по именованию полей Java-классов. Т.е. имена бинов должны начинаться со строчной буквы и быть в «Верблюжьей» нотации.
По умолчанию, так, как будет назван метод определения бина, по такому имени и нужно получать бин через метод getBean() или автоматическое связывание. Однако вы можете переопределить это имя или указать несколько псевдонимов, через параметр name аннотации @Bean . Выглядеть это будет примерно так:
@Bean(name = "gServiceName")
@Bean(name = )
Иногда полезно предоставить более подробное описание бина, например, в целях мониторинга. Для этого существует аннотация @Description :
@Bean @Description("Текстовое описание бина greetingService") GreetingService greetingService()
Жизненный цикл бина
Для управления контейнером жизненным циклом бина, вы можете реализовать метод afterPropertiesSet() интерфейса InitializingBean и метод destroy() интерфейса DisposableBean . Метод afterPropertiesSet() позволяет выполнять какие-либо действий после инициализации всех свойств бина контейнером, метод destroy() выполняется при уничтожении бина контейнером. Однако их не рекомендуется использовать, поскольку они дублируют код Spring. Как вариант, предпочтительно использовать методы с JSR-250 аннотациями @PostConstruct и @PreDestroy . Также существует вариант определить аналогичные методы как параметры аннотации @Bean , например так: @Bean(initMethod = «initMethod», destroyMethod = «destroyMethod») .В качестве примера применения данных методов, интерфейсов и аннотаций вы можете ознакомиться в классе GreetingServiceImpl .
При совместном использовании методов, интерфейсов и аннотаций, описанных выше, учитывайте их порядок вызовов. Для методов инициализации порядок будет следующий:
- Методы с аннотациями @PostConstruct в порядке их определения в классе
- Метод afterPropertiesSet()
- Метод, указанный в параметре initMethod аннотации @Bean
Для методов разрушения порядок будет следующий:
- Методы с аннотациями @PreDestroy в порядке их определения в классе
- Метод destroy()
- Метод, указанный в параметре destroyMethod аннотации @Bean
Если вам необходимо реализовать свою собственную модель жизненного цикла бина, то в таком случае бин должен реализовывать один из интерфейсов, приведенных ниже:
public interface Lifecycle
public interface LifecycleProcessor extends Lifecycle
public interface SmartLifecycle extends Lifecycle, Phased
SmartLifecycle интересен тем, что наследует интерфейс Phased , в котором есть метод int getPhase(); . Суть в том, что порядок создания бинов, реализующих этот интерфейс, зависит от возвращаемого методом значения и чем оно меньше, тем раньше всех будет создан бин и тем позже он будет разрушен.
Если вы на данном этапе запустите Starter.java , то в логах увидите, что методы разрушения не вызываются, однако программа завершает свою работу корректно. Дело в том, что для обычных приложений для этих целей стоит инициализировать контекст с типом AbstractApplicationContext , который также реализует ApplicationContext и имеет метод registerShutdownHook() . В итоге, у вас должно быть премерно следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AbstractApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" context.registerShutdownHook(); >>
После этого у вас появятся результаты работы методов при разрушении бина. Однако стоит заметить ещё раз, что это относится к обычным приложения, не относящимся к web-приложения(поскольку для них применяется отдельный тип контекста и подобный метод в них уже есть).
В некоторых случаях необходимо производить манипуляции с ApplicationContext ‘ом, например, в самом бине. Для этого существуют интерфейсы *Aware , полный список которых приведен в таблице 5.4 документации. Поэтому когда ApplicationContext создает экземпляр бина, он учитывает соответствующий интерфейс и передает ссылку на соответствующий ресурс.
Как было описано выше, Spring IoC контейнеру требуются метаданные для конфигурации. Одну из таких аннотаций мы уже рассмотрели, это @Bean , рассмотрим теперь и другие.
Другой основной аннотацией является @Component , а также её наследники @Repository , @Service и @Controller . Все они являются общими шаблонами для любых компонентов, управляемыми контейнеером. @Repository , @Service и @Controller рекомендуется использовать в тех случаях, когда вы можете отнести аннотируемый класс к определенному слою, например DAO, либо когда вам необходима поддержка функциональности, которую предоставляет аннотация. Также эти аннотации могут иметь дополнительный смысл в будущих версиях Spring Framework. В остальных же случаях достаточно использовать аннотацию @Component .
Для того, чтобы ваша конфигурация могла знать о таких компонентах и вы могли бы их использовать, существует специальная аннотация для класса вашей конфигурации @ComponentScan .
@Configuration @ComponentScan public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
По умолчанию, такая конфигурация сканирует на наличие классов с аннотацией @Component и его потомков в том пакете, в котором сама находится, а также в подпакетах. Однако, если вы хотите, чтобы сканирование было по определенным каталогам, то это можно настроить, просто добавив в аннотацию @ComponentScan параметр basePackages с указанием одного или нескольких пакетов. Выглядеть это будет примерно таким образом: @ComponentScan(basePackages = «lessons.services») , а классу GreetingServiceImpl при этом необходимо добавить аннотацию @Component .
Стоит упомянуть ещё одну мета-аннотацию @Required . Данная аннотация применяется к setter-методу бина и указывает на то, чтобы соответствующее свойство метода было установлено на момент конфигурирования значением из определения бина или автоматического связывания. Если же значение не будет установлено, будет выброшено исключение. Использование аннотации позволит избежать NullPointerException в процессе использования свойства бина. Пример использования:
package lessons.services; public class GreetingServiceImpl implements GreetingService < private ApplicationContext context; @Required public void setContext(ApplicationContext context) < this.context = context; >>
Области видимости(scopes) бинов
Когда вы создаете определение бинов, вы вы создаете рецепт для создания экземпляров класса, который определяет бин. Важно понять, что определение бинов является рецептом, потому что он означает, какого класса вы можете создать множество экземпляров по этому рецепту.
Вы можете контролировать не только какие зависимости и значения конфигурации вы можете подключить в объекте, который создан из определения бина, но также область видимости из того же определения бина. Это мощный и гибкий подход, при котором вы можете выбрать область видимости создаваемых объектов. Изначально, Spring Framework поддерживает несколько вариантов, некоторые доступны, только если вы используете web-aware ApplicationContext . Также вы можете создать свою собственную облать видимости. Ниже приведен список областей видимостей, описанных в документации на момент написания урока:
- singleton — По умолчанию. Spring IoC контейнер создает единственный экземпляр бина. Как правило, используется для бинов без сохранения состояния(stateless)
- prototype — Spring IoC контейнер создает любое количество экземпляров бина. Новый экземпляр бина создается каждый раз, когда бин необходим в качестве зависимости, либо через вызов getBean() . Как правило, используется для бинов с сохранением состояния(stateful)
- request — Жизненный цикл экземпляра ограничен единственным HTTP запросом; для каждого нового HTTP запроса создается новый экземпляр бина. Действует, только если вы используете web-aware ApplicationContext
- session — Жизненный цикл экземпляра ограничен в пределах одной и той же HTTP Session . Действует, только если вы используете web-aware ApplicationContext
- global session — Жизненный цикл экземпляра ограничен в пределах глобальной HTTP Session (обычно при использовании portlet контекста). Действует, только если вы используете web-aware ApplicationContext
- application — Жизненный цикл экземпляра ограничен в пределах ServletContext . Действует, только если вы используете web-aware ApplicationContext
С более подробной информацией о настройке приложения для применения областей видимости request , session , global session и application вы можете ознакомиться в документации. Пример реализации собственной области видимости будет рассмотрено в отдельном уроке.
Для того, чтобы указать область видимости бина, отличный от singleton , необходимо добавить аннотацию @Scope(«область_видимости») методу объявления бина или классу с аннотацией @Component :
@Component @Scope("prototype") public class GreetingServiceImpl implements GreetingService < //. >
Использование @Configuration аннотации
Как упоминалось выше, классы с аннотацией @Configuration указывают на то, что они являются источниками определения бинов, public-методов с аннотацией @Bean .
Кода бин имеет зависимость от другого бина, то зависимость выражается просто как вызов метода:
@Configuration @ComponentScan public class LessonsConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(greetingService()); >@Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Однако работает такое взаимодействие только в @Configuration -классах, в @Component -классах такое не работает.
Представим теперь ситуацию, когда у вас есть бин с областью видимости singleton , который имеет зависимость от бина с областью видимости prototype .
public abstract class CommandManager
@Configuration @ComponentScan public class LessonsConfiguration < @Bean @Scope("prototype") public Object asyncCommand() < return new Object(); >@Bean public CommandManager commandManager() < // возвращаем новую анонимную реализацию CommandManager // с новым объектом return new CommandManager() < protected Object createCommand() < return asyncCommand(); >>; > >
Большая часть приложений строится по модульной архитектуре, разделенная по слоям, например DAO, сервисы, контроллеры и др. Создавая конфигурацию, можно также её разбивать на составные части, что также улучшит читабельность и панимание архитектуры вашего приложения. Для этого в конфигурацию необходимо добавить аннотацию @Import , в параметрах которой указываются другие классы с аннотацией @Configuration , например:
@Configuration public class AnotherConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(); >>
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Таким образом, при инициализации контекста вам не нужно дополнительно указывать загрузку из конфигурации AnotherConfiguration , все останется так, как и было:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); BeanWithDependency withDependency = context.getBean(BeanWithDependency.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" logger.info(withDependency.printText()); // "Some text!" >>
В большинстве случаев, имеются такие случаи, когда бин в одной конфигурации имеет зависимость от бина в другой конфигурации. Поскольку конфигурация является источником определения бинов, то разрешить такую зависимость не является проблемой, достаточно объявить поле класса конфигурации с аннотацией @Autowired (более подробно оисано в отдельной главе):
@Configuration public class AnotherConfiguration < @Autowired GreetingService greetingService; @Bean BeanWithDependency beanWithDependency() < //что-нибудь делаем с greetingService. return new BeanWithDependency(); >>
При этом LessonsConfiguration остается без изменений:
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Классы с аннотацией @Configuration не стремятся на 100% заменить конфигурации на XML, при этом, если вам удобно или имеется какая-то необходимость в использовании XML конфигурации, то к вашей Java-конфигурации необходимо добавить аннотацию @ImportResource , в параметрах которой необходимо указать нужное вам количество XML-конфигураций. Выглядит это следующим способом:
@Configuration @ImportResource("classpath:/lessons/xml-config.xml") public class LessonsConfiguration < @Value("$") String url; //. >
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
Процесс разрешения зависимостей
IoC контейнер выполняет разрешение зависимостей бинов в следующем порядке:
- Создается и инициализируется ApplicationContext с метаданными конфигурации, которые описывают все бины. Эти метаданные могут быть описаны через XML, Java-код или аннотации
- Для каждого бина и его зависимостей вычисляются свойства, аргументы конструктора или аргументы статического фабричного метода, либо обычного(без аргументов) конструктора. Эти зависимости предоставляются бину, когда он(бин) уже создан. Сами зависимости инициализируются рекурсивно, в зависимости от вложенности в себе других бинов. Например, при инициализации бина А, котый имеет зависимость В, а В зависит от С, сначала инициализируется бин С, потом В, а уже потом А
- Каждому свойству или аргументу конструктора устанавливается значение или ссылка на другой бин в контейнере
- Для каждого свойства или аргумента конструктора подставляемое значение конвертируется в тот формат, который указан для свойства или аргумента. По умолчанию Spring может конвертировать значения из строкового формата во все встроенные типы, такие как int , long , String , boolean и др.
Spring каждый раз при создании контейнера проверяет конфигурацию каждого бина. И только бины с областью видимости(scope) singleton создаются сразу вместе со своими зависимостями, в отличие от остальных, которые создаются по запросу и в соответствии со своей областью видимости. В случае цикличной зависимости(когда класс А требует экземпляр В, а классу В требуется экземпляр А) Spring IoC контейнер обнаруживает её и выбрасывает исключение BeanCurrentlyInCreationException .
Spring контейнер может разрешать зависимости между бинами через autowiring(далее, автоматическое связывание). Данный механизм основан на просмотре содержимого в ApplicationContext и имеет следующие преимущества:
- Автоматическое связывание позволяет значительно сократить количество инструкций для указания свойств или аргументов конструктора
- Автоматическое связывание позволяет обновлять конфигурацию, несмотря на развитие ваших объектов. К примеру, вам необходимо добавить зависимость в классе и эта зависимость может быть разрешена без необходимости модификации конфигурации. Поэтому автоматическое связывание может быть особенно полезным при разработке, не исключая возможность переключения на явное описание, когда кодовая база будет стабильна
Для того, чтобы воспользоваться механизмом автоматического связывания, Spring Framework предоставляет аннотацию @Autowired . Примеры применения приведены ниже:
public class AutowiredClass < @Autowired //к полям класса @Qualifier("main") //@Autowired(required = false) //чтобы не бросалось исключение, //если не с кем связать //рекомендуется использовать @Required private GreetingService greetingService; @Autowired //к полям класса в виде массива или коллекции private GreetingService[] services; @Autowired //к Map, где ключами являются имена бинов, значения - сами бины private MapserviceMap; @Autowired //к конструктору public AutowiredClass(@Qualifier("main") GreetingService service) <> @Autowired //к обычным методам с произвольным названием аргументов и их количеством public void prepare(GreetingService prepareContext)* что-то делаем. */> @Autowired //к "традиционному" setter-методу public void setContext(GreetingService service) < this.greetingService = service; >>
Т.к. кандидатов для автоматического связывания может быть несколько, то для установки конкретного экземпляра необходимо использовать аннотацию @Qualifier , как показано ниже. Данная аннотация может быть применена как к отдельному полю класса, так и к отдельному аргументу метода или конструктора:
public class AutowiredClass < //. @Autowired //к полям класса @Qualifier("main") private GreetingService greetingService; @Autowired //к отдельному аргументу конструктора или метода public void prepare(@Qualifier("main") GreetingService greetingService)< /* что-то делаем. */ >; //. >
Соответственно, у одной из реализации GreetingService должна быть установлена соответствующая аннотация @Qualifier :
@Component @Qualifier("main") public class GreetingServiceImpl implements GreetingService < //. >
Spring также поддерживает использование JSR-250 @Resource аннотации автоматического связывания для полей класса или параметров setter-методов:
public class AutowiredClass < //. @Resource //По умолчанию поиск бина с именем "context" private ApplicationContext context; @Resource(name="greetingService") //Поиск бина с именем "greetingService" public void setGreetingService(GreetingService service) < this.greetingService = service; >//. >
Использование стандартных JSR-330 аннотаций
Spring Framework поддерживает JSR-330 аннотации. Эти аннотации работают таким же способом, как и Spring аннотации. Для того, чтобы работать с ними, необходимо добавить в pom.xml следующую зависимость:
javax.inject javax.inject 1
Ниже приведена таблица сравнения JSR-330 и Spring аннотаций для DI: