Spring Framework — полный курс
Spring Framework — самый популярный фреймворк для разработки на Java. Этот курс посвящён ядру Spring: IoC-контейнеру, внедрению зависимостей, AOP, веб-модулю, работе с БД и безопасности.
1. Что такое Spring
Spring Framework — это инфраструктурный фреймворк, созданный Родом Джонсоном в 2003 году для упрощения разработки Java-приложений. Сегодня Spring является стандартом де-факто в enterprise-разработке.
Основные модули Spring Framework:
- Spring Core — ядро фреймворка: IoC-контейнер и внедрение зависимостей.
- Spring Context — расширение Core, предоставляет механизм доступа к объектам через ApplicationContext.
- Spring AOP — аспектно-ориентированное программирование для кросс-курентной логики (логирование, транзакции).
- Spring JDBC — упрощённая работа с JDBC: шаблоны запросов и транзакционное управление.
- Spring MVC — веб-модуль для построения REST API и веб-приложений на основе DispatcherServlet.
- Spring Security — фреймворк аутентификации и авторизации с поддержкой OAuth2 и JWT.
2. Инверсия управления и Dependency Injection
Классический подход к зависимостям приводит к жёсткой связанности между компонентами:
public class OrderService {
private final OrderRepository repository = new OrderRepositoryImpl();
private final NotificationService notifier = new EmailNotificationService();
}Проблема: класс сам создаёт свои зависимости. Это затрудняет тестирование, замену реализации и изменение конфигурации.
Инверсия управления (Inversion of Control) означает, что объекты не создают зависимости сами — их создаёт и внедряет внешний контейнер. Контейнер, реализующий этот принцип в Spring, называется IoC-контейнер.
ApplicationContext — основной интерфейс IoC-контейнера. Он создаёт бины, управляет их жизненным циклом и обеспечивает внедрение зависимостей.
public class OrderService {
private final OrderRepository repository;
private final NotificationService notifier;
public OrderService(OrderRepository repository, NotificationService notifier) {
this.repository = repository;
this.notifier = notifier;
}
}Теперь зависимости передаются извне, а класс ничего не знает о конкретных реализациях.
3. Настройка контекста
Ранее Spring-приложения настраивали через XML-файлы:
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="orderService" class="com.example.OrderService">
<constructor-arg ref="orderRepository"/>
</bean>
</beans>Сегодня рекомендуется использовать Java-based конфигурацию:
@Configuration
public class AppConfig {
@Bean
public OrderRepository orderRepository() {
return new JdbcOrderRepository();
}
@Bean
public NotificationService notificationService() {
return new EmailNotificationService();
}
@Bean
public OrderService orderService() {
return new OrderService(orderRepository(), notificationService());
}
}Контекст создаётся аннотацией AnnotationConfigApplicationContext:
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService service = context.getBean(OrderService.class);4. Бины и области видимости
Bean — это объект, управляемый IoC-контейнером Spring. Чтобы зарегистрировать класс как бин, используются аннотации компонентного сканирования:
@Component— универсальная аннотация для обозначения бина.@Service— специализированная для сервисного слоя (деловая логика).@Repository— специализированная для слоя доступа к данным (DAO).
@Service
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
}Для включения сканирования добавляют аннотацию:
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}Области видимости (Scope)
| Scope | Описание |
|---|---|
| singleton | Один экземпляр на весь контейнер (по умолчанию) |
| prototype | Новый экземпляр при каждом запросе getBean() |
| request | Один экземпляр на HTTP-запрос (веб-контекст) |
| session | Один экземпляр на HTTP-сессию (веб-контекст) |
@Service
@Scope("prototype")
public class WidgetService {
}5. Внедрение зависимостей
Spring поддерживает несколько способов внедрения зависимостей:
Внедрение через конструктор (рекомендуемый)
@Service
public class PaymentService {
private final PaymentGateway gateway;
private final TransactionLog log;
@Autowired
public PaymentService(PaymentGateway gateway, TransactionLog log) {
this.gateway = gateway;
this.log = log;
}
}@Autowired можно опустить — контейнер вызовет его автоматически.Внедрение через setter и поле
@Service
public class ReportService {
@Autowired
private DataSource dataSource;
@Autowired
public void setFormatter(ReportFormatter formatter) {
this.formatter = formatter;
}
}Уточнение бина: @Qualifier
Если существует несколько реализаций одного интерфейса:
@Service
@Qualifier("email")
public class EmailNotificationService implements NotificationService { }
@Service
@Qualifier("sms")
public class SmsNotificationService implements NotificationService { }
@Service
public class AlertService {
@Autowired
public AlertService(@Qualifier("email") NotificationService notifier) {
this.notifier = notifier;
}
}Аннотация @Resource (из пакета javax.annotation) работает аналогично @Autowired, но ищет бин по имени поля, а не по типу.
6. Конфигурация через Java
Класс с аннотацией @Configuration заменяет XML-конфигурацию. Каждый метод с аннотацией @Bean определяет способ создания объекта:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Value("${db.url}")
private String dbUrl;
@Value("${db.pool.size}")
private int poolSize;
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(dbUrl);
ds.setMaximumPoolSize(poolSize);
return ds;
}
}Файл application.properties:
db.url=jdbc:postgresql://localhost:5432/mydb
db.pool.size=10@Bean в классе @Configuration являются CGLIB-прокси: Spring перехватывает вызовы и гарантирует, что возвращается один и тот же экземпляр для singleton-бинов.7. Аспектно-ориентированное программирование (AOP)
AOP позволяет вынести кросс-курентную логику (логирование, безопасность, транзакции) в отдельные модули — аспекты. Вместо того чтобы добавлять логирование в каждый метод сервиса, вы описываете его один раз.
Ключевые термины:
- Join point — точка в выполнении программы, где можно применить аспект (метод, конструктор, обработка исключения).
- Advice — код аспекта, выполняемый в определённый момент (до, после, вокруг).
- Pointcut — выражение, определяющее набор join point'ов.
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logMethodCall(JoinPoint joinPoint) {
String method = joinPoint.getSignature().getName();
System.out.println("Вызов метода: " + method);
}
@Around("execution(* com.example.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
Object result = joinPoint.proceed();
long duration = System.nanoTime() - start;
System.out.println(joinPoint.getSignature().getName() + " — " + duration + " ns");
return result;
}
}Для включения AOP добавляется аннотация:
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}8. Spring JDBC и транзакции
Традиционный JDBC требует много шаблонного кода. Spring предлагает JdbcTemplate для его упрощения:
@Repository
public class UserRepository {
private final JdbcTemplate jdbc;
public UserRepository(DataSource dataSource) {
this.jdbc = new JdbcTemplate(dataSource);
}
public User findById(long id) {
return jdbc.queryForObject(
"SELECT id, name, email FROM users WHERE id = ?",
new BeanPropertyRowMapper<>(User.class),
id
);
}
public List<User> findAll() {
return jdbc.query(
"SELECT id, name, email FROM users",
new BeanPropertyRowMapper<>(User.class)
);
}
public void save(User user) {
jdbc.update(
"INSERT INTO users (name, email) VALUES (?, ?)",
user.getName(), user.getEmail()
);
}
}Транзакции
Аннотация @Transactional автоматически открывает и закрывает транзакцию. Под капотом Spring создаёт прокси-объект, который оборачивает вызов метода:
@Service
public class TransferService {
private final AccountRepository accounts;
public TransferService(AccountRepository accounts) {
this.accounts = accounts;
}
@Transactional
public void transfer(long fromId, long toId, BigDecimal amount) {
Account from = accounts.findById(fromId);
Account to = accounts.findById(toId);
from.debit(amount);
to.credit(amount);
accounts.save(from);
accounts.save(to);
}
}@Transactional изнутри того же класса не пройдёт через прокси, и транзакция не откроется.9. Spring MVC
Веб-модуль Spring построен на фронт-контроллере DispatcherServlet. Вот как обрабатывается HTTP-запрос:
- Запрос приходит в
DispatcherServlet. - Он делегирует обработку нужному
@ControllerчерезHandlerMapping. - Контроллер обрабатывает запрос и возвращает имя представления или объект
ResponseEntity. ViewResolverпреобразует имя в объект View.- View рендерит ответ (или возвращается JSON для REST API).
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService service;
public UserController(UserService service) {
this.service = service;
}
@GetMapping
@ResponseBody
public List<UserDto> getAll() {
return service.findAll();
}
@GetMapping("/{id}")
@ResponseBody
public UserDto getById(@PathVariable long id) {
return service.findById(id);
}
@PostMapping
@ResponseBody
@ResponseStatus(HttpStatus.CREATED)
public UserDto create(@RequestBody CreateUserRequest request) {
return service.create(request);
}
}@RestController вместо @Controller — это эквивалентно добавлению @ResponseBody ко всем методам класса.Отличие от Spring Boot
В «чистом» Spring MVC необходимо вручную настраивать DispatcherServlet через web.xml или программно. Spring Boot автоматически настраивает встроенный сервер Tomcat, DispatcherServlet, ViewResolver и множество других компонентов.
10. Spring Security (обзор)
Spring Security — это мощный фреймворк для обеспечения безопасности приложений. Архитектура основана на цепочке фильтров (Security Filter Chain):
- AuthenticationFilter — проверяет подлинность пользователя (форма входа, JWT, OAuth2 токен).
- AuthorizationFilter — проверяет права доступа к ресурсу.
- BCryptPasswordEncoder — хеширование паролей алгоритмом BCrypt.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
)
.logout(logout -> logout.permitAll());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}11. Spring Boot упрощает всё
Spring Boot автоматизирует большинство рутинных операций при настройке Spring-приложений:
- Автоматическая конфигурация (Auto-Configuration) — подбор настроек на основе зависимостей в classpath.
- Встроенный сервер — Tomcat, Jetty или Undertow без отдельного развертывания.
- Файлы конфигурации —
application.propertiesиapplication.ymlвместо XML. - Актuator — встроенные эндпоинты для мониторинга приложения.
- Spring Initializr — генератор проекта на start.spring.io.
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}Одна аннотация @SpringBootApplication заменяет @Configuration, @EnableAutoConfiguration и @ComponentScan.
12. Профили и настройка окружений
Один код работает в разных окружениях: dev, test, prod. Профили Spring позволяют включать разные конфигурации и бины в зависимости от окружения.
@Configuration
@Profile("prod")
public class ProdConfig {
@Bean
DataSource dataSource() {
return DataSourceBuilder.create()
.url("jdbc:postgresql://db-prod:5432/app")
.username("${DB_USER}")
.password("${DB_PASSWORD}")
.build();
}
}# application-prod.properties
spring.profiles.active=prod
server.port=8080
app.retry.max=5- активный профиль задаётся через переменную окружения
SPRING_PROFILES_ACTIVE; - секреты — через переменные окружения, никогда не в git;
- конфигурация
@Profile("!test")исключает бины в тестах.
13. Асинхронность и события
Долгие операции (отправка писем, обработка файлов) не должны блокировать HTTP-запрос. Spring переводит такие задачи в фоновый поток или очередь.
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(16);
executor.setQueueCapacity(100);
return executor;
}
}@Service
public class NotifyService {
@Async
public void sendWelcomeEmail(String email) {
// трудоёмкая отправка не блокирует ответ
}
}Ключевое правило: @Async работает только при вызове метода через другой бин — внутренний самовызов игнорирует прокси.
@Async("taskExecutor")
public CompletableFuture<Order> loadOrder(Long id) { ... }14. Тестирование Spring-приложений
Тесты подтверждают, что контекст поднимается, зависимости связываются, а логика соответствует ожиданиям. Spring Test поднимает контекст один раз и переиспользует его.
@SpringBootTest
@ActiveProfiles("test")
class OrderServiceTest {
@Autowired
OrderService orderService;
@Test
void createOrder_calculatesTotal() {
Order order = orderService.create(42, List.of(10, 20));
assertEquals(30, order.total());
}
}| Аннотация | Назначение |
|---|---|
| @SpringBootTest | полный контекст для интеграционных тестов |
| @DataJpaTest | срез только для JPA-репозиториев |
| @WebMvcTest | тест контроллеров с мок-сервисами |
| @MockBean | подмена бина моком |
15. Обработка исключений и валидация
Каждый непойманный exception в контроллере превращается в 500-ответ без понятного сообщения. Централизованная обработка через @RestControllerAdvice перехватывает исключения и отдаёт аккуратный JSON.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ApiError> handleNotFound(NotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ApiError(e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleValidation(
MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldErrors()
.stream()
.map(f -> f.getField() + ": " + f.getDefaultMessage())
.collect(Collectors.joining("; "));
return ResponseEntity.badRequest().body(new ApiError(msg));
}
}Валидацию входных данных выполняют аннотации Bean Validation (jakarta.validation):
public record CreateUserRequest(
@NotBlank @Email String email,
@Size(min = 8) String password,
@Min(18) int age
) {}16. Spring Data JPA
Spring Data JPA снимает шаблонный код репозиториев: достаточно объявить интерфейс, и контейнер сам создаст реализацию, генерирующую запросы по имени метода.
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String email;
private String name;
}
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByEmailContaining(String part);
Optional<User> findByEmail(String email);
@Query("select u from User u where u.name = :name")
List<User> searchByName(@Param("name") String name);
}Бизнес-логика живёт в сервисе, репозиторий вызывается из него. Управление транзакциями совместимо: @Transactional из раздела 8 работает и здесь.
| Способ запроса | Пример |
|---|---|
| по имени метода | findByEmailContaining |
| JPQL через @Query | select u from User u |
| пагинация | Pageable и Page<User> |
| сортировка | Sort.by("name") |
17. Кэширование: @Cacheable
Одни и те же данные — каталог, курсы, настройки — запрашиваются снова и снова. Если убрать из уравнения базу, ответ ускорится в разы. Spring-кэш делает это декларативно.
@Configuration
@EnableCaching
public class CacheConfig { }
@Service
public class CatalogService {
private final CatalogRepository catalog;
@Cacheable(value = "catalog", key = "#categoryId")
public List<Item> itemsByCategory(long categoryId) {
return catalog.findByCategory(categoryId);
}
@CacheEvict(value = "catalog", allEntries = true)
public void refreshCatalog() { }
@CachePut(value = "stats", key = "#item.id")
public Item update(Item item) { ... }
}| Аннотация | Поведение |
|---|---|
| @Cacheable | берёт из кэша или вычисляет и кладёт |
| @CacheEvict | очищает кэш по значению |
| @CachePut | всегда выполняет метод и обновляет кэш |
Реализация настраивается отдельно: от in-memory до Redis через RedisCacheManager — см. курс Redis.
18. Микросервисы и Spring Cloud
Большой модуль делят на маленькие сервисы: каждый владеет своими данными, работает независимо и общается по HTTP или очередям. Spring Cloud добавляет к базовому Spring «клей» для распределённых систем.
// ключевые инструменты Spring Cloud
Spring Cloud Config — централизованная конфигурация
Spring Cloud Gateway — единая точка входа и маршрутизация
Eureka / Consul — реестр сервисов (discovery)
Sleuth — трассировка между сервисами
Resilience4j — защита от каскадных паденийДекомпозиция окупается, когда границы бизнес-модулей понятны. Для двух сервисов вместо одного монолита лучше потерпеть: разумный порог начала дробления заметно выше.
@CircuitBreaker(name = "payments", fallbackMethod = "fallbackPay")
public Payment pay(Order order) {
return paymentsClient.process(order);
}19. Типизированная конфигурация: @ConfigurationProperties
Разбросанные @Value-инъекции превращают конфигурацию в «поле чудес»: где какой ключ — неизвестно. Решение — привязка группы свойств к типизированному бину.
@ConfigurationProperties("app.mail")
public class MailProperties {
private String host;
private int port;
private Credentials credentials = new Credentials();
public static class Credentials {
private String user;
private String password;
// геттеры/сеттеры обязательны
}
// геттеры/сеттеры
}app.mail.host= smtp.example.com
app.mail.port= 587
app.mail.credentials.user= bot
app.mail.credentials.password= секрет- включается одной аннотацией
@EnableConfigurationPropertiesили@ConfigurationPropertiesScan; - валидация — аннотации jakarta:
@NotBlank,@Min; - вместо строковых ключей получаете объект с типами и автодополнением в IDE.
Environment.getProperty() по строкам — такие места нельзя ни переименовать, ни провалидировать.20. Интернационализация: MessageSource
Многозначные приложения строятся на ресурсах сообщений. Spring решает задачу двумя инструментами: messages.properties (файлы локалей) и MessageSource (доступ из кода).
# messages.properties
greeting=Привет, {0}!
# messages_en.properties
greeting=Hello, {0}!@Autowired MessageSource messages;
String text = messages.getMessage("greeting",
new Object[]{name}, LocaleContextHolder.getLocale());- имя файла:
messages_ru.properties,messages_en.properties— базовый без суффикса; - локаль определяется из
Accept-Language— при первом же поданном запросе; - аргументы подставляются через
{0},{1}; - в исключениях
@ControllerAdviceпереводит коды ошибок в текст для пользователя.
| Локаль | Файл |
|---|---|
| русский | messages_ru.properties |
| английский | messages_en.properties |
| базовая | messages.properties |
21. Реактивный стек: WebFlux
Spring MVC блокирует поток на каждый запрос: пока выполняется БД или внешний вызов, поток ждёт. При множестве параллельных запросов приходит проблема: потоков больше, чем ядер. Реактивный стек решает это иначе.
- WebFlux на Netty: события обрабатываются без выделения потока на каждый запрос;
- комбинаторы — слияние нескольких вызовов в один ответ, как в REST-фабриках;
- блокирующие библиотеки (JDBC, старые клиенты REST) в реактивном стаке вредны.
@RestController
public class ProfileController {
@GetMapping("/profile")
public Mono<Profile> profile(@AuthenticationPrincipal User u) {
return Mono.zip(
userService.findById(u.getId()),
orderService.sumByUser(u.getId()))
.map(t -> new Profile(t.getT1(), t.getT2()));
}
}| Параметр | MVC | WebFlux |
|---|---|---|
| модель | поток на запрос | событийная |
| когда выигрывает | обычные приложения | много IO-операций |
| проще | да | кривая обучения |
22. Паттерны и антипаттерны Spring-кода
Классические ошибки в крупных Spring-проектах повторяются из года в год. Знание их оберегает от переписывания.
| Антипаттерн | Проблема | Решение |
|---|---|---|
| God Bean | сервис на 3000 строк | декомпозиция на доменные сервисы |
| Field Injection | нет тестируемости, цикл зависимостей | конструктор + final |
| Unit-тесты на контекст | медленные тесты | тесты на мини-контексте или моках |
| Транзакция в контроллере | долгие блокировки | граница на service-слое |
- инжект по конструктору обязателен в актуальных версиях (field — исчезает);
- учитывайте
@Transactional(propagation=REQUIRES_NEW)и его пределы; - внутренние вызовы
@Transactional«не работают» из-за прокси — выносите в отдельные бины.
// через себя @Transactional не сработает (прокси-вызов)
class Payment { @Transactional public void pay() {...}
void process() { this.pay(); /* ✗ */ } }Что дальше
Вы изучили основы Spring Framework: IoC-контейнер, внедрение зависимостей, AOP, веб-модуль и безопасность. Следующий шаг — изучение Spring Boot, который значительно упрощает настройку и запуск Spring-приложений.
Переходите к курсу Spring Boot для детального знакомства с автоматической конфигурацией, REST API, тестированием и деплоем.