Инверсия (от латинского inversio) - перестановка.
Инверсия контроля (инверсия управления) — это принцип в разработке программного обеспечения, при котором управление объектами или частями программы передается контейнеру или фреймворку.
Чаще всего этот принцип используется в контексте объектно-ориентированного программирования.
В отличие от традиционного программирования, в котором наш пользовательский код обращается напрямую к библиотекам, IoC позволяет фреймворку контролировать ход программы и обращаться к нашему коду, когда это необходимо. Для этого, фреймворки используют абстракции со встроенным дополнительным поведением. Если мы хотим добавить наше собственное поведение, нам нужно расширить классы фреймворка или подключить наши собственные классы.
Преимущества этой архитектуры:
Инверсия управления может быть достигнута с помощью различных механизмов, таких как: шаблон проектирования “Стратегия”, шаблон “Локатор служб”, шаблон “Фабрика” и внедрение зависимостей (DI).
Внедрение зависимостей — это шаблон проектирования для реализации IoC, где инвертируемым (переопределяемым) элементом контроля является настройка зависимостей объекта.
Соединение объектов с другими объектами или «внедрение» объектов в другие объекты выполняется контейнером IoC, а не самими объектами.
В Spring Framework инверсия контроля достигается именно внедрением зависимостей.
В Spring Framework инверсия контроля и внедрение зависимостей считаются одним и тем же.
В Spring Framework внедрение зависимостей описывается как процесс, посредством которого объект определяет свои зависимости (то есть другие объекты, с которыми он работает) только через аргументы конструктора, аргументы фабричного метода или свойства, которые устанавливаются в экземпляре объекта после того, как он создан или возвращен из метода фабрики. После чего контейнер IoC внедряет эти зависимости в компонент при его создании.
Мы можем создать зависимость объекта следующим традиционным способом, без использования принципа IoC:
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}
В приведенном выше примере мы создаем экземпляр конкретной реализации интерфейса Item (ItemImpl1) внутри самого класса Store.
Используя DI , мы можем переписать пример без указания конкретной реализации Item, не создавая её внутри нашего объекта, а ожидая её получение извне (от внешнего фреймворка - контейнера IoC):
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
В данном случае инверсия контроля — это переход контроля над зависимостями от объекта Store к контейнеру IoC. Объект Store более не контролирует инстанцирование своего поля (зависимости) item, не создаёт этот объект самостоятельно, а делегирует этот процесс внешним силам - контейнеру IoC, который в нашем примере передаёт в конструктор Store любую из реализаций Item.
В Spring Framework IoC Container отвечает за создание, настройку и сборку объектов, известных как бины, а также за управление их жизненным циклом.
Он (контейнер) представлен интерфейсом ApplicationContext.
Spring Framework предоставляет несколько реализаций интерфейса ApplicationContext:
ClassPathXmlApplication
- для автономных приложений;
WebApplicationContext
- для веб-приложений;
AnnotationConfigApplication
- для обычной Java-конфигурации,в качестве аргумента которому передается класс, либо список классов с
аннотацией
Контейнер получает инструкции о том, какие объекты создавать, настраивать и собирать, через метаданные конфигурации, которые представлены в виде XML, Java-аннотаций или Java-кода:
Java-код - Начиная со Spring 3.0, используя Java-код, а не файлы XML, мы можем определять настройки в
специальном классе, помеченном аннотацией
Появились аннотации
В Spring объекты, образующие основу приложения и управляемые контейнером Spring IoC, называются бинами.
Бин — это объект, который создается, собирается и управляется контейнером Spring IoC.
Иначе говоря, бин — это просто один из множества объектов в вашем приложении.
Бины и их зависимости отражаются в метаданных конфигурации, используемых контейнером.
@Bean - Это аннотация Spring Framework, она используется над методом для указания того, что данный метод создает, настраивает и инициализирует новый объект, управляемый Spring IoC контейнером.
Такие методы можно использовать как в классах с аннотацией
Позволяет дополнительно определить у бина:
Параметр | Описание |
---|---|
name | имя (уникальный идентификатор) бина; |
initMethod | имя метода для вызова во время инициализации бина; |
destroyMethod | имя метода для вызова во время удаления бина из контекста; |
autowireCandidate | является ли этот бин кандидатом на автоматическое внедрение в другой бин. |
Классы, аннотированные
Классы
Также методы бинов, вызывая друг друга в таких классах, не будут создавать бины, а будет просто выполняться код метода, ведь в данном случае они отработают не через прокси.
CGLIB (Code Generation Library) - Это библиотека инструментария байтов, используемая во многих средах Java, таких как Hibernate или Spring. Инструментарий байт-кода позволяет манипулировать или создавать классы после фазы компиляции программы.
Hibernate использует CGLIB для генерации динамических прокси.
Например, он не вернет полный объект, хранящийся в базе данных, но вернет инструментальную версию хранимого класса, которая лениво загружает значения из базы данных по требованию.
Прокси — это шаблон проектирования. Создаем и используем его для добавления и изменения функционала уже существующих классов. В таком случае, прокси-объект применяется вместо исходного. Обычно он использует тот же метод, что и оригинальный, и в JAVA прокси-классы расширяют исходные.
Имя бина, которое в контейнере является одновременно и его уникальным идентификатором, по умолчанию соответствует имени метода, аннотированного @Bean.
Но если требуется указать иное имя, то можно использовать атрибут name, который принимает String. Однако, атрибут name также может принимать массив String, что позволяет использовать несколько имен.
Первый элемент массива будет являться именем и уникальным идентификатором бина, а остальные будут его псевдонимами.
public MyBean myBean() {
// instantiate and configure MyBean obj
return obj;
}
Если хотим, чтобы из этого класса был создан бин, то используем
Именно эту аннотацию ищет Spring Framework, когда сканирует наши классы.
Именем бина будет название класса с маленькой буквы.
Можно указать имя (Id) для создаваемого бина.
Аннотация @Component имеет наследников:
Все они являются частными случаями использования @Component для слоёв DAO, сервиса и контроллера MVC соответственно.
Также эти аннотации могут иметь дополнительный смысл в будущих версиях Spring Framework.
В остальных же случаях достаточно использовать аннотацию @Component.
Аннотация | Описание |
---|---|
@Component | Spring определяет этот класс как кандидата для создания bean. |
@Service |
указывает, что класс содержит бизнес-логику и вызывает методы на уровне хранилища. Ничем не отличается от классов с @Component. |
@Repository |
указывает, что класс выполняет роль хранилища (объект доступа к DAO).
Задача @Repository заключается в том, чтобы отлавливать определенные исключения персистентности
и
пробрасывать их как одно непроверенное исключение Spring Framework.
Для этого Spring оборачивает эти классы в прокси, и в контекст должен быть добавлен класс
Persistence |
@Controller |
указывает, что класс выполняет роль контроллера MVC. DispatcherServlet просматривает такие классы для поиска @RequestMapping. |
@RequestMapping используется для мапинга (связывания) с URL для всего класса или для конкретного метода обработчика.
Аннотация @Component (как и @Service и @Repository) используется для автоматического обнаружения и автоматической настройки бинов в ходе сканирования путей к классам.
Аннотация @Bean используется для явного объявления бина.
A не для того, чтобы Spring делал это автоматически в ходе сканирования путей к классам:
@Service и @Repository являются частными случаями @Component.
Технически они одинаковы , но мы используем ихдля разных целей.
Задача @Repository заключается в том, чтобы отлавливать определенные исключения персистентности и пробрасывать их как одно непроверенное исключение Spring Framework.
Для этого в контекст должен быть добавлен класс Persistence
Мы помечаем бины аннотацией @Service, чтобы указать, что они содержат бизнес-логику.
Так что нет никакого другого предназначения, кроме как использовать ее на уровне сервиса.
Аннотация Spring Framework, которой отмечают конструктор, поле, сеттер-метод или метод конфигурации, указывая, что им обязательно требуется внедрение зависимостей.
Если в контейнере не будет обнаружен необходимый для вставки бин, то будет выброшено исключение, либо можно
указать
Аннотация @Autowired является альтернативой Java-аннотации @Inject, не имеющей required = false (зависимость должна быть обязательно внедрена).
Начиная со Spring Framework 4.3, аннотация @Autowired для конструктора больше не требуется, если целевой компонент определяет только один конструктор.
Однако, если доступно несколько конструкторов и нет основного/стандартного конструктора, по крайней мере один из конструкторов должен быть аннотирован @Autowired, чтобы указать контейнеру, какой из них использовать.
По умолчанию Spring распознает объекты для вставки по типу.
Если в контейнере доступно более одного бина одного и того же типа, будет исключение. Для избежания этого можно
указать аннотацию Spring Framework -
public class FooService {
private Formatter formatter;
}
При выборе между несколькими бинами при автоматическом внедрении используется имя поля. Это поведение по умолчанию, если нет других настроек.
public class FooFormatter implements Formatter {
//...
}
public class BarFormatter implements Formatter {
//...
}
public class FooService {
private Formatter fooFormatter;
}
В этом случае Spring определит, что нужно внедрить бин с именем FooFormatter, поскольку имя поля соответствует значению, которое мы использовали в аннотации @Component для этого бина.
Мы также можем указать Spring предоставить все бины определенного типа из ApplicationContext, добавив аннотацию @Autowired в поле или метод с массивом или коллекцией этого типа:
private MovieCatalog[] movieCatalogs;
или
private Set <MovieCatalog> movieCatalogs;
или
public void setMovieCatalogs(Set <MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
Даже коллекции типа Map могут быть подключены автоматически, если тип ключа - String.
Ключами будут имена бинов, а значениями - сами бины:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
//...
}
Java-аннотация@Resource может применяться к классам, полям и методам.
Она пытается получить зависимость: сначала по имени, затем по типу, затем по описанию (Qualifier).
Имя извлекается из имени аннотируемого сеттера или поля, либо берется из параметра name.
При аннотировании классов имя не извлекается из имени класса по умолчанию, поэтому оно должно быть указано явно.
Указав данную аннотацию у полей или методов с аргументом name, в контейнере будет произведен поиск компонентов с данным именем, и в контейнере должен быть бин с таким именем:
private File defaultFile;
Если указать её без аргументов, то Spring Framework может найти бин по типу.
Если в контейнере несколько бинов-кандидатов на внедрение, то нужно использовать аннотацию
private File dependency1;
private File dependency2;
Java-аннотация @Inject входит в пакет javax.inject и, чтобы её использовать, нужно добавить зависимость:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
Размещается над полями, методами, и конструкторами с аргументами.
@Inject как и @Autowired в первую очередь пытается подключить зависимость по типу, затем по описанию и только потом по имени.
Это означает, что даже если имя переменной ссылки на класс отличается от имени компонента, но они одинакового типа, зависимость все равно будет разрешена:
private ArbitraryDependency fieldInjectDependency;
отличается от имени компонента, настроенного в контексте приложения:
public ArbitraryDependency injectDependency() {
ArbitraryDependency injectDependency = new ArbitraryDependency();
return injectDependency;
}
Разность имён injectDependency и fieldInjectDependency не имеет значения, зависимость будет подобрана по типу ArbitraryDependency.
Если в контейнере несколько бинов-кандидатов на внедрение, то нужно использовать аннотацию @Qualifier:
private ArbitraryDependency defaultDependency;
private ArbitraryDependency namedDependency;
При использовании конкретного имени (Id) бина используем @Named:
private ArbitraryDependency yetAnotherFieldInjectDependency;
Обычно бины в приложении Spring являтся синглтонами, и для внедрения зависимостей мы используем конструктор или сеттер.
Но бывает и другая ситуация: имеется бин Car – синглтон (singleton bean), и ему требуется каждый раз новый экземпляр бина Passenger. То есть Car – синглтон, а Passenger – так называемый прототипный бин (prototype bean). Жизненные циклы бинов разные. Бин Car создается контейнером только раз, а бин Passenger создается каждый раз новый – допустим, это происходит каждый раз при вызове какого-то метода бина Car. Вот здесь-то и пригодится внедрение бина с помощью Lookup-метода. Оно происходит не при инициализации контейнера, а позднее: каждый раз, когда вызывается метод.
Суть в том, что мы создаём метод-заглушку в бине Car и помечаем его специальным образом – аннотацией @Lookup. Этот метод должен возвращать бин Passenger, каждый раз новый. Контейнер Spring под капотом создаст прокси-подкласс и переопределит этот метод и будет нам выдавать новый экземпляр бина Passenger при каждом вызове аннотированного метода. Даже если в нашей заглушке он возвращает null (а так и надо делать - всё равно этот метод будет переопределен в прокси-подклассе):
@Component
public class Car {
public Passenger createPassenger() {
return null;
}
public String drive(String name) {
Passenger passenger = createPassenger();
passenger.setName(name);
return "car with " + passenger.getName();
}
}
Допустим, в бине есть метод drive(), и при каждом вызове метода drive() бину Car требуется новый экземпляр бина Passenger – сегодня пассажир Петя, завтра – Вася. То есть бин Passenger прототипный. Для получения этого бина надо написать метод-заглушку createPassenger() и аннотировать его с помощью @Lookup.
Контейнер Spring переопределит этот метод-заглушку и будет выдавать при его вызове каждый раз новый экземпляр Passenger.
Переопределяем бин Passenger как прототипный:
public class Passenger {
private String name;
// + геттер и сеттер
}
Теперь при вызове метода drive() мы можем везти каждый раз нового пассажира. Имя его передаётся в аргументе метода drive(), и затем задается сеттером во вновь созданном экземпляре пассажира.
Spring не позволяет внедрять бины напрямую в статические поля.
Пример:
public class TestDataInit {
private static OrderItemService orderItemService;
}
Если вы распечатать TestDataInit.orderItemService, там будет null.
Чтобы исправить это, необходимо создать нестатический сеттер-метод:
public class TestDataInit {
private static OrderItemService orderItemService;
public void setOrderItemService(OrderItemService orderItemService) {
TestDataInit.orderItemService = orderItemService;
}
}
@Primary - эта аннотация указывает, какой компонент определенного типа должен внедряться по умолчанию.
public class Config {
public Employee JohnEmployee() {
return new Employee("John");
}
public Employee TonyEmployee() {
return new Employee("Tony");
}
}
или
public class DepartmentManager implements Manager {
public String getManagerName() {
return "Department manager";
}
}
public class GeneralManager implements Manager {
public String getManagerName() {
return "General manager";
}
}
Теперь, где будут требоваться бины типа Employee и Manager будут созданы и внедрены TonyEmployee и GeneralManager.
Когда есть несколько бинов одного типа, подходящих для внедрения
Aннотация @Qualifier позволяет указать в качестве аргумента имя конкретного бина, который следует внедрить.
При совместном применении
Аннотация @Qualifier будет иметь над @Primary приоритет.
Внедрить в поле примитив можно с помощью аннотации @Value на уровне параметров поля или конструктора/метода.
Значения, которые необходимо внедрить аннотацией @Value, определяются в файле свойств (*.properties)
public class CollectionProvider {
//...
}
Содержимое файла values.properties:
value.from.file=Value got from the file
priority=high
listOfValues=A,B,C
Внедряем значение value.from.file, равное “Value got from the file”:
private String valueFromFile;
Если из файла не подтянутся значения по тем или иным причинам, то можно указать значения, которые будут внедрены по умолчанию.
В данном примере, если не будет доступен value.from.file, то внедрится значение “some default”:
private String someDefault;
Если нужно внедрить несколько значений, то можно их определить в файле *.properties через запятую и Spring внедрит их как массив:
private String[] valuesArray;
Кроме того, для внедрения значений можно использовать язык SpEL (Spring Expression Language):
private String spelValue;
или со значениями по умолчанию:
private String spelSomeDefault;
Можно использовать значение поля из другого бина.
Предположим, есть бин с именем someBean с полем someValue, равным 10. В поле будет записано число 10:
private Integer someBeanValue;
Можно манипулировать свойствами, чтобы получить список значений.
Получаем список строковых значений A, B и C:
private List<String> valuesList;
Можно использовать аннотацию @Value для добавления свойств в Map.
Свойство в файле свойств должно быть определено в формате {key: 'value'}:
valuesMap={key1: '1', key2: '2', key3: '3'}
Вставка:
private Map <String, Integer> valuesMap;
Можем просто внедрить значение по ключу:
private Integer valuesMapKey1;
Если достоверно не известно, содержит ли Map определенный ключ, то нужно выбрать более безопасное выражение, которое не будет генерировать исключение, а установит значение в null, если ключ не найден:
private Integer unknownMapKey;
Можно установить значения по умолчанию для свойств или ключей, которые могут не существовать:
private Map<String, Integer> unknownMap;
private Integer unknownMapKeyWithDefaultValue;
Записи карты также могут быть отфильтрованы перед внедрением.
Предположим, нам нужно получить только те записи, значения которых больше единицы:
private Map<String, Integer> valuesMapFiltered;
Мы также можем использовать аннотацию @Value для добавления всех текущих системных свойств:
private Map>String, String> systemPropertiesMap;
Можно внедрять значения в конструкторе, если оно не найдено, то будет внедрено значение по умолчанию:
public class PriorityProvider {
private String priority;
public PriorityProvider(
this.priority = priority;
}
//standard getter
}
В приведенном коде используется выражение SpEL для добавления списка значений в метод setValues:
public class CollectionProvider {
private List<String> values = new ArrayList<>();
public void setValues(
this.values.addAll(values);
}
}
Можно вставлять массивы примитивов и ссылочных типов. Со всеми массивами и коллекциями можно использовать внедрение через конструкторы, сеттеры или поля:
public class ArrayExample {
public TestBean testBean() {
return new TestBean();
}
public String[] strArray() {
return new String[]{"two", "three", "four"};
}
}
public class TestBean {
private String[] stringArray;
public void setStringArray (String[] stringArray) {
this.stringArray = stringArray;
}
}
public class ListExample {
public TestBean testBean() {
return new TestBean();
}
public List<String> strList() {
return Arrays.asList("two", "three", "four");
}
}
public class TestBean {
private List <String> stringList;
public void setStringList (List<String> stringList) {
this.stringList = stringList;
}
}
Можно собрать все бины одного типа, находящиеся в контейнере, и внедрить их в коллекцию или массив:
public class SetInjection {
public TestBean testBean() {
return new TestBean();
}
public RefBean refBean1() {
return new RefBean("bean 1");
}
public RefBean refBean2() {
return new RefBean2("bean 2");
}
public RefBean refBean3() {
return new RefBean3("bean 3");
}
public static class TestBean {
// All bean instances of type RefBean will be injecting here
private Set<RefBean> refBeans;
...
}
public static class RefBean {
private String str;
public RefBean(String str) {
this.str = str;
}
...
}
}
Если нужно внедрить вышеупомянутые бины RefBean в Map, то значениями Map будут сами бины, а ключами будут имена бинов:
{refBean1 = RefBean{str='bean 1'}, refBean2 = RefBean{str='bean 2'}, refBean3 = RefBean{str='bean 3'}}
Методы класса JavaConfig (те, которые аннотированы @Bean) могут быть объявлены с определенным квалифицирующим типом, используя @Qualifier. Используя параметр 'name' у аннотации @Bean, чтобы указать конкретный классификатор для бина, указывается не имя, а идентификатор бина, который должен быть уникальным, потому что все бины хранятся в контейнере в Map.
В случае если необходимо чтобы несколько бинов имели одно и то же имя квалификатора, чтобы их можно было внедрить в одну коллекцию с одним и тем же квалификатором, нужно использовать аннотацию @Qualifier вместе с @Bean вместо элемента name.
public class SetInjection {
public TestBean testBean() {
return new TestBean();
}
public RefBean refBean1() {
return new RefBean("bean 1");
}
public RefBean refBean2() {
return new RefBean2("bean 2");
}
public RefBean refBean3() {
return new RefBean3("bean 3");
}
public static class TestBean {
private Set<RefBean> refBeans;
...
}
}
Только бины с именами refBean2 и refBean3 попадут в коллекцию, так как у них одинаковые квалификаторы - myRefBean.
Бины могут быть упорядочены, когда они вставляются в списки (не Set или Map) или массивы.
Поддерживаются как аннотация @Order, так и интерфейс Ordered.
Например:
public class ArrayExample {
public TestBean testBean() {
return new TestBean();
}
public String refString1() {
return "my string 1";
}
public String refString2() {
return "my string 2";
}
public String refString3() {
return "my string 3";
}
private static class TestBean {
private String[] stringArray;
public void setStringArray (String[] stringArray) {
this.stringArray = stringArray;
}
public String[] getStringArray() {
return stringArray;
}
}
}
Массив строк будет выглядеть так:
[my string 2, my string 3, my string 1]
public class ActionHeroesService {
List<Hero> actionHeroes;
}
public class HeroesConfig {
public List<Hero> action() {
List<Hero> result = new ArrayList<>();
result.add(new Terminator());
result.add(new Rambo());
return result;
}
}
Часто бывает полезно включить или отключить весь класс @Configuration, @Component или отдельные методы @Bean в зависимости от каких-либо условий.
Аннотация @Conditional указывает, что компонент имеет право на регистрацию в контексте только тогда, когда все условия соответствуют.
Может применяться:
Условия проверяются непосредственно перед тем, как должно быть зарегистрировано BeanDefinition компонента, и они могут помешать регистрации данного BeanDefinition. Поэтому нельзя допускать, чтобы при проверке условий мы взаимодействовали с бинами (которых еще не существует), с их BeanDefinition-ами можно.
Условия мы определяем в специально создаваемых нами классах, которые должны имплементировать функциональный интерфейс Condition с одним единственным методом, возвращающим true или false:
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
Создав свой класс и переопределив в нем метод matches() со своей логикой, необходимо передать этот класс в аннотацию @Conditional в качестве параметра:
class MySQLAutoconfiguration {
//...
}
Для того, чтобы проверить несколько условий, можно передать в @Conditional несколько классов с условиями:
Properties additionalProperties() {
//...
}
Если класс @Configuration помечен как @Conditional, то на все методы @Bean, аннотации @Import и аннотации @ComponentScan, связанные с этим классом, также будут распространяться указанные условия.
Для детальной настройки классов, аннотированных @Configuration используется интерфейс ConfigurationCondition
В одном классе - одно условие.
Для создания более сложных условий можно использовать классы AnyNestedCondition, AllNestedConditions и NoneNestedConditions.
В Spring Framework имеется множество готовых аннотаций (и связанных с ними склассами-условиями, имплементирующими интерфейс Condition), которые можно применять совместно над одним определением бина:
Аннотация | Описание |
---|---|
ConditionalOnBean | Условие выполняется, в случае если присутствует нужный бин в BeanFactory. |
ConditionalOnClass | Условие выполняется, если нужный класс есть в classpath. |
ConditionalOnCloudPlatform | Условие выполняется, когда активна определенная платформа. |
ConditionalOnExpression | Условие выполняется, когда SpEL выражение вернуло положительное значение. |
ConditionalOnJava | Условие выполняется, когда приложение запущено с определенной версией JVM. |
ConditionalOnJndi | Условие выполняется, только если через JNDI доступен определенный ресурс. |
ConditionalOnMissingBean | Условие выполняется, в случае если нужный бин отсутствует в контейнере. |
ConditionalOnMissingClass | Условие выполняется, если нужный класс отсутствует в classpath. |
ConditionalOnNotWebApplication | Условие выполняется, если контекст приложения не является веб контекстом. |
ConditionalOnProperty | Условие выполняется, если в файле настроек заданы нужные параметры. |
ConditionalOnResource | Условие выполняется, если присутствует нужный ресурс в classpath. |
ConditionalOnSingleCandidate | Условие выполняется, если bean-компонент указанного класса уже содержится в контейнере и он единственный. |
ConditionalOnWebApplication | Условие выполняется, если контекст приложения является веб контекстом. |
Аннотация @ComponentScan используется вместе с аннотацией @Configuration для указания пакетов, которые мы хотим сканировать на наличие компонентов, из которых нужно сделать бины.
@ComponentScan без аргументов указывает Spring по умолчанию сканировать текущий пакет и все его подпакеты.
Текущий пакет - тот, в котором находится файл конфигурации с этой самой аннотацией @ComponentScan.
В контейнер попадут:
Аннотация @SpringBootApplication включает в себя аннотации @ComponentScan, @SpringBootConfiguration и @EnableAutoConfiguration , но это не мешает разместить её ещё раз отдельно для указания конкретного пакета.
Если указать @ComponentScan с атрибутом basePackages, то это изменит пакет по умолчанию на указанный:
public class SpringComponentScanApp {
//...
}
Если указать @ComponentScan с атрибутом excludeFilters, то это позволит использовать фильтр и исключить ненужные классы из процесса сканирования:
Профили — это ключевая особенность Spring Framework, позволяющая нам относить бины к разным профилям (логическим группам), например, dev, test, prod.
Можно активировать разные профили в разных средах, чтобы загрузить только те бины, которые нужны.
Аннотацию @Profile, относит бин к конкретному профилю.
Можно применять на уровне класса или метода.
Аннотация @Profile принимает в качестве аргумента имя одного или нескольких профилей.
Она фактически реализована с помощью гораздо более гибкой аннотации @Conditional.
Есть бин, который должен быть активным только во время разработки, но не должен использоваться в продакшене.
Аннотируем этот компонент с профилем «dev», и он будет присутствовать в контейнере только во время разработки - во время продакшена профиль dev просто не будет активен:
public class DevDatasourceConfig {...}
В качестве быстрого обозначения имена профилей также могут начинаться с оператора NOT, например «!dev», чтобы исключить их из профиля:
public class DevDatasourceConfig {...}
Тут компонент активируется, только если профиль «dev» не активен.
Следующим шагом является активация нужного профиля для того, чтобы в контейнере были зарегистрированы только бины, соответствующие данному профилю. Одновременно могут быть активны несколько профилей.
По умолчанию, если профиль бина не определен, то он относится к профилю "default".
Spring также предоставляет способ установить профиль по умолчанию, когда другой профиль не активен, используя свойство «spring.profiles.default».
В Spring Boot есть возможность иметь один файл настроек application.properties, в котором будут основные настройки для всех профилей, и иметь по файлу настроек для каждого профиля application-dev.properties и application-prod.properties, содержащие свои собственные дополнительные настройки.
Чем отличаются? В каких случаях что стоит использовать?
BeanFactory — это интерфейс, который предоставляет механизм конфигурации, способный управлять объектами любого типа.
В общем, BeanFactory предоставляет инфраструктуру конфигурации и основные функциональные возможности.
BeanFactory легче по сравнению с ApplicationContext.
ApplicationContext является наследником BeanFactory и полностью реализует его функционал, добавляя больше специфических enterprise-функций.
Особенность | Bean |
Application |
---|---|---|
Bean instantiation/wiring | Yes | Yes |
Integrated lifecycle management | No | Yes |
Automatic BeanPostProcessor registration | No | Yes |
Automatic BeanFactoryPostProcessor registration | No | Yes |
Convenient MessageSource access (for internalization) | No | Yes |
Built-in ApplicationEvent publication mechanism | No | Yes |
ApplicationContext расширяет BeanFactory и предоставляет функции, которые подходят для корпоративных приложений:
ApplicationContext поддерживает автоматическую регистрацию BeanPostProcessor и BeanFactoryPostProcessor.
Поэтому всегда желательно использовать ApplicationContext, потому что Spring 2.0 (и выше) интенсивно использует BeanPostProcessor.
Вызываются после вызова сеттеров
Порядок выполнения колбеков такая же, как на схеме:
Пост-процессоров может быть несколько.
Цель первого этапа — это создание всех BeanDefinition.
Объекты BeanDefinition — это набор метаданных будущего бина, макет, по которому нужно будет создавать бин в случае необходимости.
То есть для каждого бина создается свой объект BeanDefinition, в котором хранится описание того, как создавать и управлять этим конкретным бином. Сколько бинов в программе - столько и объектов BeanDefinition, их описывающих.
BeanDefinition содержат (среди прочего) следующие метаданные:
Ссылки на другие bean-компоненты, которые необходимы для его работы.
Эти ссылки также называются зависимостями.
Эти метаданные преобразуются в набор свойств, которые составляют каждое BeanDefinition.
Свойство | Ссылка с описанием |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Initialization method | Initialization Callbacks |
Destruction method | Destruction Callbacks |
При конфигурации через аннотации с указанием пакета для сканирования или JavaConfig используется класс
Annotation
в которой хранятся все описания бинов, обнаруженных в ходе парсинга конфигурации.
После первого этапа у нас имеется коллекция Map, в которой хранятся BeanDefinition-ы. BeanFactoryPostProcessor-ы на этапе создания BeanDefinition-ов могут их настроить как нам необходимо. BeanFactoryPostProcessor-ы могут даже настроить саму BeanFactory ещё до того, как она начнет работу по созданию бинов.
В интерфейсе BeanFactoryPostProcessor всего один метод:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
}
Сначала BeanFactory из коллекции Map с объектами BeanDefinition достаёт те из них, из которых создаёт все BeanPostProcessor-ы, необходимые для настройки обычных бинов.
Создаются экземпляры бинов через BeanFactory на основе ранее созданных BeanDefinition.
На данном этапе бины уже созданы, их можно лишь донастроить.
Интерфейс BeanPostProcessor позволяет вклиниться в процесс настройки до того, как бины попадут в контейнер.
ApplicationContext автоматически обнаруживает любые бины с реализацией BeanPostProcessor и помечает их как “post-processors” для того, чтобы создать их определенным способом.
Например, в Spring есть реализации BeanPostProcessor-ов, которые обрабатывают аннотации @Autowired, @Inject, @Value и @Resource.
Интерфейс несет в себе два метода:
postProcessBeforeInitialization(Object bean, String beanName)
postProcessAfterInitialization(Object bean, String beanName)
У обоих методов параметры абсолютно одинаковые. Разница только в порядке их вызова. Первый вызывается до init-метода, второй - после.
Как правило, BeanPostProcessor-ы, которые
заполняют бины через маркерные интерфейсы или тому подобное,
реализовывают метод postProcess
BeanPostProcessor-ы, которые оборачивают бины в прокси,
обычно реализуют postProcess
Прокси — это класс-декорация над бином.
Например, мы хотим добавить логику нашему бину, но джава-код уже скомпилирован, поэтому нам нужно на лету сгенерировать новый класс. Этим классом мы должны заменить оригинальный класс так, чтобы никто не заметил подмены.
Есть два варианта создания этого класса:
1. либо он должен наследоваться от оригинального класса (CGLIB) и переопределять его методы, добавляя нужную логику;
2. либо он должен имплементировать те же самые интерфейсы, что и первый класс (Dynamic Proxy).
По конвенции спринга, если какой-то из BeanPostProcessor-ов меняет что-то в классе, то он должен это делать
на этапе postProcess
Хронология событий:
Если бин имплементирует InitializingBean, то Spring вызовет метод afterPropertiesSet()
не рекомендуется к использованию как устаревший.
В конце бины пройдут через postProcess
Создадутся прокси стандартными BeanPostProcessor-ами.
Отработают кастомные BeanPostProcessor-ы и применят нашу логику к прокси-объектам.
После чего все бины окажутся в контейнере, который будет обязательно обновлен методом refresh().
Когда контекст закрывается (метод close() из ApplicationContext), бин уничтожается.
Если в бине есть метод, аннотированный @PreDestroy, то перед уничтожением вызовется этот метод.
Если бин имплементирует DisposibleBean, то Spring вызовет метод destroy()
не рекомендуется к использованию как устаревший.
Если в аннотации @Bean определен метод destroyMethod, то будет вызван и он.
Spring вызывает методы, аннотированные @PostConstruct, только один раз, сразу после инициализации свойств компонента. За данную аннотацию отвечает один из BeanPostProcessor-ов.
Метод, аннотированный @PostConstruct, может иметь любой уровень доступа, может иметь любой тип возвращаемого значения (хотя тип возвращаемого значения игнорируется Spring-ом), метод не должен принимать аргументы. Он также может быть статическим, но преимуществ такого использования метода нет, т.к. доступ у него будет только к статическим полям/методам бина, и в таком случае смысл его использования для настройки бина пропадает.
Одним из примеров использования @PostConstruct является заполнение базы данных. Например, во время разработки нам может потребоваться создать пользователей по умолчанию
Метод, аннотированный @PreDestroy, запускается только один раз, непосредственно перед тем, как Spring удаляет наш компонент из контекста приложения.
Как и в случае с @PostConstruct, методы, аннотированные @PreDestroy, могут иметь любой уровень доступа, но не могут быть статическими.
Целью этого метода может быть освобождение ресурсов или выполнение любых других задач очистки до уничтожения бина например, закрытие соединения с базой данных.
Обратите внимание, что аннотации @PostConstruct и @PreDestroy являются частью Java EE, а именно пакета javax.annotation модуля java.xml.ws.annotation. И поскольку Java EE устарела в JAVA 9, то с этой версии пакет считается устаревшим (Deprecated).
С Java 11 данный пакет вообще удален, поэтому мы должны добавить дополнительную зависимость для использования этих аннотаций:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
Spring Framework поддерживает шесть scopes:
С 3 по 6 доступны только в веб-приложениях. Мы также можем создать свой собственный scope.
Является дефолтным scope. В контейнере будет создан только один бин, и все запросы на него будут возвращать один и тот же бин. Этот бин хранится в контейнере, и все запросы и ссылки на этот бин возвращают закэшированный экземпляр.
Scope “prototype” приводит к созданию нового бина каждый раз, когда он запрашивается.
Для бинов со scope "prototype" Spring не вызывает метод destroy().
Spring не берет на себя контроль полного жизненного цикла бина со scope @prototype.
Spring не хранит такие бины в своём контексте (контейнере), а отдаёт их клиенту и больше о них не заботится (в отличие от синглтон-бинов).
Контейнер создает новый экземпляр для каждого HTTP-запроса. Таким образом, если сервер в настоящее время обрабатывает 50 запросов, тогда контейнер может иметь не более 50 бинов, по одному для каждого HTTP-запроса.
Любое изменение состояния одного экземпляра не будет видимо другим экземплярам. Эти экземпляры уничтожаются, как только HTTP-запрос завершен.
public class BeanClass {
...
}
или
public class BeanClass {
...
}
Бин создается в одном экземпляре для одной HTTP-сессии. Таким образом, если сервер имеет 20 активных сессий, тогда контейнер может иметь не более 20 бинов, по одному для каждой сессии. Все HTTP-запросы в пределах времени жизни одной сессии будут иметь доступ к одному и тому же бину.
public class BeanClass {
...
}
или
public class BeanClass {
...
}
Бин со scope “application” создается в одном экземпляре для жизненного цикла ServletContext. Виден как атрибут ServletContext. Синглтон - в одном экземпляре для ApplicationContext.
public class BeanClass {
...
}
или
...
}
Бин со scope “websocket” создается в одном экземпляре для определенного сеанса WebSocket. Один и тот же бин возвращается всякий раз, когда к нему обращаются в течение всего сеанса WebSocket.
public class BeanClass {
...
}
Spring по умолчанию не предоставляет thread scope, но его можно активировать.
Каждый запрос на бин в рамках одного потока будет возвращать один и тот же бин.
Scope threadScope = new SimpleThreadScope();
context.registerScope("thread", threadScope)
Т.е. его нужно создать и зарегистрировать в контексе ручками
Любой другой кастомный scope создается также.
Реализуем интерфейс:
org.springframework.beans.factory.config.Scope
И регистрируем в контексте:
ConfigurationBeanFactory.registerScope
В пятой версии Spring Framework не стало Global session scope.
Аспектно-ориентированное программирование (АОП) — это парадигма программирования, целью которой является повышение модульности за счет разделения междисциплинарных задач. Это достигается путем добавления дополнительного поведения к существующему коду без изменения самого кода.
АОП предоставляет возможность реализации в одном месте сквозной логики - т.е. логики, которая применяется к множеству частей приложения - и обеспечения автоматического применения этой логики по всему приложению.
Подход Spring к АОП заключается в создании "динамических прокси" для целевых объектов и "привязывании" объектов к конфигурированному совету для выполнения сквозной логики.
Если нихрена не понятно читайте
Подробная статьяДля работы с транзакциями Spring Framework использует AOP-прокси:
Для включения возможности управления транзакциями первым делом нужно разместить аннотацию @EnableTransactionManagement у класса-конфигурации @Configuration.
Аннотация @EnableTransactionManagement означает, что классы, помеченные @Transactional, должны быть обернуты аспектом транзакций.
Однако, если мы используем Spring Boot и имеем зависимости spring-data-* или spring-tx, то управление транзакциями будет включено по умолчанию.
@EnableTransactionManagement отвечает за регистрацию необходимых компонентов Spring, таких как TransactionInterceptor и proxy advices - набор инструкций, выполняемых на точках среза - Pointcut.
Регистрируемые компоненты помещают перехватчик в стек вызовов при вызове методов @Transactional. Spring создает прокси для всех классов, помеченных @Transactional (либо если любой из методов класса помечен этой аннотацией). Прокси-объекты позволяют Spring Framework вводить транзакционную логику до и после вызываемого метода - главным образом для запуска и коммита/отката транзакции.
Если мы разместим аннотацию @Transactional над классом @Service, то все его методы станут транзакционными.
Пример процесса
Например, при вызове метода save() произойдет примерно следующее:
Вначале мы имеем:
Transaction Interceptor
В TransactionInterceptor отработает код до работы метода save(), в котором будет определено, выполнить ли метод save() в пределах уже существующей транзакции БД или должна стартовать новая отдельная транзакция. TransactionInterceptor сам не содержит логики по принятию решения, решение начать новую транзакцию, если это нужно, делегируется TransactionManager.
Грубо говоря, на данном этапе наш метод будет обёрнут в try-catch и будет добавлена логика до его вызова и после:
try {
transaction.begin();
// логика до
service.save();
// логика после
transaction.commit();
} catch(Exception ex) {
transaction.rollback();
throw ex;
}
TransactionManager
Менеджер транзакций должен предоставить ответ на два вопроса:
TransactionManager принимает решение, основываясь на следующих фактах:
Атрибут «propagation» у метода, аннотированного @Transactional;
для примера, значение REQUIRES_NEW всегда стартует новую транзакцию
Если TransactionManager решил создать новую транзакцию, тогда:
И EntityManager и это соединение привязываются к текущему потоку, используя переменные ThreadLocal.
EntityManager proxy
Когда метод save() слоя Service делает вызов метода save() слоя DAO, внутри которого вызывается, например, entityManager.persist(), то не происходит вызов метода persist() напрямую у EntityManager, записанного в поле класса DAO. Вместо этого метод вызывает EntityManager proxy, который достает текущий EntityManager для нашего потока, и у него вызывается метод persist().
Отрабатывает DAO-метод save().
TransactionInterceptor
Отработает код после работы метода save(), а именно будет принято решение по коммиту/откату транзакции.
Кроме того, если мы в рамках одного метода сервиса обращаемся не только к методу save(), а к разным методам Service и DAO, то все они буду работать в рамках одной транзакции, которая оборачивает этот метод сервиса.
Вся работа происходит через прокси-объекты разных классов. Представим, что у нас в классе сервиса только один метод с аннотацией @Transactional, а остальные нет. Если мы вызовем метод с @Transactional, из которого вызовем метод без @Transactional, то оба будут отработаны в рамках прокси и будут обернуты в нашу транзакционную логику. Однако, если мы вызовем метод без @Transactional, из которого вызовем метод с @Transactional, то они уже не будут работать в рамках прокси и не будут обернуты в нашу транзакционную логику.
Если внутри метода с @Transactional есть другой метод с аннотацией @Transactional (вложенная транзакция), то отработает только первая (в которую вложена), из-за особенностей создания proxy.
Параметр | Описание |
---|---|
isolation |
Уровень изоляции @Transactional |
timeout |
Время для TransactionManager-а, чтобы дождаться простоя транзакции, прежде чем принять решение об откате не отвечающих транзакций. По умолчанию используется таймаут, установленный по умолчанию для базовой транзакционной системы. @Transactional(timeout=60) |
propagation |
Схема работы с транзакциями. REQUIRED — Указывает, что целевой метод не может работать без другой транзакции. Если до вызова этого метода уже была запущена транзакция, то метод будет работать в той же транзакции, если транзакции не было, то будет создана новая. Используется по умолчанию. REQUIRES_NEW — Указывает, что новая транзакция должна запускаться каждый раз при вызове целевого метода. Если транзакция уже идет, она будет приостановлена, прежде чем будет запущена новая. MANDATORY — Указывает, что для целевого метода требуется активная транзакция. Если активной транзакции нет, метод не сработает и будет выброшено исключение. SUPPORTS — Указывает, что целевой метод может выполняться независимо от наличия транзакции. Если транзакция работает, он будет участвовать в той же транзакции. Если транзакции нет, он всё равно будет выполняться, если не будет ошибок. Методы, которые извлекают данные, являются лучшими кандидатами для этой опции. NOT_SUPPORTED — Указывает, что целевой метод не требует распространения контекста транзакции. В основном те методы, которые выполняются в транзакции, но выполняют операции с оперативной памятью, являются лучшими кандидатами для этой опции. NEVER — Указывает, что целевой метод вызовет исключение, если выполняется в транзакционном процессе. Этот вариант в большинстве случаев не используется в проектах. @Transactional |
rollbackFor |
В Spring все классы API бросают RuntimeException, это означает, что если какой-либо метод не выполняется, контейнер всегда откатывает текущую транзакцию. Проблема заключается только в проверяемых исключениях. Таким образом, этот параметр можно использовать для декларативного отката транзакции, если происходит Checked Exception. Значение по умолчанию: rollbackFor=RunTimeException.class @Transactional |
noRollbackFor |
Указывает, что откат не должен происходить, если целевой метод вызывает это исключение. @Transactional |
Это шаблон проектирования программного обеспечения, который делит программную логику на три отдельных, но взаимосвязанных компонента: модель, представление и контроллер — таким образом, что модификация каждого компонента может осуществляться независимо.
Модель(Model) — предоставляет данные и реагирует на команды контроллера, изменяя своё состояние. Она содержит всю бизнес-логику приложения.
Представление(View) — отвечает за отображение пользователю данных из модели в нужном формате.
Контроллер(Controller) — содержит код, который отвечает за обработку действий пользователя и обменивается данными с моделью (любое действие пользователя в системе обрабатывается в контроллере).
Основная цель следования принципам MVC — отделить реализацию бизнес-логики приложения (модели) от ее визуализации (вида). Такое разделение повысит возможность повторного использования кода.
Польза применения MVC наиболее наглядна в случаях, когда пользователю нужно предоставлять одни и те же данные в разных формах. Например, в виде таблицы, графика или диаграммы (используя различные виды). При этом, не затрагивая реализацию видов, можно изменить реакции на действия пользователя (нажатие мышью на кнопке, ввод данных).
Паттерн, где центральный сервлет, DispatcherServlet, принимает все запросы и распределяет их между контроллерами, обрабатывающими разные URL.
Это оригинальный веб-фреймворк, основанный на Servlet API, предназначенный для создания веб-приложений на языке Java, с использованием двух самых популярных шаблонов проектирования - Front controller и MVC.
Spring MVC реализует четкое разделение задач, что позволяет нам легко разрабатывать и тестировать наши приложения. Данные задачи разбиты между разными компонентами:
Компоненты полностью независимы друг от друга, и отвечают только за одно направление. Поэтому MVC дает нам довольно большую гибкость. Он основан на интерфейсах (с предоставленными классами реализации), и мы можем настраивать каждую часть фреймворка с помощью пользовательских интерфейсов.
По запросу определяет, какие перехватчики (interceptors) с пре- и пост-процессорной обработкой запроса должны отработать, а затем решает, какому контроллеру (обработчику) нужно передать данный запрос на исполнение. Процесс их определения основан на некоторых критериях, детали которых зависят от реализации HandlerMapping.
Двумя основными реализациями HandlerMapping являются RequestMappingHandlerMapping (который поддерживает аннотированные методы @RequestMapping) и SimpleUrlHandlerMapping (который поддерживает явную регистрацию путей URI для обработчиков).
Помогает DispatcherServlet вызвать обработчик, сопоставленный с запросом. Для вызова аннотированного контроллера необходимо прочитать аннотации над методами контроллера и принять решение.
Основная цель HandlerAdapter - избавить DispatcherServlet от этой рутины.
Сопоставляет имена представлений, возвращаемых методами контроллеров, с фактическими представлениями (html-файлами).
Отвечает за возвращение ответа клиенту в виде текстов и изображений. Используются встраиваемые шаблонизаторы (Thymeleaf, FreeMarker и т.д.), так как у Spring нет родных. Некоторые запросы могут идти прямо во View, не заходя в Model, другие проходят через все слои.
Определение часового пояса и языка клиента для того, чтобы предложить представления на его языке.
Обеспечивает Upload — загрузку на сервер локальных файлов клиента. По умолчанию этот интерфейс не включается в приложении и необходимо указывать его в файле конфигурации. После настройки любой запрос о загрузке будет отправляться этому интерфейсу.
Сохраняет и извлекает «входной» и «выходной» FlashMap, который можно использовать для передачи атрибутов из одного запроса в другой, обычно через редирект.
Пример
Ниже приведена последовательность событий, соответствующая входящему HTTP-запросу:
Все действия происходят через один единственный DispatcherServlet.
Сконфигурировать наше Spring MVC-приложение мы можем с помощью Java-config, добавив зависимость
spring-webmvc и установив над классом конфигурации
Но, если требуется тонкая настройка, то мы можем имплементировать интерфейс WebMvcConfigurer и переопределить необходимые методы.
Теперь нужно зарегистрировать конфигурацию в Spring Context это позволит сделать созданный нами класс
MyWebAppInitializer, который нужно унаследовать от Abstract
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class>[] getRootConfigClasses() {
return new Class>[] { RootConfig.class };
}
protected Class>[] getServletConfigClasses() {
return new Class>[] { App1Config.class };
}
protected String[] getServletMappings() {
return new String[] { "/*" };
}
}
Своими внутренними методами он создает два экземпляра
WebApplicationContext в виде объектов класса
AnnotationConfig
Если же у нас только один класс конфигурации, то его нужно передать в метод getRootConfigClasses(), а getServletConfigClasses() должен возвращать null.
Все платформы MVC предоставляют способ работы с представлениями.
Spring делает это с помощью ViewResolver, который позволяет отображать модели в браузере, не привязывая реализацию к определенной технологии представления.
ViewResolver сопоставляет имена представлений, возвращаемых методами контроллеров, с фактическими представлениями (html-файлами).
Spring Framework поставляется с довольно большим количеством ViewResolver, например InternalResourceViewResolver, XmlViewResolver, ResourceBundleViewResolver и несколькими другими.
По умолчанию реализацией интерфейса ViewResolver является класс InternalResourceViewResolver.
Любым реализациям ViewResolver желательно поддерживать интернационализацию, то есть множество языков.
Паттерн Front Controller обеспечивает единую точку входа для всех входящих запросов.
Все запросы обрабатываются одним фрагментом кода, который затем может делегировать ответственность за обработку запроса другим объектам приложения. Он также обеспечивает интерфейс для общего поведения, такого как безопасность, интернационализация и передача определенных представлений определенным пользователям.
Как реализовано в Spring?
В Spring в качестве Front Controller выступает DispatcherServlet, все действия проходят через него. Как правило, в приложении задаётся только один DispatcherServlet с маппингом "/", который перехватывает все запросы. Это и есть реализация паттерна Front Controller.
Однако иногда необходимо определить два и более DispatcherServlet-а, которые будут отвечать за свой собственный функционал. Например, чтобы один обрабатывал REST-запросы с маппингом "/api", а другой обычные запросы с маппингом "/default".
Spring предоставляет нам такую возможность, и для начала нужно понять, что:
Каждый DispatcherServlet имеет свой дочерний контекст приложения.
DispatcherServlet по сути является сервлетом (он расширяет HttpServlet), основной целью которого является обработка входящих веб-запросов, соответствующих настроенному шаблону URL. Он принимает входящий URI и находит правильную комбинацию контроллера и вида.
Веб-приложение может определять любое количество DispatcherServlet-ов. Каждый из них будет работать в своем собственном пространстве имен, загружая свой собственный дочерний WebApplicationContext (на рисунке - Servlet WebApplicationContext) с вьюшками, контроллерами и т.д. Например, когда нам нужно в одном Servlet WebApplicationContext определить обычные контроллеры, а в другом REST-контроллеры.
WebApplicationContext расширяет ApplicationContext (создаёт и управляет бинами и т.д.), но помимо этого он имеет дополнительный метод getServletContext(), через который у него есть возможность получать доступ к ServletContext-у.
ContextLoaderListener создает корневой контекст приложения (на рисунке - Root WebApplicationContext) и будет использоваться всеми дочерними контекстами, созданными всеми DispatcherServlet. Напомню, что корневой контекст приложения будет общим и может быть только один. Root WebApplicationContext содержит компоненты, которые видны всем дочерним контекстам, такие как сервисы, репозитории, компоненты инфраструктуры и т.д. После создания корневого контекста приложения он сохраняется в ServletContext как атрибут, имя которого:
WebApplicationContext.class.getName() + ".ROOT"
Чтобы из контроллера любого дочернего контекста обратиться к корневому контексту приложения, мы можем использовать класс WebApplicationContextUtils, содержащий статические методы:
ServletContext context;
ApplicationContext ac = WebApplicationContextUtils
if(ac == null) {
return "root application context is null";
}
Интерфейс , лежит в пакете spring-context. В методах контроллера мы можем использовать объекты Model для того, чтобы складывать туда данные, предназначенные для формирования представлений.
Кроме того, в Model мы можем передать даже Map с атрибутами:
public String passParametersWithModel(Model model) {
Map<String, String> map = new HashMap<>();
map.put("spring", "mvc");
model.addAttribute("message", "Baeldung");
model.mergeAttributes(map);
return "viewPage";
}
Этот класс наследуется от LinkedHashMap <String, Object> и по сути служит общим контейнером модели для Servlet MVC, но не привязан к нему, и лежит в пакете spring-context.
Имеет все преимущества LinkedHashMap плюс несколько удобных методов:public String passParametersWithModelMap(ModelMap map) {
map.addAttribute("welcomeMessage", "welcome");
map.addAttribute("message", "Baeldung");
return "viewPage";
}
Этот класс лежит в пакете spring-webmvc и может одновременно хранить модели и представление, чтобы контроллер мог отдавать их в одном возвращаемом значении.
Внутри содержит поле private Object view, куда записывает нужное представление, а также поле private ModelMap model, куда и складывает все атрибуты модели:
public ModelAndView passParametersWithModelAndView() {
ModelAndView modelAndView = new ModelAndView("viewPage");
modelAndView.addObject("message", "Baeldung");
return modelAndView;
}
@Controller помечает класс как контроллер HTTP-запросов.
@Controller обычно используется в сочетании с аннотацией @RequestMapping, используемой в методах обработки запросов.
Это просто дочерняя аннотация аннотации @Component и позволяет автоматически определять классы при сканировании пакетов.
Аннотация @RestController была введена в Spring 4.0 для упрощения создания RESTful веб-сервисов. Это удобная аннотация, которая объединяет @Controller и @ResponseBody что устраняет необходимость аннотировать каждый метод обработки запросов аннотацией @ResponseBody.
@ResponseBody сообщает контроллеру, что возвращаемый объект автоматически сериализуется в json или xml и передается обратно в объект HttpResponse. Контроллер использует Jackson message converter для конвертации входящих/исходящих данных. Как правило целевые данные представлены в json или xml.
Данный класс используется для формирования ответа HTTP с пользовательскими параметрами (заголовки, код статуса и тело ответа). ResponseEntity необходим, только если мы хотим кастомизировать ответ. Во всех остальных случаях достаточно использовать @ResponseBody.
Если мы хотим использовать ResponseEntity, то просто должны вернуть его из метода, Spring позаботится обо всем остальном.
ResponseEntity<String> customHeader() {
HttpHeaders headers = new HttpHeaders();
headers.add("Custom-Header", "foo");
return new ResponseEntity<>("Custom header set", headers, HttpStatus.OK);
}
Если клиент ждет от нас JSON/XML, мы можем параметризовать ResponseEntity конкретным классом и добавить к ответу заголовки и Http статус:
public ResponseEntity <EmployeeVO> getEmployeeById (@PathVariable("id") int id) {
if (id <= 3) {
EmployeeVO employee = new EmployeeVO(1,"Lokesh","Gupta","howtodoinjava@gmail.com");
return new ResponseEntity<EmployeeVO>(employee, HttpStatus.OK);
}
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
Это интерфейс из пакета javax.servlet, имплементации которого выполняют задачи фильтрации либо по пути запроса к ресурсу (сервлету, либо по статическому контенту), либо по пути ответа от ресурса, либо в обоих направлениях.
Фильтры выполняют фильтрацию в методе doFilter. Каждый фильтр имеет доступ к объекту FilterConfig, из которого он может получить параметры инициализации и ссылку на ServletContext, который он может использовать, например, для загрузки ресурсов, необходимых для задач фильтрации. Фильтры настраиваются в дескрипторе развертывания веб-приложения.
В веб-приложении мы можем написать несколько фильтров, которые вместе называются цепочкой фильтров. Веб-сервер решает, какой фильтр вызывать первым, в соответствии с порядком регистрации фильтров.
Когда вызывается метод
doFilter
Это интерфейс из пакета org.aopalliance.intercept, предназначенный для аспектно-ориентированного программирования.
В Spring, когда запрос отправляется в Controller, перед тем как он в него попадёт, он может пройти через перехватчики Interceptor (0 или более). Это одна из реализаций АОП в Spring. Вы можете использовать Interceptor для выполнения таких задач, как запись в Log, добавление или обновление конфигурации перед тем, как запрос обработается Controller-ом.
Стек перехватчиков: он предназначен для связывания перехватчиков в цепочку в определенном порядке. При доступе к перехваченному методу или полю перехватчик в цепочке перехватчиков вызывается в том порядке, в котором он был определен.
Мы можем использовать Interceptor-ы для выполнения логики до попадания в контроллер, после обработки в контроллере, а также после формирования представления. Также можем запретить выполнение метода контроллера. Мы можем указать любое количество перехватчиков.
Перехватчики работают с HandlerMapping и поэтому должны реализовывать интерфейс HandlerInterceptor или наследоваться от готового класса HandlerInterceptorAdapter.
В случае реализации HandlerInterceptor нам нужно переопределить 3 метода, а в случае HandlerInterceptor, только необходимые нам:
Вызывается после того, как HandlerMapping определил соответствующий контроллер, но до того, как HandlerAdapter вызовет метод контроллера. С помощью этого метода каждый перехватчик может решить, прервать цепочку выполнения или направить запрос на испольнение дальше по цепочке перехватчиков до метода контроллера. Если этот метод возвращает true, то запрос отправляется следующему перехватчику или в контроллер. Если метод возвращает false, то исполнение запроса прекращается, обычно отправляя ошибку HTTP или записывая собственный ответ в response.
Отработает после контроллера, но перед формированием представления. Мы можем использовать этот метод для добавления дополнительных атрибутов в ModelAndView или для определения времени, затрачиваемого методом-обработчиком на обработку запроса клиента. Вы можете добавить больше объектов модели в представление, но вы не можете изменить HttpServletResponse, так как он уже зафиксирован.
Oтработает после формирования представления. Вызывается только в том случае, если метод preHandle этого перехватчика успешно завершен и вернул true!
Следует знать, что HandlerInterceptor связан с бином DefaultAnnotationHandler
Чтобы добавить наши перехватчики в конфигурацию Spring, нам нужно переопределить метод addInterceptors() внутри класса, который реализует WebMvcConfigurer:
public void addInterceptors
// LogInterceptor applies to all URLs.
registry
// This interceptor applies to URL /admin/oldLogin.
// Using OldURLInterceptor to redirect to new URL.
registry
// This interceptor applies to URLs like /admin/*
// Exclude /admin/oldLogin
registry
}
Порядок работы:
HandlerInterceptor в основном похож на Servlet Filter, но в отличие от последнего он просто позволяет настраивать предварительную обработку с возможностью запретить выполнение самого обработчика и настраивать постобработку.
Согласно документации Spring, фильтры более мощные, например, они позволяют обмениваться объектами запроса и ответа, которые передаются по цепочке. Это означает, что фильтры работают больше в области запроса/ответа, в то время как HandlerInterceptors являются бинами и могут обращаться к другим компонентам в приложении.
Фильтры настраивается в web.xml, а HandlerInterceptor в контексте приложения.
Listener(Слушатель)
- это класс, который реализует интерфейс javax.servlet
Он инициализируется только один раз при запуске веб-приложения и уничтожается при остановке веб-приложения.
Слушатель сидит и ждет, когда произойдет указанное событие, затем «перехватывает» событие и запускает собственное событие. Например, мы хотим инициализировать пул соединений с базой данных до запуска веб-приложения. ServletContextListener - это то, что нам нужно, он будет запускать наш код до запуска веб-приложения.
Все ServletContextListeners уведомляются об инициализации контекста до инициализации любых фильтров или сервлетов в веб-приложении.
Все ServletContextListeners уведомляются об уничтожении контекста после того, как все сервлеты и фильтры уничтожены.
Чтобы создать свой Listener нам достаточно создать класс, имплементирующий интерфейс ServletContextListener и поставить над ним аннотацию @WebListener:
public class MyAppServletContextListener implements ServletContextListener {
//Run this before web application is started
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("ServletContextListener started");
}
public void contextDestroyed
System.out.println("ServletContextListener destroyed");
}
}
Да можно.
Можно принять все значения, используя массив в методе контроллера:
http://localhost:8080/login?name=Ranga&name=Ravi&name=Sathish
↓
public String method(@RequestParam(value="name") String[] names) {...}
или
http://localhost:8080/api/foos?id=1,2,3
↓
public String getFoos(@RequestParam List<String> id) {
return "IDs are " + id;
}
Spring Boot - это модуль Spring-а, который предоставляет функцию RAD для среды Spring (Rapid Application Development - Быстрая разработка приложений). Он обеспечивает более простой и быстрый способ настройки и запуска как обычных, так и веб-приложений. Он просматривает наши пути к классам и настроенные нами бины, делает разумные предположения о том, чего нам не хватает, и добавляет эти элементы.
Spring Boot представляет собой комбинацию Spring Framework и встроенного контейнера сервлетов и отсутствие (или минимальное наличие) конфигурации приложения.
Чтобы ускорить процесс управления зависимостями Spring Boot неявно упаковывает необходимые сторонние зависимости для каждого типа приложения на основе Spring и предоставляет их разработчику в виде так называемых starter-пакетов.
Starter-пакеты представляют собой набор удобных дескрипторов зависимостей, которые можно включить в свое приложение. Это позволяет получить универсальное решение для всех технологий, связанных со Spring, избавляя программиста от лишнего поиска необходимых зависимостей, библиотек и решения вопросов, связанных с конфликтом версий различных библиотек.
Например, если вы хотите начать использовать Spring Data JPA для доступа к базе данных, просто включите в свой проект зависимость spring-boot-starter-data-jpa (вам не придется искать совместимые драйверы баз данных и библиотеки Hibernate). Если вы хотите создать Spring web-приложение, просто добавьте зависимость spring-boot-starter-web, которая подтянет в проект все библиотеки, необходимые для разработки Spring MVC-приложений, таких как spring-webmvc, jackson-json, validation-api и Tomcat.
Другими словами, Spring Boot собирает все общие зависимости и определяет их в одном месте, что позволяет разработчикам просто их использовать. Также при использовании Spring Boot, файл pom.xml содержит намного меньше строк, чем в Spring-приложениях.
Автоматическая конфигурация включается аннотацией @EnableAutoConfiguration (входит в состав аннотации @SpringBootApplication).
После выбора необходимых для приложения starter-пакетов Spring Boot попытается автоматически настроить Spring-приложение на основе выбранных jar-зависимостей, доступных в classpath классов, свойств в application.properties и т.п.
Например, если добавим spring-boot-starter-web, то Spring boot автоматически сконфигурирует такие бины как DispatcherServlet, ResourceHandlers, MessageSource и т.д.
Автоматическая конфигурация работает в последнюю очередь , после регистрации пользовательских бинов и всегда отдает им приоритет.
Если ваш код уже зарегистрировал бин DataSource — автоконфигурация не будет его переопределять.
Каждое Spring Boot web-приложение включает встроенный web-сервер. Не нужно беспокоиться о настройке контейнера сервлетов и развертывания приложения в нем. Теперь приложение может запускаться само как исполняемый jar-файл с использованием встроенного сервера.
Готовые к работе функции, такие как метрики, проверки работоспособности, security и внешняя конфигурация.
Инструмент CLI (command-line interface) для разработки и тестирования приложения Spring Boot.
Минимизация boilerplate кода (код, который должен быть включен во многих местах практически без изменений), конфигурации XML и аннотаций.
Отмечаем main класс аннотацией @SpringBootApplication. Аннотация инкапсулирует в себе:
таким образом наличие @SpringBootApplication включает сканирование компонентов, автоконфигурацию и показывает разным компонентам Spring (например, интеграционным тестам), что это Spring Boot приложение.
@EnableAutoConfiguration импортирует класс Enable
Этот класс не объявляет бины сам, а использует так называемые фабрики.
Класс Enable
Т.е. аннотация @EnableAutoConfiguration просто импортирует ВСЕ (более 150) перечисленные в spring.factories конфигурации, чтобы предоставить нужные бины в контекст приложения.
Каждая из этих конфигураций пытается сконфигурировать различные аспекты приложения (web, JPA, AMQP и т.д.), регистрируя нужные бины. Логика при регистрации бинов управляется набором @ConditionalOn* аннотаций. Можно указать, чтобы бин создавался при наличии класса в classpath (@ConditionalOnClass), наличии существующего бина (@ConditionalOnBean), отсуствии бина (@ConditionalOnMissingBean) и т.п. Таким образом наличие конфигурации не значит, что бин будет создан, и в большинстве случаев конфигурация ничего делать и создавать не будет.
Созданный в итоге Annotation
Servlet container запускается, приложение готово к работе!
Определение от WiKi
Сервлет является интерфейсом Java, реализация которого расширяет функциональные возможности сервера. Сервлет взаимодействует с клиентами посредством принципа запрос-ответ.
Java Servlet API — стандартизированный API, предназначенный для реализации на сервере и работе с клиентом по схеме запрос-ответ.
Сервлет — это класс, который умеет получать запросы от клиента и возвращать ему ответы. С помощью них строится клиент-серверная архитектура.