Файл: Практикум для студентов, обучающихся по направлению подготовки 09. 03. 04 Программная инженерия.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 30.11.2023

Просмотров: 178

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Рисунок 31 – Пример build.gradle, часть 7
Результат выполнения задачи:
Рисунок 32 – Пример build.gradle, часть 8
Gradle – простой и невероятно удобный инструмент для сборки приложений, и обязательно стоит научиться им пользоваться, познать его силу и удобство.
9.2. Задание
Создать приложение, которое выводит какое-то сообщение в консоль.
Создать Gradle Task, который создает jar-файл приложения, переносит его в отдельную папку, в которой хранится Dockerfile для jar, а затем создает Docker контейнер из данного jar-файла и запускает его.

10. ПРАКТИЧЕСКАЯ РАБОТА № 10
Цель работы
Введение в Spring. Container. Bean. Внедрение зависимостей, основанных на конструкторах и сеттерах. Конфигурация бинов.
Автоматическое обнаружение и связывание классов.
10.1. Теоретическая часть
В прошлом разрабатываемое приложение практически не обновлялось.
Например, игры на телефоны – они выходили один раз, и никто не создавал различные дополнения, патчи для них. В те времена задумываться о поддержке разрабатываемого приложения не имело смысла, но сейчас поддержка кода имеет чрезвычайно огромное значение. Чем больше кода, тем сложнее его поддерживать, с каждой строкой кода растет технический долг приложения.
Технический долг приложения – значение стоимости внедрения какого- то дополнительного функционала в существующий проект. Чем больше, сложнее система, тем сложнее добавлять новые фичи, и может доходить до такого, что добавление простейшей логики станет невыполнимой задачей просто из-за объема текущей логики. Поэтому требуется создавать такой код, который будет приносить минимальное количество технического долга.
Нужно писать такой код, в котором отдельные элементы будут максимально независимы для упрощения изменения отдельной части логики. В этом и помогает нам инверсия контроля (IoC).
Инверсия контроля – принцип, при котором управление отдельными элементами программы передается отдельному контейнеру. Лучшая фраза, что описывает инверсию контроля, это «Don’t call us, we call you». Отдельный элемент программы, в нашем случае объект, не выбирает, какой объект он будет использовать в своей логике – за это отвечает некий контейнер, который и предоставляет нужный объект.

