Spring Framework — полный курс

Spring Framework — самый популярный фреймворк для разработки на Java. Этот курс посвящён ядру Spring: IoC-контейнеру, внедрению зависимостей, AOP, веб-модулю, работе с БД и безопасности.

Уровень: средний 18 разделов ⏱ 4–5 часов

1. Что такое Spring

Spring Framework — это инфраструктурный фреймворк, созданный Родом Джонсоном в 2003 году для упрощения разработки Java-приложений. Сегодня Spring является стандартом де-факто в enterprise-разработке.

Основные модули Spring Framework:

Spring Framework и Spring Boot — это разные проекты. Boot автоматизирует настройку Spring, но понимание самого Framework необходимо для эффективной работы с Boot.

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. Чтобы зарегистрировать класс как бин, используются аннотации компонентного сканирования:

@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;
    }
}
С Spring 4.3, если у класса только один конструктор, аннотацию @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 позволяет вынести кросс-курентную логику (логирование, безопасность, транзакции) в отдельные модули — аспекты. Вместо того чтобы добавлять логирование в каждый метод сервиса, вы описываете его один раз.

Ключевые термины:

@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);
    }
}
Транзакции в Spring работают через динамические прокси. Это означает, что вызов метода @Transactional изнутри того же класса не пройдёт через прокси, и транзакция не откроется.

9. Spring MVC

Веб-модуль Spring построен на фронт-контроллере DispatcherServlet. Вот как обрабатывается HTTP-запрос:

  1. Запрос приходит в DispatcherServlet.
  2. Он делегирует обработку нужному @Controller через HandlerMapping.
  3. Контроллер обрабатывает запрос и возвращает имя представления или объект ResponseEntity.
  4. ViewResolver преобразует имя в объект View.
  5. 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);
    }
}
Для REST API используйте @RestController вместо @Controller — это эквивалентно добавлению @ResponseBody ко всем методам класса.

Отличие от Spring Boot

В «чистом» Spring MVC необходимо вручную настраивать DispatcherServlet через web.xml или программно. Spring Boot автоматически настраивает встроенный сервер Tomcat, DispatcherServlet, ViewResolver и множество других компонентов.

10. Spring Security (обзор)

Spring Security — это мощный фреймворк для обеспечения безопасности приложений. Архитектура основана на цепочке фильтров (Security Filter Chain):

@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();
    }
}
Детальное изучение Spring Security — отдельная тема. Здесь приведён базовый обзор концепций: фильтры, цепочка обработки запросов и управление доступом через конфигурацию.

11. Spring Boot упрощает всё

Spring Boot автоматизирует большинство рутинных операций при настройке Spring-приложений:

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}

Одна аннотация @SpringBootApplication заменяет @Configuration, @EnableAutoConfiguration и @ComponentScan.

Если вы только начинаете путь в Spring-разработке, изучение самого Framework даст вам фундамент, но для реальных проектов рекомендуется переходить на Spring Boot.

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 Cloud Config или хранилища (Vault): профиль задаёт источник, а не сами пароли.
Не кладите пароли и ключи в application.properties. Даже в приватном репозитории они «протекают»: подключайте их из переменных окружения или secret-хранилищ.

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подмена бина моком
Быстрые тесты — срезы (@DataJpaTest, @WebMvcTest), медленные «полные» тесты — для сценариев, где важен весь контекст. Так вы держите меньше 1 секунды на модульный тест.

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
) {}
Логируйте ошибки в одном месте (advice) и возвращайте понятный текст без стектрейса: это и безопасность, и удобство фронтенда.

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 через @Queryselect u from User u
пагинацияPageable и Page<User>
сортировкаSort.by("name")
Не вытаскивайте сущности в контроллер: для ответов клиенту используйте DTO. Иначе одна лишняя связь вызовет внезапный lazy-loading и утечку структуры БД.

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);
}
Полный разбор распределённых систем — в курсе Микросервисы, деплой контейнерами — в Docker и Kubernetes.
Начинайте с монолита с чистыми границами модулей: вынести сервис с готовыми границами — часы работы, обратная операция — недели.

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= секрет
Даже не обращайтесь к Environment.getProperty() по строкам — такие места нельзя ни переименовать, ни провалидировать.
Секреты в property-файле — риск утечки в git. Используйте переменные окружения и внешние секрет-менеджеры, а пароли по умолчанию никогда не коммитьте (см. курс Безопасность).

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
базоваяmessages.properties
Не храните текст в логике: правила «одна строка = один ключ» позволяют менять формулировки без релиза, а переводчикам — работать в одном файле.

21. Реактивный стек: WebFlux

Spring MVC блокирует поток на каждый запрос: пока выполняется БД или внешний вызов, поток ждёт. При множестве параллельных запросов приходит проблема: потоков больше, чем ядер. Реактивный стек решает это иначе.

@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()));
    }
}
ПараметрMVCWebFlux
модельпоток на запроссобытийная
когда выигрываетобычные приложениямного IO-операций
прощедакривая обучения
Реактивный стек не делает код быстрее «сам по себе». Запускайте его ради экономии потоков при IO-загрузке, а не как модную витрину — транзакции, ThreadLocal и блокирующие БД усложнят всё.

22. Паттерны и антипаттерны Spring-кода

Классические ошибки в крупных Spring-проектах повторяются из года в год. Знание их оберегает от переписывания.

АнтипаттернПроблемаРешение
God Beanсервис на 3000 строкдекомпозиция на доменные сервисы
Field Injectionнет тестируемости, цикл зависимостейконструктор + final
Unit-тесты на контекстмедленные тестытесты на мини-контексте или моках
Транзакция в контроллередолгие блокировкиграница на service-слое
// через себя @Transactional не сработает (прокси-вызов)
class Payment { @Transactional public void pay() {...}
               void process() { this.pay(); /* ✗ */ } }
Критерий качества: сервис читается как описание бизнес-операции, а не как смесь запросов и маппинга. Тогда ревью проходит без правок по принципу «зачем так долго».

Что дальше

Вы изучили основы Spring Framework: IoC-контейнер, внедрение зависимостей, AOP, веб-модуль и безопасность. Следующий шаг — изучение Spring Boot, который значительно упрощает настройку и запуск Spring-приложений.

Переходите к курсу Spring Boot для детального знакомства с автоматической конфигурацией, REST API, тестированием и деплоем.

← Вернуться к курсам