Аутентификация — Java: Корпоративные приложения на Spring Boot
Аутентификация — это проверка подлинности. Например, программа может проверить, действительно ли пользователь является тем, за кого себя выдает. Мы все участвуем в этом процессе, когда заполняем формы логинов и паролей. Spring Boot отвечает за реализацию этого процесса со стороны бэкенда.
В отличие от всех остальных эндпоинтов API, аутентификация в Spring Boot работает не сама по себе. Чтобы работать с ней, нам придется использовать достаточно навороченный пакет Spring Security. Этот пакет предоставляет множество готовых компонентов, но требует тонкой настройки. Это большая и сложная тема, по которой пишутся целые книги. Глубокое изучение Spring Security — это слишком сложно, и в этом курсе мы не будем погружаться в эту тему. Поэтому большую часть кода в этом уроке мы рассмотрим без подробного объяснения.
Все события во время аутентификации можно разделить на два уровня:
- Пользователь и его функциональность. Сюда входит все от регистрации до проверки доступов. Чтобы работать с этим уровнем, нужно взять представленный в Spring Boot набор интерфейсов и реализовать его — тогда часть нужной функциональности начнет работать автоматически
- Конкретный способ аутентификации. В зависимости от способа аутентификации мы можем использовать разные механизмы и сторонние пакеты — например, OAuth или JWT-токены
В этом уроке мы последовательно пройдем по всем частям системы и настроим их. Для аутентификации мы будем использовать JWT-токены. Во время логина система будет формировать JWT-токен, который вернется клиенту. Клиент будет пользоваться этим токеном для последующих запросов, иначе ему будет отказано в доступе.
Установка зависимостей
Помимо Spring Security, нам понадобятся пакет для тестирования и пакет oauth2-resource-server, который выполняет большую часть логики по проверки доступа внутри себя:
implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") testImplementation("org.springframework.security:spring-security-test")
Настройка процесса аутентификации
Для аутентификации пользователя понадобится два поля:
- email — это самый частый логин, кроме него иногда используют номер телефона или никнейм
- passwordDigest — это специальный хэш, связанный с паролем. Мы храним именно хэш, а не сам пароль, потому что с точки зрения безопасности, пароли хранить в базе данных нельзя. Чтобы не запутаться, мы назвали это поле passwordDigest, а не password
Внутри себя Spring Security работает с интерфейсом UserDetails , в который входят методы для работы с никнеймом, паролем и выдачей доступов. Сейчас мы реализуем только аутентификацию, поэтому нас интересует только часть методов. Остальные методы мы тоже реализуем, но после базовой функциональности:
package io.hexlet.spring.model; import java.util.ArrayList; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import lombok.Getter; import lombok.Setter; @Entity @Getter @Setter @Table(name = "users") public class User implements UserDetails, BaseEntity // Остальные поля @Column(unique = true) @Email private String email; @NotBlank private String passwordDigest; @Override public String getPassword() return passwordDigest; > @Override public String getUsername() return email; > @Override public boolean isEnabled() return true; > @Override public Collection extends GrantedAuthority> getAuthorities() return new ArrayListGrantedAuthority>(); > @Override public boolean isAccountNonExpired() return true; > @Override public boolean isAccountNonLocked() return true; > @Override public boolean isCredentialsNonExpired() return true; > >
Обычно Spring Security работает с username , но под ним может скрываться что-то другое. Например, в нашем случае геттер возвращает email . Кроме того, вместо пароля мы получаем passwordDigest . Здесь все тоже корректно, потому что сравнение будет происходить с хэшем, а не с введенным пользователем паролем.
Далее мы создадим сервис, реализующий интерфейс UserDetailsManager . Через него Spring Boot будет выполнять CRUD-операции над пользователем. Для аутентификации реализуем метод loadUserByUsername() :
// src/main/java/io/hexlet/spring/service/CustomUserDetailsService.java package io.hexlet.spring.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.stereotype.Service; import io.hexlet.spring.model.User; import io.hexlet.spring.repository.UserRepository; @Service public class CustomUserDetailsService implements UserDetailsManager @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException // Нужно добавить в репозиторий findByEmail var user = userRepository.findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("User not found")); return user; > @Override public void createUser(UserDetails userData) // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'createUser'"); > @Override public void updateUser(UserDetails user) // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'updateUser'"); > @Override public void deleteUser(String username) // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'deleteUser'"); > @Override public void changePassword(String oldPassword, String newPassword) // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'changePassword'"); > @Override public boolean userExists(String username) // TODO Auto-generated method stub throw new UnsupportedOperationException("Unimplemented method 'userExists'"); > >
Дальше нам понадобится механизм хеширования пароля. Чтобы работать с ним, создадим бин:
// src/main/java/io/hexlet/spring/config/EncodersConfig.java package io.hexlet.spring.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration public class EncodersConfig @Bean public PasswordEncoder passwordEncoder() return new BCryptPasswordEncoder(); > >
Теперь собираем все вместе. Указываем, что Spring Security должен использовать наши компоненты:
package io.hexlet.spring.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.password.PasswordEncoder; import io.hexlet.spring.service.CustomUserDetailsService; @Configuration @EnableWebSecurity public class SecurityConfig @Autowired private PasswordEncoder passwordEncoder; @Autowired private CustomUserDetailsService userService; @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception return http.getSharedObject(AuthenticationManagerBuilder.class) .build(); > @Bean public AuthenticationProvider daoAuthProvider(AuthenticationManagerBuilder auth) var provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userService); provider.setPasswordEncoder(passwordEncoder); return provider; > >
Мы сделали почти всю подготовительную работу. Осталось создать утилиту для генерации JWT-токена, и мы будем готовы реализовать аутентификацию.
Генерация состоит из двух вещей — подготовки токена и его шифрования. Рассмотрим пример класса, который делает эти операции:
// src/main/java/io/hexlet/spring/util/JWTUtils.java package io.hexlet.spring.util; import java.time.Instant; import java.time.temporal.ChronoUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.stereotype.Component; @Component public class JWTUtils @Autowired private JwtEncoder encoder; public String generateToken(String username) Instant now = Instant.now(); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .issuedAt(now) .expiresAt(now.plus(1, ChronoUnit.HOURS)) .subject(username) .build(); return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); > >
В коде выше используется JwtEncoder . Он добавляется в класс EncodersConfig , в который мы уже добавили PasswordEncoder :
package io.hexlet.spring.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import io.hexlet.spring.component.RsaKeyProperties; @Configuration public class EncodersConfig @Autowired // Создается ниже private RsaKeyProperties rsaKeys; @Bean public PasswordEncoder passwordEncoder() return new BCryptPasswordEncoder(); > @Bean JwtEncoder jwtEncoder() JWK jwk = new RSAKey.Builder(rsaKeys.getPublicKey()).privateKey(rsaKeys.getPrivateKey()).build(); JWKSourceSecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwks); > @Bean JwtDecoder jwtDecoder() return NimbusJwtDecoder.withPublicKey(rsaKeys.getPublicKey()).build(); > >
Для работы JwtEncoder нужны RSA-ключи — их можно сгенерировать, выполнив в терминале команды:
-out private.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 openssl rsa -in private.pem -pubout -out public.pem
Затем мы должны зайти в application.yml и указать путь к ключам. Например, вот так:
# src/main/resources/certs/ rsa: private-key: classpath:certs/private.pem public-key: classpath:certs/public.pem
Дальше мы создаем компонент, который прочитает эти ключи и сделает их доступными в коде:
package io.hexlet.spring.component; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import lombok.Getter; import lombok.Setter; @Component @ConfigurationProperties(prefix = "rsa") @Setter @Getter public class RsaKeyProperties private RSAPublicKey publicKey; private RSAPrivateKey privateKey; >
Не забудьте, что в реальных проектах хранить приватный ключ в репозитории нельзя. Доступ к приватному ключу должен быть ограничен.
В нашем случае аутентификация — это обычный POST-запрос, в котором передаются электронная почта и пароль. Для унификации с предыдущими настройками, мы будем использовать имя username вместо почты email . В итоге у нас получится такой DTO:
package io.hexlet.spring.dto; import lombok.Getter; import lombok.Setter; @Setter @Getter public class AuthRequest private String username; private String password; >
Наконец, перейдем к контроллеру:
package io.hexlet.spring.controller.api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.hexlet.spring.dto.AuthRequest; import io.hexlet.spring.util.JWTUtils; @RestController @RequestMapping("/api") public class AuthenticationController @Autowired private JWTUtils jwtUtils; @Autowired private AuthenticationManager authenticationManager; @PostMapping("/login") public String create(@RequestBody AuthRequest authRequest) var authentication = new UsernamePasswordAuthenticationToken( authRequest.getUsername(), authRequest.getPassword()); authenticationManager.authenticate(authentication); var token = jwtUtils.generateToken(authRequest.getUsername()); return token; > >
Все проделанные шаги свелись к строчке authenticationManager.authenticate(authentication) . В итоге внутри кода выполняется поиск пользователя в базе данных, хэширование пароля и сравнение. Если аутентификация прошла, выполнение продолжается дальше, если нет — код выбрасывает исключение.
После аутентификации программа генерирует токен и возвращает его клиенту. С этого момента клиент сам следит за отправкой токена в последующих запросах к API. При этом мы должны убрать API из публичного доступа и включить проверку токена.
Проверка наличия аутентификации
Проверка аутентификации настраивается в конфигурации, помеченной аннотацией @EnableWebSecurity . Выше мы уже создали такой класс, добавив туда конфигурацию провайдера и менеджера аутентификации. Теперь добавим туда фильтр, который применяется ко всем входящим запросам:
package io.hexlet.spring.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import io.hexlet.spring.service.CustomUserDetailsService; @Configuration @EnableWebSecurity public class SecurityConfig @Autowired private final JwtDecoder jwtDecoder; @Autowired private final PasswordEncoder passwordEncoder; @Autowired private CustomUserDetailsService userService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception // По умолчанию все запрещено return http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth // Разрешаем доступ только к /api/login, чтобы аутентифицироваться и получить токен .requestMatchers("/api/login").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .oauth2ResourceServer((rs) -> rs.jwt((jwt) -> jwt.decoder(jwtDecoder))) .httpBasic(Customizer.withDefaults()) .build(); > @Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception return http.getSharedObject(AuthenticationManagerBuilder.class) .build(); > @Bean public AuthenticationProvider daoAuthProvider(AuthenticationManagerBuilder auth) var provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userService); provider.setPasswordEncoder(passwordEncoder); return provider; > >
Извлечение текущего пользователя
Кроме проверки доступа, в коде может понадобиться пользователь, выполняющий запрос. Например, такое бывает нужно, когда создается какая-то сущность, у которой есть автор. В таком случае автор почти всегда тот, кто выполняет запрос. Spring Security предоставляет возможность извлечь его из контекста:
package io.hexlet.spring.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import io.hexlet.spring.model.User; import io.hexlet.spring.repository.UserRepository; @Component public class UserUtils @Autowired private UserRepository userRepository; public User getCurrentUser() var authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) return null; > var email = authentication.getName(); return userRepository.findByEmail(email).get(); > >
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Использование фреймворков семейства Spring Projects для разработки веб-приложений на платформе Java Текст научной статьи по специальности «Компьютерные и информационные науки»
веб-приложение / платформа Java / аннотации Java / фреймворк / Spring Projects / Spring Frame¬work / архитектура MVC / web application / Java platform / Java annotation / framework / Spring Projects / Spring Framework / MVC architecture.
Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Сакович В. В., Кожомбердиева Г. И., Бураков Д. П.
Обсуждаются особенности разработки веб-приложений на платформе Java с использованием фреймворков семейства Spring Projects . Рассматриваются основные концепции Spring Framework : IoC-контейнер, Spring Scopes, Spring MVC и Spring AOP. Освещаются возможности, предоставляемые Spring Boot: запуск приложения, профили, конфигурационные файлы. Кроме того, уделяется внимание организации взаимодействия с базами данных с помощью Spring Data, а также использованию средств авторизации, предоставляемых Spring Security. В качестве демонстрационного примера веб-приложения , разработанного с использованием Spring, представлено клиент-серверное приложение для записи студентов на консультации и управления консультациями.
i Надоели баннеры? Вы всегда можете отключить рекламу.
Похожие темы научных работ по компьютерным и информационным наукам , автор научной работы — Сакович В. В., Кожомбердиева Г. И., Бураков Д. П.
СОВРЕМЕННЫЕ ФРЕЙМВОРКИ ДЛЯ РАЗРАБОТКИ WEB-ПРИЛОЖЕНИЙ
Разработка веб — приложения с использованием Spring Framework и jQuery
РЕАЛИЗАЦИЯ ВЕБ СЕРВИСА С ПРИМЕНЕНИЕМ ПАРАДИГМЫ РЕАКТИВНОГО ПРОГРАММИРОВАНИЯ
СРАВНИТЕЛЬНЫЙ АНАЛИЗ ТЕХНОЛОГИЙ ДЛЯ РАЗРАБОТКИ СЕРВЕРНОЙ ЧАСТИ СИСТЕМЫ УПРАВЛЕНИЯ ПРОДАЖАМИ
SPRING FRAMEWORK В ВЫСОКОНАГРУЖЕННЫХ ПРИЛОЖЕНИЯХ
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.
Using Spring Projects Family Frameworks for Developing Web Applications on Java Platform
The article discusses the features of developing web applications on the Java platform using frameworks from the Spring Projects family. The basic concepts of the Spring Framework are considered: IoC container, Spring Scopes, Spring MVC, and Spring AOP. The possibilities provided by the Spring Boot are covered: application launch, profiles, and configuration files. In addition, attention is paid to the organization of interaction with databases using the Spring Data as well as using of authorization tools provided by the Spring Security. As a demo of a web application developed using the Spring features, the article provides a client server application for signing up students for a consultation and consultation management.
Текст научной работы на тему «Использование фреймворков семейства Spring Projects для разработки веб-приложений на платформе Java»
Использование фреймворков семейства Spring Projects для разработки веб-приложений
на платформе Java
В. В. Сакович, к.т.н. Г. И. Кожомбердиева Петербургский государственный университет путей сообщения Императора Александра I
Санкт-Петербург, Россия lampirg 1@gmail. com, kgi-liizht@yandex. ru
Аннотация. Обсуждаются особенности разработки веб-приложений на платформе Java с использованием фреймворков семейства Spring Projects. Рассматриваются основные концепции Spring Framework: IoC-контейнер, Spring Scopes, Spring MVC и Spring AOP. Освещаются возможности, предоставляемые Spring Boot: запуск приложения, профили, конфигурационные файлы. Кроме того, уделяется внимание организации взаимодействия с базами данных с помощью Spring Data, а также использованию средств авторизации, предоставляемых Spring Security. В качестве демонстрационного примера веб-приложения, разработанного с использованием Spring, представлено клиент-серверное приложение для записи студентов на консультации и управления консультациями.
Ключевые слова: веб-приложение, платформа Java, аннотации Java, фреймворк, Spring Projects, Spring Framework, архитектура MVC.
Широкое распространение веб-приложения получили в начале 2000-х годов в условиях бурного развития сети Интернет и гипертекстовой системы WWW [1]. Вследствие этого в области программной инженерии было выделено даже отдельное направление — веб-программирование, то есть разработка использующих веб-технологии информационных систем, представляющих собой полноценные веб-приложения, или частей этих систем, использующих вебстраницы для представления пользовательского интерфейса.
В настоящей статье рассматриваются особенности использования семейства фреймворков Spring Projects при разработке веб-приложений на платформе Java.
В качестве примера представлено демонстрационное приложение, функциональность которого ориентирована на решение практически значимой в вузовской среде задачи записи студентов на консультации и управления консультациями, а реализация — на показательное применение возможностей и преимуществ семейства фреймворков Spring Projects.
Статья содержит результаты выпускной квалификационной бакалаврской работы В. В. Саковича. Описание фреймворков семейства Spring Projects сопровождается отсылками к конкретным примерам их использования при разработке демонстрационного приложения. Приложение разработано в интегрированной среде разработки IntelliJ IDEA (с использованием в качестве дополнительных инструментов веб-приложения Spring Initializr и средства для
к.т.н. Д. П. Бураков независимый исследователь Санкт-Петербург, Россия burakovdmitry8@gmail.com
сборки проекта Maven) и может рассматриваться как действующий прототип реальной системы.
Веб-приложения и средства их разработки По своей структуре веб-приложения представляют собой приложения, построенные в соответствии с архитектурой «клиент-сервер». Структура типичного приложения представлена на рисунке 1 [2].
Рис. 1. Структура веб-приложения
Отличительной чертой веб-приложений является то, что клиентская часть выполняется в стандартном веб-браузере, причем клиентская и серверная части могут работать на разных операционных системах, то есть веб-приложения являются межплатформенными службами.
Клиентская часть веб-приложения реализует пользовательский интерфейс, формирует запросы к серверной части приложения и обрабатывает ответы от нее. Серверная часть получает запрос от клиента, выполняет вычисления, после этого формирует веб-страницу и отправляет ее клиенту по сети с использованием протокола HTTP. При этом серверная часть веб-приложения может выступать в качестве клиента других служб, например сервера базы данных или другого веб-приложения, расположенного на другом сервере.
Поскольку веб-приложения состоят из серверной и клиентской части, в веб-программировании выделяется программирование клиентской части, выполняемой непосредственно в браузере, и программирование серверной части, работающей на стороне веб-сервера. Для реализации как клиентской, так и серверной части могут быть использованы различные технологии по усмотрению разработчика или организации, внедряющей веб-приложение. Так, для реализации клиентской части, помимо языка разметки
HTML, активно используются таблицы стилей CSS и скриптовый язык JavaScript. На серверной стороне веб-приложений в основном применяются средства динамической генерации веб-страниц, например широко известный скриптовый язык PHP. Однако для разработки корпоративных веб-приложений, отличающихся, как правило, сложной структурой и разнообразием сервисов, предоставляемых конечным пользователям, применяются решения, основанные на использовании таких универсальных языков программирования, как Java.
Будучи высокоуровневым кроссплатформенным языком, язык Java используется во многих областях разработки программного обеспечения. При этом в стандартной редакции платформы Java (Java SE) не предусмотрены средства для удобной реализации серверных приложений. Для решения этой проблемы разработчиками Java была создана редакция платформы для разработки корпоративных систем Java EE (Java Platform, Enterprise Edition), включившая в себя такие технологии разработки серверной части веб-приложений, как JSP (Java Sever Pages) и сервлеты. Однако, ввиду того, что Java EE (с 2018 года — Jakarta EE) развивалась довольно медленно, а ее функциональность была относительно низкоуровневой, существовал запрос на альтернативные средства разработки серверных приложений, которые, использовав созданные в Java EE спецификации, позволяли бы создавать негромоздкие, легко масштабируемые, высокоуровневые веб-приложения.
Такой альтернативой стало семейство фреймворков Spring, существенно упростившее создание приложений и вследствие этого завоевавшее популярность среди разработчиков [3]. Данное семейство фреймворков является одним из самых востребованных средств для разработки веб-приложений [4], которое используется в таких компаниях, как Amazon, Google, Microsoft, Netflix и многих других [5].
Использование фреймворков как программных платформ, определяющих структуру разрабатываемой прикладной программной системы, облегчает разработку и объединение различных компонентов большого программного проекта. Как правило, облегчение разработки сложных систем достигается за счет использования каркасного подхода, при котором любая конфигурация программы строится на основе постоянной части, то есть каркаса, не зависящего от конфигурации и несущего в себе «гнезда», к которым подключаются сменные модули или точки расширения каркаса, определяющие специфическую функциональность разрабатываемой системы.
Семейство фреймворков Spring Projects
Изначально под термином Spring подразумевался только Spring Framework — фреймворк с открытым исходным кодом для Java-платформы, появившийся в середине 2000-х годов как альтернатива технологии Enterprise JavaBeans, которая поддерживает разработку серверных компонентов с бизнес-логикой и является частью Java EE. Однако по мере развития этого фреймворка на его основе сформировались новые фреймворки, которые образовали расширенное семейство Spring Projects [6]. Некоторые фреймворки данного семейства представлены на рисунке 2 [7].
Отметим фреймворки семейства, представляющие наибольший интерес в рамках настоящей статьи:
Рис. 2. Семейство фреймворков Spring Projects
• Spring Framework — основной фреймворк, использующийся во всех Spring-приложениях, предназначенный для управления ходом функционирования программы;
• Spring Boot — фреймворк, позволяющий быстро создавать и настраивать полноценные и готовые к запуску приложения, основанные на Spring;
• Spring Data — фреймворк для работы с базами данных, работающий с JDBC, JPA, а также NoSQL СУБД (например, MongoDB);
• Spring Security — фреймворк, предоставляющий средства для авторизации и аутентификации, а также базовую защиту от злоумышленных операций.
Для быстрой генерации каркасного проекта приложения, использующего фреймворки семейства Spring Projects, предназначен специальный инструмент — веб-приложение Spring Initializr [8]. Оно позволяет пользователю выбрать инструментальное средство для сборки проекта (например, Maven), версию Spring Boot, версию платформы Java, а также используемые средства семейства Spring Projects (и некоторых других инструментов). На основе выбранных пунктов меню и введенной информации Spring Initializr генерирует готовый zip-архив каркасного проекта приложения.
Возможности фреймворка Spring Framework
Рассмотрим полезные возможности Spring Framework, поддерживаемые ядром и внутренними пакетами этого основного фреймворка семейства. Описание средств, предоставляемых Spring Framework, сопровождается отсылками к примерам их использования В. В. Саковичем при разработке демонстрационного приложения.
Внедрение зависимостей IoC-контейнером
В решениях семейства Spring Projects активно применяется так называемая инверсия управления (Inversion of Control, IoC), которая также иногда называется внедрением зависимостей (Dependency Injection, DI). Это шаблон проектирования, при использовании которого зависимости экземпляров класса (то есть объекты, с которыми они взаимодействуют) определяются на основе аргументов конструктора (или метода, создающего экземпляр класса), а также полей объекта, значение которых определяется уже после создания экземпляра класса.
Внедрением зависимостей после создания экземпляра в Spring Framework занимается так называемый IoC-контейнер. IoC-контейнер представляет собой объект типа интерфейса BeanFactory. Как правило, при этом данный
объект создается на основе класса, реализующего производный интерфейс ApplicationContext. Например, в веб-приложениях используется автоматически создаваемый фреймворком Spring Boot экземпляр класса AnnotationConfigServletWebServerApplicationContext. При внедрении зависимостей Spring использует имеющийся в Java механизм рефлексии, в частности, активно применяются аннотации, опрашиваемые при выполнении приложения с помощью рефлексии [9].
Объекты, которые настраивает и внедряет IoC-контейнер, называются Spring bean-компонентами. Объявить Spring bean можно либо пометив его класс аннотацией @Component, либо использовав метод, помеченный аннотацией @Bean в классе, помеченным аннотацией @Configuration, либо используя xml-файлы [10]. Например, экземпляру класса NotificationAspect (который в демонстрационном приложении используется при формировании уведомления для отправки по электронной почте) для выполнения функций требуется объект типа интерфейса EmailService. Для этого классы, реализующие данный интерфейс, помечены аннотацией @Component. Во время выполнения программы IoC-контейнер создаст экземпляр класса, реализующего интерфейс EmailService и передаст его в качестве аргумента конструктору при создании экземпляра класса NotificationAspect.
Области применения Spring Scopes
Каждый объект Spring bean имеет свою область применения (scope), причем Spring Framework поддерживает шесть основных областей применения:
• singleton — на протяжении всей работы приложения существует только один Spring bean данного класса (выбирается по умолчанию);
• prototype — для каждого использующего его объекта внедряется свой Spring bean;
• request — для каждого HTTP-запроса создается свой Spring bean;
• session — для каждой сессии создается свой Spring bean;
• application — для каждого объекта типа интерфейса ServletContext создается свой Spring bean;
• websocket — для каждого объекта типа интерфейса WebSocket создается свой Spring bean.
Для приписывания области применения объекту Spring bean, необходимо применить аннотацию @Scope к классу, помеченному аннотацией @Component или методу, помеченному аннотацией @Bean. В качестве значения члена аннотации передается название области применения.
Для применения большинства областей scope фреймворку Spring необходимо использование прокси (объекта-посредника). Тип используемого прокси (на основе интерфейсов или на основе классов) также указывается в значении члена аннотации [11].
В качестве примера использования настройки области применения в демонстрационном приложении можно привести класс TeacherController, который содержит поле типа ConsultationPatternListWrapper, предназначенного для хранения данных об одном пользователе-преподавателе на протяжении нескольких запросов. Для этого используется область применения session и прокси на основе классов.
Архитектура Spring MVC
Spring Framework содержит два внутренних фреймворка для создания веб-приложений: Spring MVC и Spring WebFlux (предназначенный для реактивного веб-программирования).
Spring MVC основан на сервлете DispatcherServlet, который обрабатывает запросы, делегируя функции соответствующим компонентам Spring bean.
Архитектура MVC (Model-View-Controller), применяемая при проектировании программного обеспечения, представлена на рисунке 3 [12]. Она основана на взаимодействии трех типов компонентов [13]:
• Model (модель) содержит изменяемые данные, которые обновляет контроллер и которые передаются представлению;
• View (представление, вид) отвечает за отображение данных модели пользователю;
• Controller (контроллер) управляет моделью и отображением на основе входных данных.
Defines data structure e.g. updates application to reflect added item
Updates / e.g. list item to show added item I / \ \ Manipulates
View Defines display (Ul) e.g. user clicks ‘add to cart’ Sends input from user Controller Contains control logic e.g. receives update from view then notifies model to ‘add item’
Sometimes updates directly
Рис. 3. Архитектура MVC
Экземпляр класса DispatcherServlet обрабатывает запрос в соответствии с этой архитектурой, как показано на рисунке 4 [14].
Рис. 4. Обработка НТТР-запроса экземпляром класса
Обработка запроса происходит следующим образом:
1. Экземпляр класса DispatcherServlet принимает НТТР-запрос.
2. На основе объекта типа интерфейса HandlerMapping определяется контроллер, которому будет передана обработка запроса.
3. Контроллер обрабатывает запрос.
4. В результате обработки запроса контроллером экземпляр DispatcherServlet получает модель и логическое имя представления.
5. Объект типа интерфейса ViewResolver выбирает представление по его имени.
6. Представление, данные которого основаны на модели, формирует HTTP-ответ.
7. Пользователю отправляется HTTP-ответ.
Представление может быть создано при помощи таких
средств, как генератор веб-страниц Thymeleaf или технология JSP (Java (или Jakarta) Server Pages) [14].
Контроллер представляет собой Java-класс, помеченный аннотацией @Controller. Он объявляет методы, к которым применяется аннотация @RequestMapping (или одна из аннотаций, которая помечена данной аннотацией, например, @GetMapping или @PostMapping). На основе значений членов данной аннотации объект типа HandlerMapping сможет сопоставить HTTP-запрос соответствующему методу контроллера [15].
Примером контроллера в демонстрационном приложении является класс TeacherController. Этот класс объявлен как Spring bean-компонент, что позволяет внедрить в объект этого класса экземпляры других классов для выполнения различных функций. При этом в классе объявлены такие методы, как getTeacherProfile, deleteConsultation и т. д., к которым применяются аннотации @GetMapping или @PostMapping. Данные методы обрабатывают запрос, используя объект типа интерфейса Model, если необходимо передать пользователю данные.
Концепция Spring AOP
Для того чтобы неявным образом вызвать функцию после, перед или вместо выполнения определенного метода, Spring Framework использует аспекты и средства Spring AOP (Aspect Oriented Programming).
Внутренний фреймворк Spring AOP основан на AspectJ и использует идею прокси (объекта-посредника). Прокси подменяет собой изначальный объект и при вызове метода сначала выполняет свою логику, а затем делегирует полномочия изначальному методу. Принцип его работы приведен на рисунке 5 [16].
fоо() on the proxy
then foo<> on the object
Рис. 5. Принцип работы прокси-объекта
Для того чтобы объявить класс в качестве аспекта, необходимо применить к нему аннотацию @Aspect. Для того чтобы метод аспекта был выполнен при вызове метода другого объекта, необходимо применить к нему одну из следующих аннотаций: @Before (перед), @AfterReturning (после при успешном завершении), @AfterThrowing (после при завершении в результате возникновения исключительной ситуации), @After (после) или @Around (перед и после). Метод или
методы, при вызове которых должны отработать аспекты, определяются на основе значения члена одной из перечисленных аннотаций.
Такие методы в качестве аргумента могут принимать объект типа интерфейса JoinPoint, который содержит различную информацию об аспекте и методе, к которому применяется метод аспекта (например, аргументы метода). Если к методу аспекта применяется аннотация @Around, то он должен принимать в качестве аргумента объект типа интерфейса ProceedingJoinPoint, который содержит метод proceed для вызова оригинального метода [17].
Примером использования аспекта в демонстрационном приложении является уже упоминавшийся класс Notifica-tionAspect, в котором объявлен метод noti-fyStudentsAboutDeletion. Метод помечен аннотацией @After, а в качестве значения члена аннотации устанавливается название метода удаления консультации. Таким образом, данный метод будет вызван после удаления консультации независимо от места вызова метода удаления.
Другие фреймворки семейства Spring Projects
Описание средств, которыми располагают фреймворки Spring Boot, Spring Data и Spring Security, сопровождается отсылками к примерам их использования В. В. Саковичем при разработке демонстрационного приложения.
Фреймворк Spring Boot предоставляет возможность запуска веб-приложения при помощи вызова метода run класса SpringApplication из метода main. Класс, содержащий метод main, должен быть помечен аннотацией @SpringBootApplicationю.
При вызове метода run Spring Boot автоматически настроит множество параметров, а для веб-приложения дополнительно автоматически создаст сервер, используя встроенный контейнер сервлетов (по умолчанию — Tomcat).
Другой важной возможностью, поддерживаемой Spring Boot, являются профили. При разработке приложения может потребоваться использование разных Spring bean для одной и той же задачи. Например, в демонстрационном приложении имеется имитационный сервис оповещения по электронной почте, который оставляет сообщения в системе, но не отправляет сами письма. Однако на этапе эксплуатации должен использоваться настоящий сервис оповещений.
Реализовать такую возможность позволяет аннотация @Profile, которая принимает в качестве значения члена аннотации профиль или логически объединенное (операторами И, ИЛИ, НЕ) множество профилей. Аннотация применяется при настройке Spring bean (либо к классу, помеченному аннотацией @Component, либо к методу, помеченному аннотацией @Bean) [3]. Таким образом, имитационный сервис включается только при активном профиле dev, а настоящий сервис — при активном профиле prod.
Spring Boot позволяет настраивать приложение посредством конфигурационных файлов (расширения properties илиyml). Отметим некоторые параметры, предоставляемые Spring Boot [18]:
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
• server.port — адрес порта, с которого сервер принимает запросы (по умолчанию — 8080);
• spring.proffles.active — активные профили приложения;
• spring.security.user.password — пароль пользователя, создаваемый по умолчанию;
• server.servlet.session.timeout — время действия сессии;
• spring.mail.host — хост SMTP-сервера.
Используя разделитель «—», можно настроить разные
значения конфигурационных параметров для разных профилей. Для этого необходимо добавить конфигурационный параметр spring.config.activate.on-profile [3].
Фреймворк Spring Data содержит средства для работы с базами данных. Фреймворк предоставляет приложению интерфейс Repository, который является интерфейсом-маркером, с помощью которого выявляются потомки этого интерфейса. Наследующие интерфейсу Repository интерфейсы CrudRepository и ListCrudRepository объявляют базовые методы по работе с базой данных (CRUD — Create, Read, Update, Delete).
Для практического применения средств для работы с базами данных при разработке приложения необходимо создать интерфейс, наследующий Repository. Объявить метод интерфейса-репозитория можно двумя способами: указанием имени метода в соответствии с соглашением об именах или при помощи аннотации @Query, указав в качестве значения члена аннотации запрос к базе данных.
При первом подходе Spring Data предлагает множество вариантов создания комплексного запроса. Среди них поиск по какому-либо полю, поиск с условием, логические объединения (И, ИЛИ), сортировка значений, разбиение запроса на страницы и т. д. При этом Spring Data позволяет такому методу возвращать как единственный объект типа, соответствующего хранимым в репозитории данным, так и коллекцию таких объектов.
При запуске приложения Spring Data проанализирует всех потомков интерфейса Repository и, если у какого-либо интерфейса не будет реализации (и он не помечен аннотацией @NoRepositoryBean), обеспечит реализацию интерфейса с помощью механизма рефлексии в соответствии с соглашением об именах методов или на основе значения члена аннотации @Query. Однако, при необходимости, разработчик может создать реализацию интерфейса самостоятельно (в виде компонента Spring bean) [19].
В качестве примера автоматической реализации интерфейса в демонстрационном приложении можно привести интерфейс TeacherRepository, для которого на этапе выполнения программы создается прокси-объект типа jdk.proxy4.$Proxy139.
Интерфейс TeacherRepository объявляет метод для поиска консультации, который помечен аннотацией @Query. На этапе выполнения прокси на основе значения члена аннотации @Query реализует метод, выполняющий поиск консультации в базе данных. Помимо этого, прокси реализует метод findByEmail суперинтерфейса PersonRepository на основе имени метода.
Средства обеспечения безопасности и авторизации пользователей предоставляются фреймворком Spring Security и основаны на фильтре сервлетов, который делегирует
полномочия по настройке безопасности набору фильтров SecurityFilterChain, как показано на рисунке 6 [20].
Набор фильтров безопасности может быть создан методом, помеченным аннотацией @Bean в классе, помеченным аннотацией @Configuration. Такой метод должен принимать экземпляр класса HttpSecurity в качестве аргумента [3].
Рис. 6. Принцип работы средств безопасности Spring Security
Отметим некоторые методы класса HttpSecurity [21]:
• securityMatcher — устанавливает типы URL-адресов, при которых будет применятся набор фильтров;
• authenticationProvider — устанавливает источник аутентификации;
• authorizeHttpRequests — настраивает правила доступа пользователей к веб-страницам;
• formLogin — настраивает страницу входа в аккаунт;
• logout — настраивает страницу выхода из аккаунта.
В качестве примера настройки авторизации в демонстрационном приложении, структура и функциональность которого представлены в следующем разделе статьи, можно рассматривать класс TeacherSecurityConfig, который содержит три метода, помеченных аннотацией @Bean:
• teacherDetailService возвращает объект типа интерфейса UserDetailsService, который находит объект типа интерфейса UserDetails, предоставляющий следующую информацию (для поиска используются средства для работы с базой данных) [21]: имя пользователя; пароль; права доступа; различная информация о состоянии аккаунта.
• teacherAuthenticationProvider возвращает объект типа интерфейса AuthenticationProvider, который аутенти-фицирует пользователя. Для поиска пользователя используется объект типа интерфейса UserDetailsService.
• teacherFilterChain настраивает фильтры безопасности, в качестве источника аутентификации используя объект, возвращаемый предыдущим методом. Метод возвращает объект типа интерфейса SecurityFilterChain.
Веб-приложение для записи студентов
на консультации и управления консультациями
Источником примеров использования средств фрейм-ворков семейства Spring Projects, описываемых в предыдущих разделах, является демонстрационное приложение, позволяющее преподавателю организовать запись студентов
на свои консультации через Интернет. Преподаватели могут вносить сведения о предстоящих консультациях (в том числе — запланировать на будущее повторяющиеся, например еженедельные, консультации) и отменять их, просматривать списки студентов, записавшихся на консультации, а студенты — просматривать расписание консультаций, записываться на них и отменять свою запись. В необходимых случаях приложение оповещает об изменениях как преподавателей, так и студентов по электронной почте.
Структура приложения соответствует общей структуре веб-приложений, представленной на рисунке 1.
Клиентская часть приложения представлена html-стра-ницами, доступными для просмотра в веб-браузере.
Серверная часть реализована на платформе Java с использованием описанных ранее возможностей фреймвор-ков семейства Spring Projects. Серверная часть приложения реализует следующие функции:
• Авторизация. Функции приложения недоступны для неавторизированных пользователей, в процессе авторизации определяется две роли пользователей — студенты и преподаватели, которым доступны разные функции приложения.
• Взаимодействие с базой данных. Информация о студентах, преподавателях, консультациях и записях о них хранится в реляционной БД (используется СУБД H2, предоставляемая Spring по умолчанию). Реализованы средства для создания, чтения, обновления и удаления данных.
• Запись студентов на консультации позволяет авторизованному студенту записаться на консультацию или отменить свою запись.
• Управление консультаций преподавателями позволяет преподавателям создавать, просматривать и удалять консультации или планы консультаций.
• Оповещение по электронной почте. При записи студента на консультацию оповещается преподаватель, при удалении преподавателем консультации оповещаются все записанные на нее студенты.
На рисунках 7 и 8 представлены диаграммы прецедентов для разрабатываемого веб-приложения, полученные на этапе его логического проектирования при анализе постановки задачи.
Рис. 5. Диаграмма прецедентов студента
Рис. 6. Диаграмма прецедентов преподавателя
Исходя из выявленных прецедентов, для клиентской части разработано одиннадцать веб-страниц приложения, реализующих варианты взаимодействия с пользователями типа «студент» и «преподаватель». В качестве средства для реализации клиентской части выбран инструмент генерации веб-страниц на основании шаблона Thymeleaf.
На рисунке 9 представлена диаграмма компонентов разработанного веб-приложения.
Приложение включает в себя такие компоненты, как:
• общая клиентская часть, предназначенная для всех пользователей;
• интерфейс студента, предназначенный для работы студентов с веб-приложением;
• интерфейс преподавателя, предназначенный для работы преподавателей с веб-приложением.
• средства по взаимодействию с базой данных, предназначенные для манипуляции данными в БД;
• средства авторизации, предназначенные для авторизации пользователя и определения его типа;
• компонент записи на консультации, с помощью которого студенты могут пользоваться функциями, обеспечивающими запись на консультации;
• компонент управления консультациями, с помощью которого преподаватели могут пользоваться функциями, связанными с созданием, планированием и отменой консультаций;
• компонент оповещения по электронной почте студентов и преподавателей об изменениях в графике проведения консультаций.
В настоящее время семейство фреймворков Spring Projects активно применяется для разработки корпоративных систем и веб-сервисов на платформе Java.
В статье обсуждаются особенности разработки веб-приложений с использованием средств фреймворков Spring Framework, Spring Boot, Spring Data и Spring Security. Кратко описывается инструмент Spring Initializr, применяемый для создания проектов приложений. Рассматриваются основные концепции Spring Framework: IoC-контейнер, Spring
Рис. 7. Диаграмма компонентов веб-приложения
Scopes, Spring MVC и Spring AOP. Освещаются возможности, предоставляемые Spring Boot: запуск приложения, профили, конфигурационные файлы. Уделяется внимание организации взаимодействия с базами данных с помощью Spring Data и использованию средств авторизации, предоставляемых Spring Security.
Описание возможностей фреймворков семейства Spring Projects сопровождается отсылками к примерам их использования при разработке демонстрационного приложения веб-приложения, функциональность которого ориентирована на решение практически значимой в вузовской среде задачи записи студентов на консультации и управления консультациями. Приложение разработано и отлажено при подготовке выпускной квалификационной бакалаврской работы В. В. Саковича и может рассматриваться как действующий прототип реальной системы.
По мнению авторов, преимущества использования Spring Projects заключаются в следующем.
Во-первых, обилие специализированных фреймворков, входящих в семейство, позволяет упростить решение задач, с которыми сталкиваются разработчики при разработке сложных систем. Так, IoC-контейнер позволяет сосредоточиться на разработке классов, отвечающих за функциональность приложения, а их взаимодействие в ходе выполнения обеспечивает Spring Framework. Во-вторых, использование Spring Boot позволяет упростить запуск приложения путем автоматизации его настройки. Кроме того, фреймворки Spring Projects позволяют разработчику интегрировать приложение с разнообразным набором сервисов, включая базы данных и контейнеры сервлетов.
Таким образом, Spring Projects хорошо подходит для разработки сложных и многофункциональных систем, в том числе в области транспорта, например для решения задач комплексного управления логистикой и предоставления услуг клиентам такими компаниями в области транспорта, как ОАО «РЖД».
Использованные в статье материалы прошли апробацию на LXXXIII Всероссийской научно-технической конференции студентов, аспирантов и молодых ученых
«Транспорт: проблемы, идеи, перспективы», состоявшейся в ПГУПС в рамках фестиваля «Неделя науки — 2023». Материалы доклада В. В. Саковича, подготовленного под руководством его соавторов, рекомендованы к публикации кафедрой «Информационные и вычислительные системы».
1. Хомоненко, А. Д. Разработка Web-приложений для работы с базами данных: Учебное пособие / А. Д. Хомоненко, В. В. Рогальчук, А. В. Тырва; под ред. А. Д. Хомоненко. — Санкт-Петербург: ПГУПС, 2012. — 87 с.
2. Web Application Architecture: The Latest Guide 2022. — 2022. — 10 March // ClicklT. DevOps & Software Development URL: http://www.clickittech.com/devops/web-applica-tion-architecture (дата обращения 18.06.2023).
3. Уоллс, К. Spring в действии. Шестое издание = Spring in Action. Sixth Edition / пер. с англ. А. Н. Киселева. — Москва: ДМК Пресс, 2022. — 544 c.
4. Vermeer B. Spring Dominates the Java Ecosystem with 60% Using It for Their Main Applications — 2020. — 5 February // Snyk. Developer Security Platform. URL: http://snyk.io/blog/ spring-dominates-the-java-ecosystem-with-60-using-it-for-their-main-applications (дата обращения 18.06.2023).
5. Why Spring? // Spring. URL: http://spring.io/why-spring (дата обращения 18.06.2023).
6. Projects // Spring. URL: http://spring.io/projects (дата обращения 18.06.2023).
7. Understanding Spring Framework and Spring Ecosystem // Pranay Bathini’s Blog. — 2021. — 02 July.
URL: http://www.pranaybathini.com/2021/07/understanding-spring-framework-and-ecosystem.html (дата обращения 18.06.2023).
8. Spring Initializr. URL: http://start.spring.io (дата обращения 18.06.2023).
9. Шилдт, Г. Java 8. Полное руководство. Девятое издание = Java: The Complete Reference. Ninth Edition / пер. с англ. и редакция И. В. Берштейна. — Москва: Издательский дом «Вильямс», 2015. — 1376 с.
10. Spring Framework. Core Technologies // Spring. URL: http://docs.spring.io/spring-framework/reference/core.html (дата обращения 18.06.2023).
11. Spring Framework. Bean Scopes // Spring.
URL: http://docs.spring.io/spring-framework/reference/core/ beans/factory-scopes.html (дата обращения 18.06.2023).
12. MVC — MDN Web Docs Glossary: Definitions of Web-related terms // MDN Web Docs.
URL: http://developer.mozilla.org/en-US/docs/Glossary/MVC (дата обращения 18.06.2023).
13. Späth, P. Beginning Java MVC 1.0: Model View Controller Development to Build Web, Cloud, and Microservices Applications. — New York: Apress, 2020. — 460 p.
14. Walls, C. Spring in Action. Fourth Edition. — New York: Manning, 2014. — 624 c.
15. Spring Framework. Spring Web MVC // Spring. URL: http://docs.spring.io/spring-framework/reference/web/ webmvc.html (дата обращения 18.06.2023).
16. Spring Framework. Proxying Mechanisms // Spring. URL: http://docs.spring.io/spring-framework/reference/core/aop/ proxying.html (дата обращения 18.06.2023).
17. Spring Framework. Aspect Oriented Programming with Spring // Spring. URL: http://docs.spring.io/spring-framework/ reference/core/aop.html (дата обращения 18.06.2023).
18. Common Application Properties // Spring.
URL: http://docs.spring.io/spring-boot/docs/cunient/reference/html/ application-properties.html (дата обращения 18.06.2023).
19. Spring Data Commons — Reference Documentation. Version 3.1.1 / O. Gierke, T. Darimont, C. Strobl, [et al.] // Spring. — Обновлено 16.06.2023.
URL: http://docs.spring.io/spring-data/commons/docs/current/ reference/html (дата обращения 18.06.2023).
20. Spring Security. Architecture // Spring.
URL: http://docs.spring.io/spring-security/reference/servlet/ architecture.html (дата обращения 18.06.2023).
21. Spring Security Docs 6.1.0 API // Spring.
URL: http://docs.spring.io/spring-security/site/docs/current/api/ index.html (дата обращения 18.06.2023).
Using Spring Projects Family Frameworks for Developing Web Applications on Java Platform
V. V. Sakovich, PhD G. I. Kozhomberdieva Emperor Alexander I St. Petersburg State Transport University Saint Petersburg, Russia lampirg1@gmail.com, kgi-liizht@yandex.ru
Abstract. The article discusses the features of developing web applications on the Java platform using frameworks from the Spring Projects family. The basic concepts of the Spring Framework are considered: IoC container, Spring Scopes, Spring MVC, and Spring AOP. The possibilities provided by the Spring Boot are covered: application launch, profiles, and configuration files. In addition, attention is paid to the organization of interaction with databases using the Spring Data as well as using of authorization tools provided by the Spring Security. As a demo of a web application developed using the Spring features, the article provides a client server application for signing up students for a consultation and consultation management.
Keywords: web application, Java platform, Java annotation, framework, Spring Projects, Spring Framework, MVC architecture.
i Не можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
1. Khomonenko A. D., Rogalchuk V. V., Tyrva A. V. Development of Web Applications for Working with Databases: Study guide [Razrabotka Web-prilozheniy dlya raboty s bazami dan-nykh: Uchebnoe posobie]. Saint Petersburg, PSTU, 2012, 87 p.
2. Web Application Architecture: The Latest Guide 2022, ClickIT. DevOps & Software Development. Published online at March 10, 2022. Available at: http://www.clickittech.com/ devops/web-application-architecture (accessed 18 Jun 2023).
3. Walls C. Spring in Action. Sixth Edition [Spring v deystvii. Shestoe izdanie]. Moscow, DMK Press Publishing House, 2022, 544 p.
4. Vermeer B. Spring Dominates the Java Ecosystem with 60% Using It for Their Main Applications, Snyk. Developer Security Platform. Published online at February 05, 2020. Available at: http://snyk.io/blog/spring-dominates-the-java-ecosys-tem-with-60-using-it-for-their-main-applications (accessed 18 Jun 2023).
5. Why Spring, Spring. Available at: http://spring.io/why-spring (accessed 18 Jun 2023).
6. Projects, Spring. Available at: http://spring.io/projects (accessed 18 Jun 2023).
7. Understanding Spring Framework and Spring Ecosystem, Pranay Bathini’s Blog. Published online at July 02, 2021. Available at: http://www.pranaybathini.com/2021/07/under-standing-spring-framework-and-ecosystem.html (accessed 18 Jun 2023).
8. Spring Initializr. Available at: http://start.spring.io/ (accessed 18 Jun 2023).
PhD D. P. Burakov independent researcher Saint Petersburg, Russia burakovdmitry8@gmail.com
9. Schildt H. Java: The Complete Reference. Ninth Edition [Java 8. Polnoe rukovodstvo. Devyatoe izdanie]. Moscow, Williams Publishing House, 2015, 1376 c.
10. Spring Framework. Core Technologies, Spring. Available at: http://docs.spring.io/spring-framework/reference/ core.html (accessed 18 Jun 2023).
11. Spring Framework. Bean Scopes, Spring.
Available at: http://docs.spring.io/spring-framework/reference/ core/beans/factory-scopes.html (accessed 18 Jun 2023).
12. MVC — MDN Web Docs Glossary: Definitions of Web-related terms, MDN Web Docs. Available at: http://devel-oper.mozilla.org/en-US/docs/Glossary/MVC (accessed 18 Jun 2023).
13. Späth P. Beginning Java MVC 1.0: Model View Controller Development to Build Web, Cloud, and Microservices Applications. New York, Apress, 2020, 460 p.
14. Walls C. Spring in Action. Fourth Edition. New York, Manning, 2014, 624 p.
15. Spring Framework. Spring Web MVC, Spring. Available at: http://docs.spring.io/spring-framework/reference/web/ webmvc.html (accessed 18 Jun 2023).
16. Spring Framework. Proxying Mechanisms, Spring. Available at: http://docs.spring.io/spring-framework/reference/ core/aop/proxying.html (accessed 18 Jun 2023).
17. Spring Framework. Aspect Oriented Programming with Spring, Spring. Available at: http://docs.spring.io/spring-framework/reference/core/aop.html (accessed 18 Jun 2023).
18. Common Application Properties, Spring. Available at: http://docs.spring.io/spring-boot/docs/current/reference/html/ application-properties.html (accessed 18 Jun 2023).
19. Gierke O., Darimont T., Strobl C., et al. Spring Data Commons — Reference Documentation. Version 3.1.1, Spring. Last updated at June 16, 2023.
Available at: http://docs.spring.io/spring-data/commons/docs/ current/reference/html (accessed 18 Jun 2023).
20. Spring Security. Architecture, Spring.
Available at: http://docs.spring.io/spring-security/reference/ servlet/architecture.html (accessed 18 Jun 2023).
21. Spring Security Docs 6.1.0 API, Spring.
Spring MVC приложение за 15 минут
Spring web фреймворк разработан, чтобы облегчить жизнь современному enterprise разработчику создающему web-приложения. Он базируется на популярном архитектурном шаблоне: MVC(Model-View-Controller). Приложения получается слабо связными и гибкими в настройке как и сам Spring фреймворк.
Жизненный цикл запроса
Каждый раз когда пользователь нажимает ссылку или отправляет форму в web-браузере запрос отправляется на сервер. Давайте посмотрим на схему обработки запроса в Spring MVC(ссылка кликабельна и ведет на документацию на spring.io):