Наиболее известной реализацией инверсии контроля является инъекция
зависимостей (Dependency Injection (DI)) – шаблон, в котором контейнер производит инъекцию требуемых зависимостей (объектов) в свойства другого объекта, которому требуются некие зависимости. Допустим, у нас есть класс человека «Костя», которому нужны штаны, и есть интерфейс штанов и несколько имплементаций. У «Кости» есть метод walk, в котором он выводит в консоль класс штанов, которые надел. Но мы не хотим, чтобы «Костя» сам выбирал, какие штаны ему носить. Нам нужен некий посредник, который определит, какие штаны носить «Косте», создаст нужный объект и произведет инъекцию объекта штанов «Косте»: class Kostya { private Trousers trousers; public void walk() {
System.out.println("I'm wearing " + trousers.getName());
} public void setTrousers(Trousers trousers) { this.trousers = trousers;
}
} interface Trousers {
String getName();
} class Joggers implements Trousers{
@Override public String getName() { return "Joggers!";
}
} class Pantaloons implements Trousers {
@Override public String getName() { return "Pantaloons....";
}


}
И здесь нам пригодится как раз некий контекст, который и произведет инъекцию зависимостей. Здесь на сцену и выходит Spring, точнее, Spring Core с IoC.
Bean (бин)
В Spring есть понятие Bean (бин). Bean – это объект, зависимостями и жизненным циклом которого управляет Spring. У Spring существует некий контекст – ApplicationContext, который содержит множество бинов, которые мы хотим использовать. Он управляет ими и производит инъекцию зависимостей там, где требуется.
Но нам нужно как-то объяснить контексту, какие бины мы хотим создать. Для этого нам нужно написать некую конфигурацию с разными бинами. Далее будет приведен пример с Java конфигурацией, так как на данный момент она является стандартом де-факто практически везде, но также конфигурацию можно писать и на Groovy, и при помощи xml файла, и с использованием Kotlin DSL.
Опишем нашу конфигурацию и решим, что «Костя» будет носить джоггеры:
@Configuration public class BeanConfig {
@Bean public Trousers trousers() { return new Joggers();
}
@Bean public Kostya kostya(Trousers trousers) {
Kostya kostya = new Kostya(); kostya.setTrousers(trousers); return kostya;
}

Аннотация Configuration показывает, что данный класс является конфигурацией для бинов. Аннотация Bean означает, что то, что возвращает данный метод, является бином.
Также можно определить область видимости (scope) бина. Существует много видов областей видимости, сейчас мы ограничимся лишь двумя
(singleton и prototype):
1) singleton – создается только один бин и используется для всех остальных. Да, вам не нужно вручную писать логику для того, чтобы класс был синглтоном, об этом позаботится Spring;
2) prototype – может иметь любое количество инстансов. Создается каждый раз новый бин.
Определить область видимости можно при помощи аннотации Scope.
Вот пример:
@Bean
@Scope("prototype") public Trousers trousers() { return new Joggers();
}
Но по умолчанию создается синглтон, что нам и требуется. Поэтому нам не нужно нигде использовать аннотацию Scope.
Теперь попробуем получить бин «Кости» и вызвать его метод walk:
ApplicationContext context = new
AnnotationConfigApplicationContext(BeanConfig.class);
Kostya kostya = context.getBean(Kostya.class); kostya.walk();
И вывод:
I’m wearing Joggers!
Process finished with exit code 0
Контекст увидел, что мы создали джоггеры, и сам произвел инъекцию их в бин. Но каждый раз для каждого бина писать где-то в конфигурации метод неудобно, gоэтому у Spring существует такое понятие, как

ComponentScan. Context сам пробегается по пакету, ищет классы, помеченные специальными аннотациями, и создает их бины. Изменим код нашего приложения для автоматического сканирования. Для начала уберем бины из конфигурации и добавим аннотацию:
@Configuration
@ComponentScan public class BeanConfig {
}
Теперь нам нужно как-то пометить нужные классы, чтоб создались их бины. Для этого можно использовать аннотацию Component. Поставим аннотацию над классом «Панталон» и «Костя». Но нам требуется также как- то сообщить, что мы хотим добавить зависимость. Контекст же должен как-то понять, нужно ли в данное свойство производить инъекцию. Для этого используется аннотация Autowired. Но где ее ставить: над сеттером, над свойством, над конструктором, может вообще над классом?
Существуют различные способы инъекции, рассмотрим каждый из них:
1) через свойство (field injection) – ставим аннотацию над свойством и производится инъекция. Не особо рекомендуется для использования, хотя вполне можно так делать;
2) через сеттер – аннотация над сеттером; данный сеттер вызывается для инъекции бина.
Пример внедрении зависимости через сеттер:
@Component public class Kostya { private Trousers trousers; public void walk() {
System.out.println("I'm wearing " + trousers.getName());
}
@Autowired public void setTrousers(Trousers trousers) { this.trousers = trousers;


}
}
3) через конструктор – наиболее удобный способ инъекции, можно даже не писать аннотацию, достаточно лишь создать конструктор с параметром, который нужно получить.
Пример через конструктор:
@Component public class Kostya { private final Trousers trousers; public Kostya(Trousers trousers) { this.trousers = trousers;
} public void walk() {
System.out.println("I'm wearing " + trousers.getName());
}
}
Также добавим аннотацию для панталон и запустим наш код:
@Component public class Pantaloons implements Trousers {
@Override public String getName() { return "Pantaloons....";
}
}
Вывод:
I’m wearing Pantaloons...
Process finished with exit code 0
Для того, чтобы лучше понять Spring, рекомендуется сайт baeldung.com, а также официальная документация Spring.

