Файл: Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 174
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
public List getUsers() {
//code
} public void saveOrUpdate(User user){
//code
}
}
Репозитории тоже имеют свою аннотацию – Repository. Но также у
Spring есть еще одна интереснейшая технология – Spring Data. Рассмотрим данную технологию на Spring Data JPA, так как она используется для реляционных баз данных.
Данная технология основывается как раз-таки на репозиториях, но очень сильно уменьшает количество boilerplate кода. Все, что требуется сделать, это сконфигурировать взаимодействие с базой данных, создать интерфейс репозитория для каждой сущности – и все, можно дальше работать с бизнес-логикой, изредка дополняя методами в интерфейсах для нужного взаимодействия. Для начала возьмем приложение из предыдущего задания, и уберем предыдущую конфигурацию, перенеся полностью конфигурацию в application.yml: spring: datasource: url: your_url username: your_username password: your_password
И добавим над конфигурацией аннотацию EnableJpaRepositories:
@Configuration
@EnableJpaRepositories public class AppConfig {
}
С конфигурацией покончено. Теперь создадим интерфейсы- репозитории, а также добавим возможность поиска по названию породы в
Dog:
//code
} public void saveOrUpdate(User user){
//code
}
}
Репозитории тоже имеют свою аннотацию – Repository. Но также у
Spring есть еще одна интереснейшая технология – Spring Data. Рассмотрим данную технологию на Spring Data JPA, так как она используется для реляционных баз данных.
Данная технология основывается как раз-таки на репозиториях, но очень сильно уменьшает количество boilerplate кода. Все, что требуется сделать, это сконфигурировать взаимодействие с базой данных, создать интерфейс репозитория для каждой сущности – и все, можно дальше работать с бизнес-логикой, изредка дополняя методами в интерфейсах для нужного взаимодействия. Для начала возьмем приложение из предыдущего задания, и уберем предыдущую конфигурацию, перенеся полностью конфигурацию в application.yml: spring: datasource: url: your_url username: your_username password: your_password
И добавим над конфигурацией аннотацию EnableJpaRepositories:
@Configuration
@EnableJpaRepositories public class AppConfig {
}
С конфигурацией покончено. Теперь создадим интерфейсы- репозитории, а также добавим возможность поиска по названию породы в
Dog:
public interface UserRepository extends JpaRepositoryLong> {
} public interface DogRepository extends JpaRepositoryLong> {
List findAllByBreed(String breed);
}
И все. Spring по названию метода определяет то, каким должен быть запрос. Например, напишем метод по поиску по породе и имени: public interface DogRepository extends JpaRepositoryLong> {
List findAllByBreed(String breed);
List findAllByBreedAndName(String breed, String name);
}
Достаточно просто и удобно. При этом все взаимодействие с БД передается Spring, и не приходится постоянно размышлять о создании сессии, написании HQL даже для простых запросов и т.д. Если все же нужно выполнить более сложный запрос, который не может сделать Spring, или нативный запрос, можно воспользоваться аннотацией Query:
@Query(value = "select dogs.* from dogs join users on users.id = dogs.user_id where users.first_name = :username", nativeQuery = true)
List findAllByUserName(String username);
Теперь остается лишь воспользоваться Dependency injection (DI) и получить бин данного интерфейса. Spring сам создает имплементацию интерфейса и наполняет нужной логикой. Также JpaRepository имеет стандартные методы, такие как findAll, так что не придется их даже объявлять:
@Service
@RequiredArgsConstructor public class DogService { private final DogRepository dogRepository; public User getUserByDog(Long dogId) {
} public interface DogRepository extends JpaRepository
List
}
И все. Spring по названию метода определяет то, каким должен быть запрос. Например, напишем метод по поиску по породе и имени: public interface DogRepository extends JpaRepository
List
List
}
Достаточно просто и удобно. При этом все взаимодействие с БД передается Spring, и не приходится постоянно размышлять о создании сессии, написании HQL даже для простых запросов и т.д. Если все же нужно выполнить более сложный запрос, который не может сделать Spring, или нативный запрос, можно воспользоваться аннотацией Query:
@Query(value = "select dogs.* from dogs join users on users.id = dogs.user_id where users.first_name = :username", nativeQuery = true)
List
Теперь остается лишь воспользоваться Dependency injection (DI) и получить бин данного интерфейса. Spring сам создает имплементацию интерфейса и наполняет нужной логикой. Также JpaRepository имеет стандартные методы, такие как findAll, так что не придется их даже объявлять:
@Service
@RequiredArgsConstructor public class DogService { private final DogRepository dogRepository; public User getUserByDog(Long dogId) {
return dogRepository.findById(dogId).orElseThrow(() -> new IllegalStateException("Dog with this id not found")).getUser();
} public List getAllDogs() { return dogRepository.findAll();
}
}
Рекомендуется ознакомиться с документацией Spring для лучшего понимания репозиториев
(https://docs.spring.io/spring- data/jpa/docs/2.2.10.RELEASE/reference/html/#reference).
Spring Data repository – отличная технология для уменьшения boilerplate кода.
} public List
}
}
Рекомендуется ознакомиться с документацией Spring для лучшего понимания репозиториев
(https://docs.spring.io/spring- data/jpa/docs/2.2.10.RELEASE/reference/html/#reference).
Spring Data repository – отличная технология для уменьшения boilerplate кода.
1 2 3 4 5
18.2. Задание
Переписать код предыдущего задания с использованием сервисов и отделения логики контроллера от логики сервиса и репозитория. В программе всё взаимодействие с базой данных должно быть реализовано через репозитории Spring Data Jpa.
Переписать код предыдущего задания с использованием сервисов и отделения логики контроллера от логики сервиса и репозитория. В программе всё взаимодействие с базой данных должно быть реализовано через репозитории Spring Data Jpa.
19. ПРАКТИЧЕСКАЯ РАБОТА № 19
Цель работы
Знакомство с логированием с использованием Logback в Spring.
19.1. Теоретическая часть
Логирование – очень важная часть разработки. Часто мы не имеем возможности запустить дебаггер, воспроизвести нужную проблему и посмотреть, что происходит в программе в ней. Тем более, когда проблема не воспроизводится, без логирования мы вообще фактически остаемся ни с чем.
Воспользуемся библиотекой Logback для реализации логирования, а также Slf4j вместе с аннотациями Lombok для уменьшения boilerplate. После добавления всех нужных зависимостей (spring boot starter logging), создадим файл logback.xml, и сделаем логирование всей информации в стандартный поток вывода, а также в файл, который при превышении порога или прохождении 30 дней создает новый файл логов, а старый сжимает при помощи gzip. Но перед заполнением logback.xml, рассмотрим уровни логирования:
– Error – выводятся ошибки, то, что может критически повлиять на работу системы;
– Warn – предупреждения, которые не сломают логику, но могут негативно повлиять;
– Info – стандартный вывод информации;
– Debug – информация для дебага;
– Trace – максимально полная информация, очень редко используется, лучше не делайте логирование в файл с уровнем trace, иначе впоследствии логи будут практически нечитаемы.
А теперь заполним logback.xml:
%d{HH:mm:ss.SSS} [%thread] %-5level
%logger{36} – %msg%n
%-4relative [%thread] %-5level %logger{35}
– %msg%n
Корневой элемент – configuration. В Logback есть 3 основных элемента:
– Logger – контекст для логирования сообщений, может иметь несколько аппендеров;
– Appender – место, куда кладутся логи, например, файлы;
– Layout – то, как должны выглядеть логи.
В данном случае мы создаем 2 аппендера: в стандартный поток вывода, а также в файл с созданием файла каждый день, максимальное количество 30, а также максимальный размер 3 ГБайт. В pattern описывается паттерн, как должен выглядеть лог. Это и есть тот самый Layout.
rollingPolicy как раз-таки определяет обновление файла логов, чтобы он не разрастался, и определяет максимальное количество файлов, максимальный размер файлов.
TimeBasedRollingPolicy обозначает, что изменение основывается на времени.
Теперь можно запустить приложение и посмотреть, создался ли файл, записываются ли логи. Если все нормально, остается добавить собственное логирование. Для этого воспользуемся аннотацией Slf4j, и залогируем какой- либо сервис:
@Service
@RequiredArgsConstructor
@Slf4j public class UserServiceImpl implements UserService { private final UserRepository userRepository; public List getUsers() { log.info("Find all users"); return userRepository.findAll();
} public void saveOrUpdate(User user){ log.info("Save user {}", user); userRepository.save(user);
}
}
Теперь можно запустить и проверить, все ли залогировалось, а также открыть файл и проверить, записались ли туда логи.
Логирование не является сложным, но имеет огромное значение при отладке программ.
19.2. Задание
Создать файл logback.xml, добавить логирование во все методы классов- сервисов.
TimeBasedRollingPolicy обозначает, что изменение основывается на времени.
Теперь можно запустить приложение и посмотреть, создался ли файл, записываются ли логи. Если все нормально, остается добавить собственное логирование. Для этого воспользуемся аннотацией Slf4j, и залогируем какой- либо сервис:
@Service
@RequiredArgsConstructor
@Slf4j public class UserServiceImpl implements UserService { private final UserRepository userRepository; public List
} public void saveOrUpdate(User user){ log.info("Save user {}", user); userRepository.save(user);
}
}
Теперь можно запустить и проверить, все ли залогировалось, а также открыть файл и проверить, записались ли туда логи.
Логирование не является сложным, но имеет огромное значение при отладке программ.
19.2. Задание
Создать файл logback.xml, добавить логирование во все методы классов- сервисов.
20. ПРАКТИЧЕСКАЯ РАБОТА № 20
Цель работы
Использование Spring
AOP
. Pointcut, JoinPoint. Advice.
20.1. Теоретическая часть
Нам может потребоваться часто создать такую логику, которая будет присутствовать сразу во множестве разнообразных методов – например, обернуть все методы взаимодействия к БД в транзакцию, или логировать входные параметры всех методов сервисов. Для реализации такой логики идеально подойдет Aspect Oriented Programming (AOP) – парадигма программирования для реализации сквозной логики. Мы можем одну и ту же логику применять сразу к множеству модулей в программе (рисунок 35).
Рисунок 35 – Представление Aspect Oriented Programming (AOP)
В аспектно-ориентированном программировании существует несколько основных понятий:
– Aspect – класс, реализующий сквозную функциональность;
– Advice – блок кода, который должен выполниться в каких-то определенных местах работы программы;
– Pointcut – некий предикат, который описывает ту точку кода, в которой выполняется Advice;
– JoinPoint – конкретная точка выполнения программы, в которой будет исполняться Advice. То есть, Pointcut это множество JoinPoint точек.
А теперь попробуем сделать простой аспект, который будет логировать просто входные параметры всех классов сервисов. Не забудьте добавить аннотацию EnableAspectJAutoProxy над конфигурацией.
@Slf4j
@Component
@org.aspectj.lang.annotation.Aspect public class Aspect {
@Before("allServiceMethods()") public void logParameters(JoinPoint joinPoint) { log.info("Parameters: {}", joinPoint.getArgs());
}
@Pointcut("within(ru.mirea.springcourse.service.*)") public void allServiceMethods() {}
}
Аннотация Aspect создает специальный аспект, в котором будут описываться Pointcut, Advice.
Before – аннотация, которая обозначает Advice, который будет выполняться перед выполнением некоего метода.
Есть различные аннотации, например, After, AfterThrowing, Around,
AfterReturning.
Pointcut описывает набор точек выполнения программы – методов, в которых выполняется Advice:
– within – объекты заданного типа или классов пакета или подпакетов;
– execution – по имени метода;
– this – прокси реализует заданный тип;
– bean – имеет определённый идентификатор или имя;
– annotation – помечены указанной аннотацией.
Теперь можно запустить приложение, и проверить работоспособность аспектов.
20.2. Задание
Для приложения из предыдущего задания добавить логирование времени выполнения каждого метода сервиса с использованием Spring AOP.
21. ПРАКТИЧЕСКАЯ РАБОТА № 21
Цель работы
Проксирование. Аннотация Transactional. Аннотация Async.
21.1. Теоретическая часть
В данной работе мы изучим проксирование, на котором основаны многие функциональные возможности в Spring, например, AOP, планирование заданий, асинхронность.
Логично, что проксирование основано на паттерне Прокси. Создается объект, который подменяет исходный, с дополнительной логикой. При этом проксирование может быть многоуровневое, прокси может проксировать другой прокси, так как он просто внутри использует предыдущий объект.
Есть различные имплементации проксирования: JDK-проксирование,
CGLIB. Также существует AspectJ-проксирование на этапе компиляции, но мы его не будем сейчас затрагивать. JDK имплементация основана на том, что создается объект, который имплементирует все интерфейсы класса, который имплементирует целевой объект. При таком типе проксирования можно использовать только public методы.
CGLIB имплементация основана на том, что прокси наследуется от целевого класса, и таким образом может использовать методы. В таком случае можно проксировать все методы, кроме private.
Изначально Spring пытается воспользоваться проксированием на основе интерфейсов, если не получается, то использует CGLIB. Но с версии Spring boot 2 по умолчанию используется CGLIB, что имеет большое значение, так как если мы будем использовать Spring с Kotlin, то нужно не забывать о специальном extension, который все классы делает open, иначе Spring просто не сможет проксировать.
Аннотация Transactional позволяет оборачивать метод в транзакцию, не прописывая дополнительный код. Если на простом уровне, то он просто оборачивает метод в такой код:
var transaction = session.beginTransaction(); try { invokeMethod(); transaction.commit();
} catch(Exception e){ transaction.rollback();
}
Эта аннотация уменьшает код, при этом передавая всю работу с транзакциями на Spring. Также у аннотации есть параметр readonly, который стоит ставить в true, если не производится никаких изменений в БД, а только чтение. Это убирает различные дополнительные проверки, улучшает производительность метода.
Аннотация Async делает метод асинхронным. Он исполняет метод в отдельном треде.
21.2. Задание
Для приложения из предыдущего задания пометить все классы сервисов, в которых происходит взаимодействие с базой данных, как
Transactional. Добавить отправку информации о сохранении каждого объекта по электронной почте, создав отдельный класс EmailService с асинхронными методами отправки сообщений. Для асинхронности методов используйте аннотацию Async.
} catch(Exception e){ transaction.rollback();
}
Эта аннотация уменьшает код, при этом передавая всю работу с транзакциями на Spring. Также у аннотации есть параметр readonly, который стоит ставить в true, если не производится никаких изменений в БД, а только чтение. Это убирает различные дополнительные проверки, улучшает производительность метода.
Аннотация Async делает метод асинхронным. Он исполняет метод в отдельном треде.
21.2. Задание
Для приложения из предыдущего задания пометить все классы сервисов, в которых происходит взаимодействие с базой данных, как
Transactional. Добавить отправку информации о сохранении каждого объекта по электронной почте, создав отдельный класс EmailService с асинхронными методами отправки сообщений. Для асинхронности методов используйте аннотацию Async.
22. ПРАКТИЧЕСКАЯ РАБОТА № 22
Цель работы
Планирование заданий. Scheduler в Spring.
22.1. Теоретическая часть
Иногда требуется выполнять какие-то запланированные задания, которые выполняются в определенный момент времени – например, очистку таблицы от устаревших значений, подгрузка новых значений, инвалидация кэша. Для этого используется Scheduler. Не забудьте добавить аннотацию
EnableScheduling над конфигурацией.
Теперь создадим класс
SchedulerService:
@Service public class SchedulerServiceImpl implements
SchedulerService {
@Scheduled(cron = "0 * * * * *")
@Override public void doScheduledTask() {
System.out.println("Scheduled task");
}
}
Для того, чтобы создать запланированное задание, нужна аннотация
Scheduled. Данный метод затем проксируется и запускается по расписанию. cron означает cron-значение, описывающее время запуска. В данном случае оно будет выполняться каждую минуту.
22.2. Задание
Для приложения из предыдущего задания создать класс-сервис с методом, который будет вызываться каждые 30 минут и очищать определённую директорию, а затем создавать по файлу для каждой из сущностей и загружать туда все данные из базы данных. Также добавить возможность вызывать данный метод с использованием Java Management
Extensions (JMX).
23. ПРАКТИЧЕСКАЯ РАБОТА № 23
Цель работы
Использование Spring Security для аутентификации и авторизации пользователей.
23.1. Теоретическая часть
Каждое приложение должно так или иначе поддерживать защиту от несанкционированного доступа. Для этого предоставлена прекрасная технология Spring Security.
Самым основным элементом Security является Filter Chain – цепочка фильтров. Вся безопасность реализуется через данную цепочку фильтров. По очереди каждый фильтр проверяет запрос, получает нужные данные, в случае чего блокирует дальнейшую фильтрацию. Также вместо отдельного фильтра может быть FilterChainProxy, который хранит в себе семейство иных фильтров.
Также в фильтре можно создать имплементацию интерфейса
Authentication, которую можно положить в SecurityContextHolder. Дело в том, что для каждого треда хранится отдельный экземпляр SecurityContextHolder, в котором хранится аутентификация для пользователя. Благодаря этому мы, например, можем получить Principal в контроллере.
Но это не все – есть огромное количество самых разнообразных абстракций в Spring security, однако на данный момент мы затронем самые базовые конфигурации для Security.
@Configuration public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws
Exception {
}
}