Spring MVC использует подход называемый wiki/Front_controller. В роли главного сервлета или front controller, принимающего все входящие запросы выступает DispatcherServlet. Работа DispatcherServlet состоит в том, чтобы послать запрос на соответствующий Spring MVC контроллер. Он пользуется помощью так называемых handler mappings которые помогают ему определить какой именно контроллер нужно вызвать.
После этого контроллер начинает отрабатывать.
Стоит заметить, что обычно хорошо спроектированный контроллер делегирует работу по выполнению запроса на какой либо сервисный метод содержащий бизнес-логику.
Обычно после выполнения бизнес-логики пользователю нужно показать какую то информацю как результат. Эта информация обычно храниться в модели. Однако информация пеед показом должна пройти обработку, чтобы принять удобоваримый вид, например HTML. Для этого контроллер посылает модель и название view обратно в DispatcherServlet. Таким образом контролеер отвязан от конкретной реализации view. DispatcherServlet посылает запрос в view resolver чтобы понять какую действительно HTML-страницу нужно показать пользователю.
Конфигурация
Начиная с версии Servlet 3.0 мы можем использовать не web.xml а Java-конфигурацию. Рекомендуется наследоваться от AbstractAnnotationConfigDispatcherServletInitializer который предоставляет возможность настроить 2 контекста: AppConfig и WebConfig
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer @Override protected Class[] getRootConfigClasses() // AppConfig defines beans that would be in root-context.xml return new Class[] AppConfig.class >; > @Override protected Class[] getServletConfigClasses() // WebConfig defines beans that would be in servlet.xml return new Class[] WebConfig.class >; > @Override protected String[] getServletMappings() return new String[] "/" >; > >
Важно понимать разницу между корневым ApplicationContext и WebServletApplicationContext. Первый обычно содержит все сервисные и инфрастуктурные бины вашего приложения. Второй обычно содержит контекст относящийся к отдельному DispatcherServlet.
Как вы уже догадались, в отдельном приложении может быть несколько DispatcherServlet.
Таким образом, отдельные бины могут быть переопределены в WebServletApplicationContext, если это нужно.