10.2. Задание
Создать приложение, в котором создается ApplicationContext и из него берётся бин с названием, переданным в качестве аргумента к приложению, и вызывается метод интерфейса, который он имплементирует. Нужно создать по одному бину для каждого класса, определить им название. Проверить, что вызывается при вводе названия каждого из бинов. Классы и интерфейс определяются в соответствии с вариантом индивидуального задания.
10.3. Варианты индивидуального задания
1) Интерфейс Knight с методом void fight(), его имплементации:
StrongKnight, WeakKnight, KingOfKnights.
2) Интерфейс Magican с методом doMagic(), его имплементации:
Voldemort, HarryPotter, RonWeesly.
3) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.
4) Интерфейс Lighter с методом doLight(), его имплементации: Lamp,
Flashlight, Firefly.
5) Интерфейс Musician с методом doCoding(), его имплементации:
Drummer, guitarist, trombonist.
6) Интерфейс Fighter с методом doFight(), его имплементации:
StreetFighter, Boxer, Judoka.
7) Интерфейс Politician с методом doPolitic(), его имплементации:
Trump,Biden, Merkel.
8) Интерфейс SortingAlgorithm с методом doSort(), его имплементации:
MergeSort, InsertionSort, QuickSort.
9) Интерфейс Printer с методом doPrint(), его имплементации:
ConsolePrinter, FilePrinter.
10) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.

11) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.
12) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.
13) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.
14) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.
15) Интерфейс Programmer с методом doCoding(), его имплементации:
Junior, Middle, Senior.

