Методичка java. Методичка По Шаблонам Java. Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия
Скачать 1.94 Mb.
|
18.2. Задание Переписать код предыдущего задания с использованием сервисов и отделения логики контроллера от логики сервиса и репозитория. В программе всё взаимодействие с базой данных должно быть реализовано через репозитории 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 } 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. 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 { } } Класс, наследующийся от WebSecurityConfigurerAdapter, является фактически входной точкой со всей конфигурацией. Для начала запретим все эндпоинты, кроме /login, /logout для неавторизованных пользователей, а также отключим Cross Origin Resource Sharing (CORS) и Cross Site Request Forgery (CSRF). Они сейчас не имеют значение, но могут усложнить изучение Spring Security: @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().cors().disable() .authorizeRequests().antMatchers("/login", "/logout").permitAll() .anyRequest().authenticated(); } } Теперь хотелось бы добавить форму логина, а также какой-то источник логинов и паролей пользователей: @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().cors().disable() .authorizeRequests().antMatchers("/login", "/logout").permitAll() .anyRequest().authenticated() .and().formLogin() .and().userDetailsService(userDetailsService()); } public UserDetailsService userDetailsService() { InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); userDetailsService.createUser(new User("user", "password", List.of(new SimpleGrantedAuthority("ROLE_USER")))); return userDetailsService; } @Bean PasswordEncoder encoder() { return NoOpPasswordEncoder.getInstance(); } } PasswordEncoder – класс, который отвечает за кодирование паролей. В данном случае используется NoOpPasswordEncoder, но никогда его не стоит использовать в реальном проекте – он никак не шифрует пароли. В данном случае он используется просто для простоты. А теперь запустим проект и попробуем перейти на защищенный эндпоинт. Нас отправит на /login (рисунок 36). Рисунок 36 – Форма авторизации Попробуем ввести наш логин «user» и пароль «password». После логина перейдем на защищенный эндпоинт, и увидим информацию. Также нам хотелось бы еще где-то хранить сессию. Для этого используется Spring Session, который активируется добавлением одной зависимости – spring session jdbc. 23.2. Задание В приложении из предыдущего задания добавить возможность регистрации и авторизации пользователей, хранение cookie сессий в базе данных PostgreSQL, хеширование паролей алгоритмом Bcrypt, защиту всех запросов, кроме запросов на авторизацию и регистрацию, от неавторизированных пользователей. 24. ПРАКТИЧЕСКАЯ РАБОТА № 24 Цель работы Тестирование в Spring Framework с использованием Junit. 24.1. Теоретическая часть В данном задании мы обратим внимание на модульное тестирование – такой вид тестирования, в котором тестируется отдельный модуль, класс, в изоляции от остальных, на правильное функционирование. Модульное тестирование важно не на стадии создания данных тестов, а при дальнейшем развитии кодовой базы, когда тесты не дадут сломаться тому, что уже работает. Для написания модульных тестов будем использовать библиотеку JUnit, которая тоже прекрасно интегрирована с Spring. Также нам потребуется создание mock объектов – реализаций некоторых объектов-пустышек, не выполняющих логики, либо симулирующие выполнение какой-то логики. Также мы можем определять, что они будут возвращать, для удобства тестирования. Мы будем тестировать UserService из предыдущего задания. Для начала создадим класс в папке test UserServiceImplTest, и добавим два метода – getUsers, saveOrUpdate, и пометим аннотацией Test. class UserServiceImplTest { @Test void getUsers() { } @Test void saveOrUpdate() { } } Уже сейчас мы можем запустить, и тесты успешно пройдут. Но нам нужно все же проверить какую-то логику. Но для начала нам нужно сделать mock объект интерфейса UserRepository. Для этого добавим аннотацию ExtendWith для активации Mockito, а также добавим свойство UserRepository с аннотацией Mock: @ExtendWith(MockitoExtension.class) class UserServiceImplTest { @Mock private UserRepository userRepository; @Test void getUsers() { } @Test void saveOrUpdate() { } } Теперь уже добавим логику в тест getUsers: @ExtendWith(MockitoExtension.class) class UserServiceImplTest { @Mock private UserRepository userRepository; @Test void getUsers() { User user = new User(); user.setFirstName("Vasya"); User user2 = new User(); user2.setFirstName("Dima"); Mockito.when(userRepository.findAll()).thenReturn(List.of(user, user2)); UserService userService = new UserServiceImpl(userRepository); Assertions.assertEquals(2, userService.getUsers().size()); Assertions.assertEquals("Vasya", userService.getUsers().get(0).getFirstName()); } @Test void saveOrUpdate() { } } Assertions отвечает за проверку данных – являются ли они теми, которые мы ожидаем от программы, или нет. Теперь добавим логику для метода saveOrUpdate: @ExtendWith(MockitoExtension.class) class UserServiceImplTest { @Mock private UserRepository userRepository; @Captor ArgumentCaptor @Test void getUsers() { User user = new User(); user.setFirstName("Vasya"); User user2 = new User(); user2.setFirstName("Dima"); Mockito.when(userRepository.findAll()).thenReturn(List.of(user, user2)); UserService userService = new UserServiceImpl(userRepository); Assertions.assertEquals(2, userService.getUsers().size()); Assertions.assertEquals("Vasya", userService.getUsers().get(0).getFirstName()); } @Test void saveOrUpdate() { User user = new User(); user.setFirstName("Vitya"); UserService userService = new UserServiceImpl(userRepository); userService.saveOrUpdate(user); Mockito.verify(userRepository).save(captor.capture()); User captured = captor.getValue(); assertEquals("Vitya", captured.getFirstName()); } } ArgumentCaptror отвечает за перехват аргументов. Нам нужно узнать, поменялся ли как-то объект User в процессе выполнения метода UserService, поэтому нам нужно перехватить аргумент и проверить его. Тестирование также важно, как и сама разработка, ведь любой код в первую очередь пишется для того, чтобы его кто-то использовал – как приложение, как библиотеку, не важно. Код всегда должен выполнять бизнес- требования, иначе и само программирование становится бесполезным. 24.2. Задание Написать модульное тестирование для всех классов сервисов приложения из предыдущего задания. 25. ОПИСАНИЕ ВЫПОЛНЕНИЯ РАБОТ 25.1. Последовательность выполнения практической работы Для выполнения практических работ необходимо: 1) на рабочем месте, где будут выполняться практические работы, установить следующие средства разработки: а) OpenJDK версии 11 и выше; б) InteliJ IDEA Community Edition (желательно Ultimate Edition, можно получить по студенческой лицензии на сайте Jetbrains); в) Docker; 2) создать пустой проект в InteliJ IDEA; 3) создать пустой класс, содержащий статический main метод; 4) запустить созданную программу и убедиться в работоспособности. 25.2. Порядок выполнения индивидуального задания Индивидуальное задание должно быть оформлено в отдельном проекте. Для проверки работоспособности выполненного индивидуального задания следует использовать отдельный класс с методом main. 26. ЗАЩИТА ПРАКТИЧЕСКИХ РАБОТ 26.1. Результат практической работы Результатом практической работы является: 1) рабочий проект, выполненный в соответствии с заданием практической работы; 2) отчет, содержащий все этапы выполненной работы, оформленный по примеру. 26.2. Этапы защиты практической работы Этапы защиты практической работы: 1) демонстрация рабочего проекта, выполненного в соответствии с заданием; 2) ответы на дополнительные вопросы по рабочему проекту (студент должен владеть теоретической базой, свободно читать и комментировать строки листинга программы, уметь формулировать выводы о проделанной работе); 3) отчет по практической работе предоставляется в электронном виде. ПЕРЕЧЕНЬ СОКРАЩЕНИЙ AOP – Aspect Oriented Programming – API – Application programming Interface – CORS – Cross Origin Resource Sharing – CSRF – Cross Site Request Forgery – DI – Dependency Injection – DSL – Domain specific language – DTO – Data Transfer Object – HQL – Hibernate Query Language – HTTP – HyperText Transfer Protocol – IoC – Inversion of Control – JDK – Java Development Kit – JMX – Java Management Extensions – JPA – Java Persistence API – JVM – Java Virtual Machine – MVC – Model-View-Controller – ORM – Object Relational Mapping – SQL – Structured query language – xml – eXtensible Markup Language – БД – база данных – БИБЛИОГРАФИЧЕСКИЙ СПИСОК 1. Стелтинг С., Маасен О. Применение шаблонов Java. Библиотека профессионала.: Пер. с англ. — М.: Издательский дом "Вильяме", 2002. — 576 с.: ил. — Парал. тит. англ. 2. Functional Interfaces in Java: Fundamentals and Examples 1st ed. Edition, Kindle Edition [Электронный ресурс]. URL: https://www.amazon.com/Functional-Interfaces-Java-Fundamentals-Examples- ebook/dp/B07NRHQSCW (дата обращения: 29.01.21). Заголовок с экрана. 3. Hibernate Search 6.0.0.Final: Reference Documentation [Электронный ресурс]. URL: https://docs.jboss.org/hibernate/stable/search/reference/en- US/html_single/ (дата обращения: 29.01.21). Заголовок с экрана. 4. Паттерны проектирования на Java. Каталог Java-примеров. [Электронный ресурс]. URL: https://refactoring.guru/ru/design-patterns/java (дата обращения: 29.01.21). Заголовок с экрана. 5. Руководство по Spring [Электронный ресурс]. URL: https://proselyte.net/tutorials/spring-tutorial-full-version/ (дата обращения: 29.01.21). Заголовок с экрана. 6. The Reactive Manifesto [Электронный ресурс]. URL: https://www.reactivemanifesto.org/ (дата обращения: 29.01.21). Заголовок с экрана. 7. Spring Framework Documentation [Электронный ресурс]. URL: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html (дата обращения: 29.01.21). Заголовок с экрана. 8. Hibernate Search 6.0.0. Final: Reference Documentation [Электронный ресурс]. URL: https://docs.jboss.org/hibernate/stable/search/reference/en- US/html_single/ (дата обращения: 29.01.21). Заголовок с экрана. |