Бины и контейнер
В обычной программе на Java вы сами создаёте объекты через new. В Spring эту работу берёт на себя контейнер (его ещё называют контекстом приложения). При запуске он находит ваши классы, создаёт по одному объекту каждого и хранит их. Объект, которым управляет контейнер, называется бином (bean).
Как пометить класс
Контейнер создаёт бины из классов с особыми аннотациями:
| Аннотация | Для чего |
|---|---|
@Component | Любой вспомогательный класс |
@Service | Класс с логикой приложения |
@Repository | Класс для работы с данными |
@Controller, @RestController | Обработчики HTTP-запросов |
@Configuration | Класс с настройками и методами @Bean |
Технически все они — разновидности @Component. Разные названия помогают читать код: по аннотации сразу видно роль класса.
Первый сервис
// src/main/java/com/example/blog/PostService.java
package com.example.blog;
import java.util.List;
import org.springframework.stereotype.Service;
@Service
public class PostService {
public List<String> titles() {
return List.of("Привет, Spring", "Зачем нужны записи");
}
}Благодаря @Service контейнер при старте создаст один объект PostService. Вызывать new PostService() нигде не нужно.
Как получить бин
Другой бин получает сервис через конструктор — Spring сам передаст готовый объект:
// src/main/java/com/example/blog/TitleController.java
package com.example.blog;
import java.util.List;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TitleController {
private final PostService postService;
public TitleController(PostService postService) {
this.postService = postService;
}
@GetMapping("/api/titles")
public List<String> titles() {
return postService.titles();
}
}Запрос curl http://localhost:8080/api/titles вернёт ["Привет, Spring","Зачем нужны записи"]. Как именно Spring подбирает аргументы конструктора, разберём в следующем уроке.
Где Spring ищет классы
Поиск (component scanning) начинается с пакета, в котором лежит класс с @SpringBootApplication, то есть com.example.blog, и охватывает все его подпакеты. Класс в com.example.blog.posts будет найден, а в com.example.other — нет.
Бин из чужого класса
На класс из библиотеки аннотацию не повесить. Для таких случаев пишут метод с @Bean в классе настроек:
// src/main/java/com/example/blog/AppConfig.java
package com.example.blog;
import java.time.Clock;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public Clock clock() {
return Clock.systemUTC();
}
}Spring вызовет clock() один раз и запомнит результат как бин типа Clock с именем clock. Теперь любой класс может попросить Clock в конструкторе — это пригодится, чтобы ставить дату создания статьи.
Частая ошибка
Если забыть аннотацию @Service или положить класс вне пакета com.example.blog, приложение не запустится:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.blog.PostController required a bean of type 'com.example.blog.PostService' that could not be found.Spring говорит: контроллеру нужен PostService, но такого бина нет. Проверьте аннотацию и пакет класса.