11. ПРАКТИЧЕСКАЯ РАБОТА № 11
Цель работы
Разобраться с использованием Spring boot.
11.1. Теоретическая часть
У нас есть прекрасные системы сборки приложений, но все равно возникают проблемы с версиями зависимостей. Нужно постоянно следить за версиями, часто возникают проблемы с тем, что одна библиотека зависит от другой с устаревшей версией, а вы используете более новую. Также очень неудобно в Spring постоянно писать один и тот же код, чтобы просто запустить программу с контекстом. Хотелось бы все это автоматизировать. И хотелось бы сделать такие библиотеки, чтобы можно было добавить зависимость на одну интеграцию от Spring, и сразу же можно было ее использовать, так еще и чтобы была конфигурация по умолчанию. И хочется еще, чтобы по одному файлу с настройками поднимались все нужные бины, и можно было сразу писать бизнес-логику. И самое удивительное – такая технология есть. Это Spring Boot. Она позволяет просто скачать проект, добавить нужные зависимости, и сразу писать код для бизнес-логики.
11.2. Задание
Создать приложение с использованием Spring Boot Starter Initializr
(https://start.spring.io/) с такими зависимостями:
– Spring Web;
– Lombok;
– Validation;
– Spring boot Actuator.
Запустить приложение и удостовериться, что не появилось никаких ошибок. Добавить все эндпоинты в Actuator, сделать HTTP-запрос на проверку состояния приложения. Собрать jar-файл приложения, запустить и проверить состояние при помощи REST-запроса.


12. ПРАКТИЧЕСКАЯ РАБОТА № 12
Цель работы
Работа с жизненным циклом компонентов. Аннотации PostConstruct,
PreDestroy.
12.1. Теоретическая часть
Для использования аннотаций PostConstruct, PreDestroy требуется
Spring boot. Для того, чтобы создать консольное приложение в Spring boot, воспользуйтесь CommandLineRunner.
Контекст также контролирует жизненный цикл бина (рисунок 33).
Рисунок 33 – Жизненный цикл бина
Иногда может понадобиться произвести какие-то действия не в конструкторе, так как при вызове конструктора могут не быть инициализированы все бины (в случае если у нас есть field injection или setter injection), а когда бин уже полностью подготовлен. Для этого и используется аннотация PostConstruct.
Для логики при уничтожении бина используется аннотация PreDestroy.
Пример кода с аннотациями:
@Component public class PostConstructSample {
@PostConstruct public void init() {
System.out.println("Bean is ready");
}
}

12.2. Задание
Создать приложение, которое при запуске берет данные из одного файла, хеширует, а при остановке приложения удаляет исходный файл, оставляя только файл с захешированными данными. Названия первого и второго файла передаются в качестве аргументов при запуске. При отсутствии первого файла создает второй файл и записывает в него строку null.
Реализовать с использованием аннотаций PostConstruct, PreDestroy.

13. ПРАКТИЧЕСКАЯ РАБОТА № 13
Цель работы
Конфигурирование приложения. Environment.
13.1. Теоретическая часть
У приложения может быть достаточно много настроек. Самым банальным примером является строка подключения к базе данных. Не хочется каждый раз лезть в код, когда запускаешь приложение в другой среде – локальной, тестовой, продакшн. И Spring предоставляет невероятно удобный способ конфигурирования приложений. Вся конфигурация находится в одном файле в папке resources – по умолчанию это application.properties, но часто используется application.yml, так как yml формат более читаем. В нем мы можем определять какие-то свойства, которые можем получить в дальнейшем в приложении. Например, добавим в application.yml имя пользователя: program: user: name: User
А теперь попробуем получить эту проперти в классе User
@Component public class User {
@Value("${program.user.name}") private String name;
@PostConstruct public void init() {
System.out.println(name);
}
}
Запускаем программу, и видим, что наше значение из файла вывелось.


13.2. Задание
Создать файл application.yml в папке resources, добавить в него такие свойства:
– student.name – имя студента;
– student.last_name – фамилия студента;
– student.group – название группы студента.
При запуске приложения выведите данные свойства в консоль при помощи интерфейса Environment или аннотации Value.

14. ПРАКТИЧЕСКАЯ РАБОТА № 14
Цель работы
Знакомство со Spring MVC. Работа с Rest API в Spring.
14.1. Теоретическая часть
Как мы знаем, основная задача сервера – принимать запросы, обрабатывать их и возвращать некий ответ. Чаще всего для этого используется протокол HTTP. Но протокол HTTP не описывает принципов по построению запроса, он является лишь стандартом взаимодействия. Требуется какой-то принцип, удобный, понятный и который легко можно отлаживать. Для этого существует REST API, с которым я советую ознакомиться самостоятельно, ибо информации о нем чрезвычайно много. Я лишь акцентирую внимание контроллеры, созданные для работы с REST API, а также Spring MVC (Model-
View-Controller).
Входной точкой для любого запроса является контроллер – класс, аннотированный аннотацией Controller или RestController. Метод, помеченный аннотацией RequestMapping, GetMapping, PostMapping,
PutMapping является одной из входных точек для HTTP запроса. Попробуем сделать простой контроллер, который будет возвращать строку.
@Controller public class SimpleController {
@GetMapping("/hello") public @ResponseBody String hello() { return "Hello";
}
}
Аннотация ResponseBody значит, что то, что вернет метод, будет преобразовано в тело ответа. Главное отличие RestController от Controller в том, что RestController является по сути объединением двух аннотаций –
Controller и ResponseBody. Просто, но эффектно и удобно. Тогда возникает вопрос – зачем нужна просто аннотация Controller? Мы же почти всегда хотим
какой-то ответ на запрос. Но в некоторых случаях нам нужно вернуть страницу – статический html или же сгенерированный шаблонизатором. Для этого можно использовать просто контроллер, возвращающий строку. Spring попытается найти какой-то документ по названию, совпадающему со строкой, которую вы вернули. По поводу того, что может возвращать метод контроллера, лучше почитать в документации
Spring
(https://docs.spring.io/spring-framework/docs/current/reference/html/web.html).
Допустим, нам нужно получить тело запроса в формате json, и преобразовать в объект, а затем где-то использовать. Эта проблема решается одной аннотацией и одной настройкой.
@Controller public class SimpleController {
@GetMapping(value = "/hello", consumes =
MediaType.APPLICATION_JSON_VALUE) public @ResponseBody String hello(@RequestBody User user) { return "Hello";
}
} consumes определяет, какой вид в теле запроса потребляет метод.
GetMapping означает, что он принимает GET запрос.
Контроллеры в Spring – чрезвычайно обширная тема, но очень полезная, они сильно упрощают разработку.
1   2   3   4   5