В простейшем случае настройка WebAppContext сведется к такому Java-config:
@Configuration @EnableWebMvc @ComponentScan(basePackages = "ru.smartcoder.spring_mvc_example.controller") public class WebConfig extends WebMvcConfigurerAdapter @Bean public ViewResolver viewResolver() InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; > @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) configurer.enable(); > >
- @EnableWebMvc поможет включить Spring MVC без дополнительных xml настроек
- viewResolever метод создаст бин типа ViewResolver который поможет DispatcherServlet определить нужную JSP для отображения.
- метод configureDefaultServletHandling перенаправит все запросы на статические ресурсы с DispatcherServlet на сервлет вашего контейнера для лучшей производительности what-does-configuredefaultservlethandling-means
Таким может быть настройка корневого контекста:
@Configuration @ComponentScan(basePackages=, excludeFilters=< @ComponentScan.Filter(type= FilterType.ANNOTATION, value=EnableWebMvc.class) >) public class AppConfig
Заметьте, что для того, чтобы исключить из @ComponentScan конфигурацию с аннотацией @EnableWebMvc мы использовали параметр excludeFilters. В данном приложении это необязательно, однако стоит иметь ввиду имеющиеся возможности у component scan preventing-a-enablewebmvc-annotated-class
Запуск
Для того, чтобы запустить приложение выполните следующую команду maven:
Зайдя по ссылке http://localhost:9090/hello вы увидите что наш контроллер отработал успешно.
Заключение
Updated: November 13, 2016
Урок 2: Введение в Spring IoC контейнер
Этот урок освещает работу с Spring Framework IoC контейнером и основан на оригинальной документации §5. The IoC container.
Что вы создадите
Вы создадите некоторое количество классов, в которых будет рассмотрена функциональность Spring Framework IoC контейнера.
Что вам потребуется
- Любимый текстовый редактор или IDE
- JDK 7 и выше
- Maven 3.0+
- Исходный код предыдущего урока
Настройка проекта
Прежде чем вы начнете изучать этот урок, вам необходимо внести некоторые изменения в проект. Для начала создайте структуру папок src/main/resources и переместите в него файл настроек логгирования log4j.properties , тем самым поместив его в classpath проекта. Теперь немного измените файл сборки pom.xml , добавив и изменив в нем следующее:
. 1.7 1.5.8 . org.slf4j jcl-over-slf4j $ org.slf4j slf4j-api $ org.slf4j slf4j-log4j12 $ . org.apache.maven.plugins maven-compiler-plugin 3.2 $ $ $ -Xlint:all true true .
И наконец, создайте структуру папок src/main/java/lessons/starter/ . В данном пакете вы будете создавать классы с методами public static void main(String[] args) , которые вы будете запускать для того, чтобы можно было видеть результаты действий в процессе изучения данного материала.
Введение
Inversion of Control (IoC), также известное как Dependency Injection (DI), является процессом, согласно которому объекты определяют свои зависимости, т.е. объекты, с которыми они работают, через аргументы конструктора/фабричного метода или свойства, которые были установлены или возвращены фабричным методом. Затем контейнер inject(далее «внедряет») эти зависимости при создании бина. Этот процесс принципиально противоположен, поэтому и назван Inversion of Control, т.к. бин сам контролирует реализацию и расположение своих зависимостей, используя прямое создание классов или такой механизм, как шаблон Service Locator.
Основными пакетами Spring Framework IoC контейнера являются org.springframework.beans и org.springframework.context . Интерфейс BeanFactory предоставляет механизм конфигурации по управлению любым типом объектов. ApplicationContext — наследует нитерфейс BeanFactory и добавляет более специфичную функциональность. Ниже в таблице представлены различия между ними:
Функционал
BeanFactory
ApplicationContext
Инициализация/автоматическое связывание бина
Автоматическая регистрация BeanPostProcessor
Автоматическая регистрация BeanFactoryPostProcessor
Удобный доступ к MessageSource (для i18n)
ApplicationEvent публикация
В большинстве случаев предпочтительно использовать ApplicationContext , поэтому в дальнейшем будет использоваться только он и его реализации. Поскольку он включает в себя всю функциональность BeanFactory , его можно и нужно использовать, за исключением случаев, когда приложение запускается на устройствах с ограниченными ресурсами, в которых объем потребляемой памяти может быть критичным, даже в пределах нескольких килобайт, либо когда вы разрабатываете приложение, в котором необходима поддержка совместимости со сторонними библиотеками, использующими JDK 1.4 или не поддерживают JSR-250. Spring Framework активно использует BeanPostProcessor для проксирования и др., поэтому, если вам необходима поддержка такой функциональности, как AOP и транзакций, то при использовании BeanFactory необходимо добавить вручную регистрацию BeanPostProcessor и BeanFactoryPostProcessor , как показано ниже:
ConfigurableBeanFactory factory = new XmlBeanFactory(. ); // теперь зарегистрируем необходимый BeanPostProcessor экземпляр MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // запускаем, используя factory
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // получаем какое-то значения свойства из Properties-файла PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // теперь заменяем значение свойства на новое cfg.postProcessBeanFactory(factory);
Аннотации @Autowired , @Inject , @Resource и @Value обрабатываются Spring реализацией BeanPostProcessor , поэтому вы не можете их применять в своих собственных BeanPostProcessor и BeanFactoryPostProcessor , а только лишь явной инициализацией через XML или @Bean метод.
Описание работы IoC контейнера
Ниже представлена диаграмма, отражающая, как работает Spring. Ваши классы приложения совмещаются с метаданными конфигурации, в результате чего будет создан и инициализирован ApplicationContext , а на выходе вы получите полностью настроенное и готовое к выполнению приложение.

