Изучение Spring MVC напоминает процесс очеловечивания робота: вы добавляете уши (контроллеры для принятия запросов), мозг (сервисы для обработки логики) и руки (репозитории для работы с базой данных). Сегодня мы сосредоточимся на ушах и мозге. Репозитории уже были созданы ранее, так что стоит доверить им непосредственное взаимодействие с базой данных.
Что такое контроллеры и сервисы в Spring MVC?
Контроллеры получают входящие запросы и решают, как их обработать, передавая задачи сервисам. Сервисы, в свою очередь, реализуют бизнес-логику, а если потребуется, обращаются к репозиториям (аналог склада с ингредиентами).
Controller:
- Отвечает за маршрутизацию запросов на правильный обработчик.
- Обрабатывает входные данные (например, JSON из HTTP-запроса).
- Возвращает ответ клиенту.
Service:
- Выполняет основную бизнес-логику.
- Декларирует и/или использует методы репозитория для работы с базой данных.
- Гарантирует, что функциональность является изолированной и переиспользуемой.
Создание REST-контроллеров: обработка входящих HTTP-запросов
REST-контроллеры — это сердце вашего веб-приложения. Они принимают HTTP-запросы (GET, POST, PUT, DELETE) и возвращают ответы. Для их создания используется аннотация @RestController, а эндпоинты задаются с помощью аннотаций @RequestMapping, @GetMapping, @PostMapping и т.д.
Пример минимального контроллера:
@RestController // Говорит Spring, что этот класс будет контроллером
@RequestMapping("/api/v1/users") // Определяет базовый путь для всех эндпоинтов внутри контроллера
public class UserController {
@GetMapping("/{id}") // Обработка GET-запросов по пути /api/v1/users/{id}
public String getUserById(@PathVariable Long id) {
return "User with ID: " + id; // Возвращает строку с ID пользователя
}
@PostMapping // Обработка POST-запросов на /api/v1/users
public String createUser(@RequestBody String userName) {
return "Created user: " + userName; // Возвращает сообщение
}
}
@RestController: указывает, что возвращаемые данные будут в формате JSON или XML.@RequestMapping: устанавливает базовый URL для всех обработчиков в этом контроллере.@GetMapping,@PostMappingи т.д.: настраивают обработку определённых HTTP-методов (GET, POST, DELETE, PUT).@PathVariable: позволяет извлекать параметры из части URL.@RequestBody: читает тело запроса, преобразуя его из JSON в объект Java.
Разработка сервисного слоя для бизнес-логики
Как мы выяснили, контроллеры не должны содержать бизнес-логику — это зона ответственности сервисов. Они выступают мостом между контроллерами и репозиториями. Сервисы создаются как компоненты Spring с использованием аннотации @Service.
Пример сервиса:
@Service // Помечает класс как Spring-компонент
public class UserService {
private final UserRepository userRepository; // Репозиторий для взаимодействия с базой данных
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id) // Получаем пользователя из базы
.orElseThrow(() -> new RuntimeException("User not found!")); // Ошибка, если пользователь не найден
}
public void createUser(User user) {
userRepository.save(user); // Сохраняем пользователя в базу данных
}
}
@Service: сообщает Spring, что это компонент, содержащий бизнес-логику.UserRepository: автоматически внедряется через конструктор (Dependency Injection, DI).findByIdиsave: стандартные методы репозитория для CRUD-операций.
Интеграция контроллеров с сервисами
Контроллеры и сервисы работают в связке. Контроллер вызывает методы сервиса для обработки запросов. Для этого мы передаём сервис как зависимость в контроллер через DI.
Пример интеграции:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService; // Внедрение зависимости
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id); // Вызов метода сервиса
}
@PostMapping
public void createUser(@RequestBody User user) {
userService.createUser(user); // Вызов метода сервиса
}
}
Улучшение структуры проекта через использование паттернов
Чтобы ваш проект был понятным и поддерживаемым, стоит следовать таким паттернам:
- Controller-Service-Repository: никогда не смешивайте логику из контроллеров и сервисов.
- DTO (Data Transfer Object): используйте DTO для передачи данных между слоями. Это особенно важно, когда структура сущности базы данных и данных, ожидаемых клиентом, различается.
- Распределение обязанностей: контроллеры занимаются только запросами, сервисы реализуют логику, а репозитории работают с базой данных.
Пошаговый пример разработки
Создадим простую функциональность для работы с пользователями.
1. Сущность:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Геттеры и сеттеры...
}
2. Репозиторий:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Возможны дополнительные кастомные запросы
}
3. Сервис:
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public void saveUser(User user) {
userRepository.save(user);
}
}
4. Контроллер:
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUserById(id));
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
@PostMapping
public ResponseEntity<String> createUser(@RequestBody User user) {
userService.saveUser(user);
return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully");
}
}
Типичные ошибки и способы их устранения
- Смешивание логики в контроллере: не пишите бизнес-логику в контроллере. Сервисы лучше для этого подходят.
- Отсутствие обработки ошибок: убедитесь, что вы обрабатываете исключения (например, через
@ExceptionHandlerили@ControllerAdvice). - Невалидация данных: перед использованием данных запросов примените валидацию через аннотацию
@Valid. - Циклические зависимости: используйте DI корректно, избегая взаимных ссылок между компонентами.
Теперь ваши контроллеры и сервисы готовы к обработке запросов, а мозг и уши вашего приложения отлично сработаны. В следующей лекции мы продолжим стройку нашего фулл-стека, погружаясь в вопросы безопасности и аутентификации через Spring Security!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