Spring Framework - DmitryGontarenko/usefultricks GitHub Wiki
Spring - фреймворк, который состоит из множества компонентов и облегчает множество аспектов разработки приложений на Java. Например:
- Контекст приложений (Application Context) и Внедрение зависимостей (Dependency Injection)
- Удобный и эффективный доступ к БД (замета JDBC)
- Компоненты для разработки Web-приложений (Spring MVC)
- С помощью Spring Security можно обезопасить ваше приложение, настроить авторизация, аутентификацию и т.д.
- Spring Boot - упрощает разработку на Spring, избавляя нас от лишний конфигурации.
Типичное Java приложение - это набор Java объектов, которые взаимодействуют друг с другом и ссылаются на друг друга. Такая ссылка называется зависимостью, т.е. один объект зависит от другого. Чем больше и сложнее приложение, тем больше объектов и сложнее связи между ними. Spring помогает нам в работе с этим множеством объектов.
Без использование Spring пришлось бы создавать объекты вручную:
Engine engine = new Engine();
Возникающие проблемы:
- Код становится запутанным
- У класса, объект которого мы создаем, тоже могут быть свои зависимости (объекты, которые нужны ему для работы)
- Часто нам необходимо один объект делить между всеми другими объектами. Такой объект должен создаваться только один раз. Это может быть, например, класс Database, который содержит в себе настройки подключения к БД. Можно решить проблему с помощью паттерна Singleton, но для этого требуется писать дополнительный код. Нам так же понадобиться внедрять ссылку на объект Database во все остальные классы, это можно сделать вручную - но будет сложно и запутанно.
Такие проблемы можно решить с помощью Spring:
- Мы создаем объекты, которые необходимы нам для работы;
- Описываем и связываем эти объекты с помощью Spring конфигурации (XML, annotation or Java code);
- Spring считывают эту конфигурацию и помещает объекты (бины) в Spring Application Context, а так же берет на себя управление их жизненным циклом. Он сам внедряет все необходимые зависимости, нам остается только описать эту связь (Dependency Injection);
- Мы используем все необходимые нам объекты из Spring контейнера (Application Context).
Bean - это Java объект, когда Java объекты создаются с помощью Spring`a, они называются бинами.
По сути, приложение Спринг — это набор бинов, связанных вместе через контейнер.
Способы конфигурирования Spring приложений:
Для работы со Spring нам необходимо:
- Подключить зависимости:
spring-core
spring-beans
spring-context - Создать конфигурационный файл:
resources/application-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
...
</beans>
- Создать бин нашего класса в конфигурационном файле:
<bean id="testBean" class="com.package.TestBean">
<constructor-arg value="Dmitriy"/>
</bean>
С помощью атрибута constructor-arg
мы сможем передавать какое-либо значение в конструктор класса.
4. Теперь необходимо что бы Spring прочитал наш конфигурационный файл и создал этот бин, и положил его в свой Application Context.
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
TestBean testBean = context.getBean("testBean", TestBean.class);
System.out.println(testBean.getName()); // Dmitriy
context.close();
ClassPathXmlApplicationContext
- обращается к конфигурационному файлу (application-context.xml), считывает его и помещает все бины которые там описаны в Application Context.
Получаем наш бин с помощью context.getBean()
. Первым аргументом передаем id бина, вторым аргументом указываем тот класс, бин которого мы хотим получить.
Когда мы заканчиваем работу с Application Context, его необходимо закрыть - context.close()
.
Это такой архитектурный подход (абстрактный принцип), когда сущность не сама создает своим зависимости, а когда этой сущности зависимости поставляются извне.
public class MusicPlayer {
private Music music;
// Зависимость внедряется извне
public MusicPlayer(Music music) { this.music = music; }
public String playMusic() { System.out.println(music.getSong()) }
}
Объект, который мы хотим внедрить в сущность MusicPlayer, и который реализует интерфейс Music, нужно где то создавать.
Решение - использовать Dependency Injection (Внедрение зависимостей).
Сделаем это с помощью XML-конфигурации:
<bean id="musicBean" class="com.home.sl.ClassicalMusic" />
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<constructor-arg ref="musicBean"/>
</bean>
С помощью атрибута constructor-arg
указываем, что конструктору класса MusicPlayer
в качестве аргумента будет передан бин musicBean
. Теперь Java-класс будет выглядеть так:
MusicPlayer musicPlayer = context.getBean("musicPlayer", MusicPlayer.class);
musicPlayer.playMusic(); // Sebastian Bach - Joke
Внедрение зависимостей - это реализация принципа инверсии управления.
public MusicPlayer(Music music) { this.music = music; }
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<constructor-arg ref="musicBean"/>
</bean>
public void setMusic(Music music) { this.music = music; }
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<property name="music" ref="musicBean"/>
</bean>
Атрибут property
используется для внедрения значений через сеттеры. Передавать можно не только ссылки, но и значения. Добавим в наш класс MusicPlayer
поля name
и volume
, и определим для них сеттеры.
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<property name="music" ref="musicBean"/>
<property name="name" value="Radio"/>
<property name="volume" value="50"/>
</bean>
Значение для внедряемых зависимостей можно поместить в отдельный файл.
- Создаем файл
application.properties
musicPlayer.name=Radio
musicPlayer.volume=70
- Импортируем его в наш
application-context
и используем созданные значения:
<context:property-placeholder location="classpath:application.properties"/>
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<property name="music" ref="musicBean"/>
<property name="name" value="${musicPlayer.name}"/>
<property name="volume" value="${musicPlayer.volume}"/>
</bean>
singleton - по умолчанию создается один объект (он создается до вызова метода getBean()
). Соответственно, при всех вызовах getBean()
будет возвращаться ссылка на один и тот же объект.
prototype - каждый раз создает новый объект при вызове метода getBean()
.
Spring предоставляет ещё три scope, которые доступны только при использовании web специфичных ApplicationContext:
request - создаётся один экземпляр бина на каждый HTTP запрос.
session - создаётся один экземпляр бина на каждую HTTP сессию.
global-session - создаётся один экземпляр бина на каждую глобальную HTTP сессию.
Вы запускаете Spring приложение -> Запускается Spring контейнер -> Создается объект бина (чтение конфигурационного файла) -> В бин внедряются зависимости (dependency injection) -> Вызывается указанный init-method -> Бин готов к использованию -> Использование бина (передача пользователю) -> Вызывается указанный destroy-method -> Остановка Spring приложения.
В init-method
содержится логика, которая должна сопутствовать созданию бина.
destroy-method
запускается в ходе уничтожения бина (закрытия потоков ввода=вывода, закрытия доступа к БД).
- У этих методов может быть любой модификатор доступа (
public
,protected
,private
). -
Тип возвращаемого значения может быть любой, но чаще всего используется
void
(т.к. нет возможности получить возвращаемое значение). - Название может быть любым.
- Эти методы не должны принимать какие-либо аргументы.
- Для бинов с атрибутом
scope="prototype"
Spring не вызывает destroy метод, а init метод будет вызываться столько раз, сколько создана объектов бина.
Реализуем это на Java:
class ClassicalMusic implements Music {
public void doMyInit() { System.out.println("Doing my initialization"); }
public void doMyDestroy() { System.out.println("Doing my destruction"); }
public String getSong() { return "Sebastian Bach - Joke"; }
}
Указываем в атрибутах бина созданные методы:
<bean id="musicBean"
class="com.home.sl.RockMusic"
init-method="doMyInit"
destroy-method="doMyDestroy"/>
Получаем бин и вызываем метод getSong()
у класса ClassicalMusic
:
ClassicalMusic classicalMusic = context.getBean("musicBean", ClassicalMusic.class);
System.out.println(classicalMusic.getSong());
Результат:
Doing my initialization
Sebastian Bach - Joke
Doing my destruction
Конфигурирование Spring`а с помощью аннотаций.
Когда мы используем аннотации, Спринг сканирует наши классы и авоматически создает бины.
- Для начало включим в конфигурационном файле сканирование всех наших классов:
<context:component-scan base-package="com.home.sl" />
- Аннотацией
@Component
помечаем класс, из которого будет создан бин:
@Component
public class ClassicalMusic implements Music {...}
С помощью аннотации @Autowired
мы больше не внедряем зависимости вручную, Spring сам ищет подходящий бин и автоматически вндряет его.
@Autowired
можно использовать на полях, конструкторах или сеттерах (по факту любое имя метода):
@Autowired
private Music music;
@Autowired
public MusicPlayer(Music music) { this.music = music; }
@Autowired
public void setMusic(Music music) { this.music = music; }
Мы можем указывать аннотацию как над классами, так и над интерфейсами (тогда Спринг будет искать те бины, которые реализуют данный интерфейс).
Может возникнуть ошибка, когда Spring найдет несколько бинов, которые могут подходить для внедрения зависимости. Для того, что бы уточнить, какой из бинов мы хотим внедрить, существует аннотация @Qualifier
.
Пример использования для полей и констуктора:
@Autowired
@Qualifier("rockMusic")
private Music music;
public MusicPlayer(@Qualifier("rockMusic") Music music1,
@Qualifier("classicalMusic") Music music2) {
this.music1 = music1;
this.music2 = music2;
}
С помощью аннотации @Scope
можно так же указать необходимое значения для бина:
@Component
@Scope("singleton")
public class ClassicalMusic implements Music {...}
Так же в Spring существуют аннотации для init и destroy-методов, это @PostConstract
и @PreDestroy
соответственно:
@PostConstruct
public void doMyInit() { System.out.println("Doing my init"); }
@PreDestroy
public void doMyDestroy() { System.out.println("Doing my destroy"); }
Аннотация @Value
позволяет внедрять значения из внешнего файла. Первые шаги идентичны подключению в XML, но теперь мы внедряем значение с помощью аннотации @Value
:
@Value("${musicPlayer.name}")
private String name;
Конфигурирование Spring`а с помощью Java-кода и аннотаций.
Аннотацией @Configuration
помечается Java класс, который мы хотим использовать для конфигурирования Спринг приложения.
Для каждого XML тега есть соответствующая Аннотация.
С помощью Аннотации @Bean
можно вручную внедрить зависимости (без @Autowired
). Аннотация PropertySource
позволяет внедрять значения из внешнего файла.
Пример на Java:
@Configuration
@ComponentScan("com.home.ls")
@PropertySource("classpath:application.properties")
public class SpringConfig {
@Bean
@Scope("singleton")
public ClassicalMusic classicalMusic() { return new ClassicalMusic(); }
@Bean
public MusicPlayer musicPlayer() { return new MusicPlayer(classicalMusic()); }
}
Как бы это выглядело на XML:
<context:component-scan base-package="com.home.ls" />
<context:property-placeholder location="classpath:application.properties"/>
<bean id="musicBean" class="com.home.ls.ClassicalMusic" scope="singleton"/>
<bean id="musicPlayer" class="com.home.sl.MusicPlayer">
<constructor-arg ref="musicBean"/>
</bean>
Теперь, что бы обратиться к Spring-контексту, необходимо создавать объект класса AnnotationConfigApplicationContext
(в замен ClassPathXmlApplicationContext
), в конструкторе которого указывается класс, помеченный Аннотацией @Configuration
.
AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext(SpringConfig.class);
Интерфейс BeanPostProcessor
позволяет реализовать методы перед инициализацией и после уничтожения экземпляра бина.
ApplicationContext автоматически обнаруживает любые бины, которые реализуют BeanPostProcessor
и регистрируют их как post-processors для того, чтобы создать их определённым способом.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
/**
* Модель
*/
public class MessageDto {
private String message;
public String getMessage() {
return "Your message: " + message;
}
public void setMessage(String message) {
this.message = message;
}
public void init() {
System.out.println("bean init method");
}
public void destroy() {
System.out.println("bean destroy method");
}
}
/**
* Реализация BeanPostProcessor
*/
public class BeanPostProcessorImpl implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before Initialization " + beanName);
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After Initialization " + beanName);
return bean;
}
}
public class BppApplication {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
MessageDto message = (MessageDto) context.getBean("messageDto");
System.out.println(message.getMessage());
context.registerShutdownHook();
}
}
Спринг конфигурация:
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id = "messageDto" class = "com.home.bpp.MessageDto"
init-method = "init" destroy-method = "destroy">
<property name = "message" value = "Hello World!"/>
</bean>
<bean class = "com.home.bpp.BeanPostProcessorImpl" />
</beans>
Результат выполнения программы:
Before Initialization messageDto
bean init method
After Initialization messageDto
Your message: Hello World!
bean destroy method