ApplicationContext представляет собой Spring IoC контейнер и необходим для инициализации, настройки и сборки бинов для построения приложения.

В метаданных конфигурации разработчик описывает как инициализировать, настроить IoC контейнер и собрать объекты в вашем приложении. В данном и других уроках этого цикла везде, где возможно, будет использоваться подход на основе аннотаций и Java-конфигурации. Если вы сторонник XML-конфигурации, либо хотите посмотреть как делать тоже самое через XML, обратитесь к оригинальной документации по Spring Framework или соответствующего модуля/проекта.
Настройка IoC контейнера
Основными признаками и частями Java-конфигурации IoC контейнера являются классы с аннотацией @Configuration и методы с аннотацией @Bean . Аннотация @Bean используется для указания того, что метод создает, настраивает и инициализирует новый объект, управляемый Spring IoC контейнером. Такие методы можно использовать как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Класс с аннотацией @Configuration говорит о том, что он является источником определения бинов. Самая простейшая из возможных конфигураций выглядит следующим образом:
package lessons; import org.springframework.context.annotation.Configuration; /** * Конфигурационный класс Spring IoC контейнера */ @Configuration public class LessonsConfiguration
Полный @Configuration vs легкий @Bean режимы
Когда методы с аннотацией @Bean определены в классах, не имеющих аннотацию @Configuration , то относятся к обработке в легком режиме, то же относится и к классам с аннотацией @Component . Иначе, такие методы относятся к полному режиму обработки.
В отличие от полного, в легком режиме @Bean методы не могут просто так объявлять внутренние зависимости. Поэтому, в основном предпочтительно работать в полном режиме, во избежание трудноуловимых ошибок.
Для того, чтобы приступить к настройке и изучению Spring IoC контейнера, вы должны инициализировать ApplicationContext , который поможет также с разрешением зависимостей. Для обычной Java-конфигурации применяется AnnotationConfigApplicationContext , в качестве аргумента к которому передается класс, либо список классов с аннотацией @Configuration , либо с любой другой аннотацией JSR-330, в том числе и @Component :
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); >>
Как вариант, можно инициализировать контекст(ы) таким образом:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(LessonsConfiguration.class); context.refresh(); >>
Использование @Bean аннотации
Как упоминалось выше, для того, чтобы объявить Bean-объект(далее просто бин), достаточно указать аннотацию @Bean тому методу, который возвращает тип бина как в классах с аннотацией @Configuration , так и в классах с аннотацией @Component (или её наследниках). Например, определим интерфейс какого-нибудь сервиса и его реализацию:
package lessons.services; public interface GreetingService
package lessons.services; public class GreetingServiceImpl implements GreetingService < @Override public String sayGreeting() < return "Greeting, user!"; >>
Теперь, для того, чтобы объект с типом GreetingService был доступен для использования, необходимо описать его в конфигурации следующим образом:
@Configuration public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
А для того, чтобы использовать его, достаточно выполнить следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" >>
Метод getBean() может принимать в качестве аргумента как класс(как показано выше), так и названия бина(подробнее будет рассмотрено ниже), либо другие варианты, с которыми вы можете ознакомится в документации. Однако такой подход не рекомендуется использовать в production-конфигурациях, т.к. для подобных целей существует механизм Dependency Injection (DI), собственно говоря, для чего и предназначен Spring IoC контейнер. Использование DI будет рассмотрено ниже в отдельной главе.
Именовать бины принято в соответствии со стандартным соглашением по именованию полей Java-классов. Т.е. имена бинов должны начинаться со строчной буквы и быть в «Верблюжьей» нотации.
По умолчанию, так, как будет назван метод определения бина, по такому имени и нужно получать бин через метод getBean() или автоматическое связывание. Однако вы можете переопределить это имя или указать несколько псевдонимов, через параметр name аннотации @Bean . Выглядеть это будет примерно так:
@Bean(name = "gServiceName")
@Bean(name = )
Иногда полезно предоставить более подробное описание бина, например, в целях мониторинга. Для этого существует аннотация @Description :
@Bean @Description("Текстовое описание бина greetingService") GreetingService greetingService()
Жизненный цикл бина
Для управления контейнером жизненным циклом бина, вы можете реализовать метод afterPropertiesSet() интерфейса InitializingBean и метод destroy() интерфейса DisposableBean . Метод afterPropertiesSet() позволяет выполнять какие-либо действий после инициализации всех свойств бина контейнером, метод destroy() выполняется при уничтожении бина контейнером. Однако их не рекомендуется использовать, поскольку они дублируют код Spring. Как вариант, предпочтительно использовать методы с JSR-250 аннотациями @PostConstruct и @PreDestroy . Также существует вариант определить аналогичные методы как параметры аннотации @Bean , например так: @Bean(initMethod = «initMethod», destroyMethod = «destroyMethod») .В качестве примера применения данных методов, интерфейсов и аннотаций вы можете ознакомиться в классе GreetingServiceImpl .
При совместном использовании методов, интерфейсов и аннотаций, описанных выше, учитывайте их порядок вызовов. Для методов инициализации порядок будет следующий:
- Методы с аннотациями @PostConstruct в порядке их определения в классе
- Метод afterPropertiesSet()
- Метод, указанный в параметре initMethod аннотации @Bean
Для методов разрушения порядок будет следующий:
- Методы с аннотациями @PreDestroy в порядке их определения в классе
- Метод destroy()
- Метод, указанный в параметре destroyMethod аннотации @Bean
Если вам необходимо реализовать свою собственную модель жизненного цикла бина, то в таком случае бин должен реализовывать один из интерфейсов, приведенных ниже:
public interface Lifecycle
public interface LifecycleProcessor extends Lifecycle
public interface SmartLifecycle extends Lifecycle, Phased
SmartLifecycle интересен тем, что наследует интерфейс Phased , в котором есть метод int getPhase(); . Суть в том, что порядок создания бинов, реализующих этот интерфейс, зависит от возвращаемого методом значения и чем оно меньше, тем раньше всех будет создан бин и тем позже он будет разрушен.
Если вы на данном этапе запустите Starter.java , то в логах увидите, что методы разрушения не вызываются, однако программа завершает свою работу корректно. Дело в том, что для обычных приложений для этих целей стоит инициализировать контекст с типом AbstractApplicationContext , который также реализует ApplicationContext и имеет метод registerShutdownHook() . В итоге, у вас должно быть премерно следующее:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); AbstractApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" context.registerShutdownHook(); >>
После этого у вас появятся результаты работы методов при разрушении бина. Однако стоит заметить ещё раз, что это относится к обычным приложения, не относящимся к web-приложения(поскольку для них применяется отдельный тип контекста и подобный метод в них уже есть).
В некоторых случаях необходимо производить манипуляции с ApplicationContext ‘ом, например, в самом бине. Для этого существуют интерфейсы *Aware , полный список которых приведен в таблице 5.4 документации. Поэтому когда ApplicationContext создает экземпляр бина, он учитывает соответствующий интерфейс и передает ссылку на соответствующий ресурс.
Как было описано выше, Spring IoC контейнеру требуются метаданные для конфигурации. Одну из таких аннотаций мы уже рассмотрели, это @Bean , рассмотрим теперь и другие.
Другой основной аннотацией является @Component , а также её наследники @Repository , @Service и @Controller . Все они являются общими шаблонами для любых компонентов, управляемыми контейнеером. @Repository , @Service и @Controller рекомендуется использовать в тех случаях, когда вы можете отнести аннотируемый класс к определенному слою, например DAO, либо когда вам необходима поддержка функциональности, которую предоставляет аннотация. Также эти аннотации могут иметь дополнительный смысл в будущих версиях Spring Framework. В остальных же случаях достаточно использовать аннотацию @Component .
Для того, чтобы ваша конфигурация могла знать о таких компонентах и вы могли бы их использовать, существует специальная аннотация для класса вашей конфигурации @ComponentScan .
@Configuration @ComponentScan public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
По умолчанию, такая конфигурация сканирует на наличие классов с аннотацией @Component и его потомков в том пакете, в котором сама находится, а также в подпакетах. Однако, если вы хотите, чтобы сканирование было по определенным каталогам, то это можно настроить, просто добавив в аннотацию @ComponentScan параметр basePackages с указанием одного или нескольких пакетов. Выглядеть это будет примерно таким образом: @ComponentScan(basePackages = «lessons.services») , а классу GreetingServiceImpl при этом необходимо добавить аннотацию @Component .
Стоит упомянуть ещё одну мета-аннотацию @Required . Данная аннотация применяется к setter-методу бина и указывает на то, чтобы соответствующее свойство метода было установлено на момент конфигурирования значением из определения бина или автоматического связывания. Если же значение не будет установлено, будет выброшено исключение. Использование аннотации позволит избежать NullPointerException в процессе использования свойства бина. Пример использования:
package lessons.services; public class GreetingServiceImpl implements GreetingService < private ApplicationContext context; @Required public void setContext(ApplicationContext context) < this.context = context; >>
Области видимости(scopes) бинов
Когда вы создаете определение бинов, вы вы создаете рецепт для создания экземпляров класса, который определяет бин. Важно понять, что определение бинов является рецептом, потому что он означает, какого класса вы можете создать множество экземпляров по этому рецепту.
Вы можете контролировать не только какие зависимости и значения конфигурации вы можете подключить в объекте, который создан из определения бина, но также область видимости из того же определения бина. Это мощный и гибкий подход, при котором вы можете выбрать область видимости создаваемых объектов. Изначально, Spring Framework поддерживает несколько вариантов, некоторые доступны, только если вы используете web-aware ApplicationContext . Также вы можете создать свою собственную облать видимости. Ниже приведен список областей видимостей, описанных в документации на момент написания урока:
- singleton — По умолчанию. Spring IoC контейнер создает единственный экземпляр бина. Как правило, используется для бинов без сохранения состояния(stateless)
- prototype — Spring IoC контейнер создает любое количество экземпляров бина. Новый экземпляр бина создается каждый раз, когда бин необходим в качестве зависимости, либо через вызов getBean() . Как правило, используется для бинов с сохранением состояния(stateful)
- request — Жизненный цикл экземпляра ограничен единственным HTTP запросом; для каждого нового HTTP запроса создается новый экземпляр бина. Действует, только если вы используете web-aware ApplicationContext
- session — Жизненный цикл экземпляра ограничен в пределах одной и той же HTTP Session . Действует, только если вы используете web-aware ApplicationContext
- global session — Жизненный цикл экземпляра ограничен в пределах глобальной HTTP Session (обычно при использовании portlet контекста). Действует, только если вы используете web-aware ApplicationContext
- application — Жизненный цикл экземпляра ограничен в пределах ServletContext . Действует, только если вы используете web-aware ApplicationContext
С более подробной информацией о настройке приложения для применения областей видимости request , session , global session и application вы можете ознакомиться в документации. Пример реализации собственной области видимости будет рассмотрено в отдельном уроке.
Для того, чтобы указать область видимости бина, отличный от singleton , необходимо добавить аннотацию @Scope(«область_видимости») методу объявления бина или классу с аннотацией @Component :
@Component @Scope("prototype") public class GreetingServiceImpl implements GreetingService < //. >
Использование @Configuration аннотации
Как упоминалось выше, классы с аннотацией @Configuration указывают на то, что они являются источниками определения бинов, public-методов с аннотацией @Bean .
Кода бин имеет зависимость от другого бина, то зависимость выражается просто как вызов метода:
@Configuration @ComponentScan public class LessonsConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(greetingService()); >@Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Однако работает такое взаимодействие только в @Configuration -классах, в @Component -классах такое не работает.
Представим теперь ситуацию, когда у вас есть бин с областью видимости singleton , который имеет зависимость от бина с областью видимости prototype .
public abstract class CommandManager
@Configuration @ComponentScan public class LessonsConfiguration < @Bean @Scope("prototype") public Object asyncCommand() < return new Object(); >@Bean public CommandManager commandManager() < // возвращаем новую анонимную реализацию CommandManager // с новым объектом return new CommandManager() < protected Object createCommand() < return asyncCommand(); >>; > >
Большая часть приложений строится по модульной архитектуре, разделенная по слоям, например DAO, сервисы, контроллеры и др. Создавая конфигурацию, можно также её разбивать на составные части, что также улучшит читабельность и панимание архитектуры вашего приложения. Для этого в конфигурацию необходимо добавить аннотацию @Import , в параметрах которой указываются другие классы с аннотацией @Configuration , например:
@Configuration public class AnotherConfiguration < @Bean BeanWithDependency beanWithDependency() < return new BeanWithDependency(); >>
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Таким образом, при инициализации контекста вам не нужно дополнительно указывать загрузку из конфигурации AnotherConfiguration , все останется так, как и было:
public class Starter < private static final Logger logger = LogManager.getLogger(Starter.class); public static void main(String[] args) < logger.info("Starting configuration. "); ApplicationContext context = new AnnotationConfigApplicationContext(LessonsConfiguration.class); GreetingService greetingService = context.getBean(GreetingService.class); BeanWithDependency withDependency = context.getBean(BeanWithDependency.class); logger.info(greetingService.sayGreeting()); // "Greeting, user!" logger.info(withDependency.printText()); // "Some text!" >>
В большинстве случаев, имеются такие случаи, когда бин в одной конфигурации имеет зависимость от бина в другой конфигурации. Поскольку конфигурация является источником определения бинов, то разрешить такую зависимость не является проблемой, достаточно объявить поле класса конфигурации с аннотацией @Autowired (более подробно оисано в отдельной главе):
@Configuration public class AnotherConfiguration < @Autowired GreetingService greetingService; @Bean BeanWithDependency beanWithDependency() < //что-нибудь делаем с greetingService. return new BeanWithDependency(); >>
При этом LessonsConfiguration остается без изменений:
@Configuration @ComponentScan @Import(AnotherConfiguration.class) public class LessonsConfiguration < @Bean GreetingService greetingService() < return new GreetingServiceImpl(); >>
Классы с аннотацией @Configuration не стремятся на 100% заменить конфигурации на XML, при этом, если вам удобно или имеется какая-то необходимость в использовании XML конфигурации, то к вашей Java-конфигурации необходимо добавить аннотацию @ImportResource , в параметрах которой необходимо указать нужное вам количество XML-конфигураций. Выглядит это следующим способом:
@Configuration @ImportResource("classpath:/lessons/xml-config.xml") public class LessonsConfiguration < @Value("$") String url; //. >
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
Процесс разрешения зависимостей
IoC контейнер выполняет разрешение зависимостей бинов в следующем порядке:
- Создается и инициализируется ApplicationContext с метаданными конфигурации, которые описывают все бины. Эти метаданные могут быть описаны через XML, Java-код или аннотации
- Для каждого бина и его зависимостей вычисляются свойства, аргументы конструктора или аргументы статического фабричного метода, либо обычного(без аргументов) конструктора. Эти зависимости предоставляются бину, когда он(бин) уже создан. Сами зависимости инициализируются рекурсивно, в зависимости от вложенности в себе других бинов. Например, при инициализации бина А, котый имеет зависимость В, а В зависит от С, сначала инициализируется бин С, потом В, а уже потом А
- Каждому свойству или аргументу конструктора устанавливается значение или ссылка на другой бин в контейнере
- Для каждого свойства или аргумента конструктора подставляемое значение конвертируется в тот формат, который указан для свойства или аргумента. По умолчанию Spring может конвертировать значения из строкового формата во все встроенные типы, такие как int , long , String , boolean и др.
Spring каждый раз при создании контейнера проверяет конфигурацию каждого бина. И только бины с областью видимости(scope) singleton создаются сразу вместе со своими зависимостями, в отличие от остальных, которые создаются по запросу и в соответствии со своей областью видимости. В случае цикличной зависимости(когда класс А требует экземпляр В, а классу В требуется экземпляр А) Spring IoC контейнер обнаруживает её и выбрасывает исключение BeanCurrentlyInCreationException .
Spring контейнер может разрешать зависимости между бинами через autowiring(далее, автоматическое связывание). Данный механизм основан на просмотре содержимого в ApplicationContext и имеет следующие преимущества:
- Автоматическое связывание позволяет значительно сократить количество инструкций для указания свойств или аргументов конструктора
- Автоматическое связывание позволяет обновлять конфигурацию, несмотря на развитие ваших объектов. К примеру, вам необходимо добавить зависимость в классе и эта зависимость может быть разрешена без необходимости модификации конфигурации. Поэтому автоматическое связывание может быть особенно полезным при разработке, не исключая возможность переключения на явное описание, когда кодовая база будет стабильна
Для того, чтобы воспользоваться механизмом автоматического связывания, Spring Framework предоставляет аннотацию @Autowired . Примеры применения приведены ниже:
public class AutowiredClass < @Autowired //к полям класса @Qualifier("main") //@Autowired(required = false) //чтобы не бросалось исключение, //если не с кем связать //рекомендуется использовать @Required private GreetingService greetingService; @Autowired //к полям класса в виде массива или коллекции private GreetingService[] services; @Autowired //к Map, где ключами являются имена бинов, значения - сами бины private MapserviceMap; @Autowired //к конструктору public AutowiredClass(@Qualifier("main") GreetingService service) <> @Autowired //к обычным методам с произвольным названием аргументов и их количеством public void prepare(GreetingService prepareContext)* что-то делаем. */> @Autowired //к "традиционному" setter-методу public void setContext(GreetingService service) < this.greetingService = service; >>
Т.к. кандидатов для автоматического связывания может быть несколько, то для установки конкретного экземпляра необходимо использовать аннотацию @Qualifier , как показано ниже. Данная аннотация может быть применена как к отдельному полю класса, так и к отдельному аргументу метода или конструктора:
public class AutowiredClass < //. @Autowired //к полям класса @Qualifier("main") private GreetingService greetingService; @Autowired //к отдельному аргументу конструктора или метода public void prepare(@Qualifier("main") GreetingService greetingService)< /* что-то делаем. */ >; //. >
Соответственно, у одной из реализации GreetingService должна быть установлена соответствующая аннотация @Qualifier :
@Component @Qualifier("main") public class GreetingServiceImpl implements GreetingService < //. >
Spring также поддерживает использование JSR-250 @Resource аннотации автоматического связывания для полей класса или параметров setter-методов:
public class AutowiredClass < //. @Resource //По умолчанию поиск бина с именем "context" private ApplicationContext context; @Resource(name="greetingService") //Поиск бина с именем "greetingService" public void setGreetingService(GreetingService service) < this.greetingService = service; >//. >
Использование стандартных JSR-330 аннотаций
Spring Framework поддерживает JSR-330 аннотации. Эти аннотации работают таким же способом, как и Spring аннотации. Для того, чтобы работать с ними, необходимо добавить в pom.xml следующую зависимость:
javax.inject javax.inject 1
Ниже приведена таблица сравнения JSR-330 и Spring аннотаций для DI: