diff --git a/examples/README.md b/examples/README.md index 77699970..47719ffe 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,9 +4,11 @@ * *unit-testing-plain-spring* - Spring * *unit-testing-spring-boot* - , Spring Boot +* *unit-testing-pure-spring-parallel* - " " "" IOService +* *ioservice-replacing-example* - UI -> Swing UI, IOService * *hibernate-fetch-mode-demo* - Hibernate, N+1 * *mongo-db-demo* - MongoDB, MongoEventListener, Mongock * *docker-test-containers* - TestContainers * *spring-cloud-demo-stvort* - Config server, Eureka, Zuul, Feign client * *spring-mail-integration-demo* - SpringMail SpringIntegration -* *liquibase-demo* - liquibase \ No newline at end of file +* *liquibase-demo* - liquibase diff --git a/examples/ioservice-replacing-example/.gitignore b/examples/ioservice-replacing-example/.gitignore new file mode 100644 index 00000000..c456c4a3 --- /dev/null +++ b/examples/ioservice-replacing-example/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ diff --git a/examples/ioservice-replacing-example/README.md b/examples/ioservice-replacing-example/README.md new file mode 100644 index 00000000..e624c807 --- /dev/null +++ b/examples/ioservice-replacing-example/README.md @@ -0,0 +1,7 @@ +## Пример подмены консольного UI -> Swing UI, с помощью смены реализации IOService + +* *В примере есть класс проводящий опрос PollService и использующий для этого IOService* +* *В зависимости от настройки use.swing в application.yml создаются либо бины из пакета console, либо бины из пакета swing* +* *Т.к. один из этих бинов это реализация IOService, то получается либо консольный, либо оконный интерфейс программы без семы кода класса PollService* +* *Оконный интерфейс представлен классом PollMainForm, который взаимодействует с основной частью программы, через очереди в сервисе MessageSystem* +* *Через них же (с помощью IOService) основная часть программы взаимодействует с оконным интерфейсом* diff --git a/examples/ioservice-replacing-example/pom.xml b/examples/ioservice-replacing-example/pom.xml new file mode 100644 index 00000000..e98fb711 --- /dev/null +++ b/examples/ioservice-replacing-example/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.1.RELEASE + + + + ru.otus + ioservice-replacing-example + 0.0.1-SNAPSHOT + ioservice-replacing-example + IOService replacing example + + + 11 + 11 + 11 + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + + + org.mockito + mockito-junit-jupiter + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/IOServiceExampleSpringBootApplication.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/IOServiceExampleSpringBootApplication.java new file mode 100644 index 00000000..05a57288 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/IOServiceExampleSpringBootApplication.java @@ -0,0 +1,13 @@ +package ru.otus.ioservice.example; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; + + +@SpringBootApplication +public class IOServiceExampleSpringBootApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(IOServiceExampleSpringBootApplication.class).headless(false).run(args); + } +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/api/IOService.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/api/IOService.java new file mode 100644 index 00000000..18572cea --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/api/IOService.java @@ -0,0 +1,6 @@ +package ru.otus.ioservice.example.api; + +public interface IOService { + void out(String message); + String readString(); +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/ConsoleIOService.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/ConsoleIOService.java new file mode 100644 index 00000000..bdf68124 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/ConsoleIOService.java @@ -0,0 +1,31 @@ +package ru.otus.ioservice.example.console; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import ru.otus.ioservice.example.api.IOService; + +import java.io.PrintStream; +import java.util.Scanner; + +@ConditionalOnProperty(name = "use.swing", havingValue = "false") +@Service +public class ConsoleIOService implements IOService { + private final PrintStream out; + private final Scanner sc; + + + public ConsoleIOService() { + this.out = System.out; + this.sc = new Scanner(System.in); + } + + @Override + public void out(String message) { + out.println(message); + } + + @Override + public String readString() { + return sc.nextLine(); + } +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/UIConfig.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/UIConfig.java new file mode 100644 index 00000000..32c1f367 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/console/UIConfig.java @@ -0,0 +1,18 @@ +package ru.otus.ioservice.example.console; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.otus.ioservice.example.poll.PollService; + +@ConditionalOnProperty(name = "use.swing", havingValue = "false") +@Configuration +public class UIConfig { + + @Bean + public CommandLineRunner starter (PollService pollService) { + return args -> pollService.poll(); + } + +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/poll/PollService.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/poll/PollService.java new file mode 100644 index 00000000..35382eb0 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/poll/PollService.java @@ -0,0 +1,29 @@ +package ru.otus.ioservice.example.poll; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import ru.otus.ioservice.example.api.IOService; + +@RequiredArgsConstructor +@Service +public class PollService { + + private final IOService ioService; + + public void poll() { + System.out.println("Началось!"); + + ioService.out("Как вас зовут?"); + String name = ioService.readString(); + ioService.out(String.format("Приятно познакомиться: %s", name)); + ioService.out("Как ваши дела? Если хорошо, введите \"OK\""); + String res = ioService.readString(); + if (res.equalsIgnoreCase("OK")) { + ioService.out("Это радует"); + } else { + ioService.out("Ничего, скоро все наладится!"); + } + ioService.out(String.format("До свидания %s", name)); + } + +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/MessageSystem.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/MessageSystem.java new file mode 100644 index 00000000..9152c1c4 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/MessageSystem.java @@ -0,0 +1,38 @@ +package ru.otus.ioservice.example.swing; + +import lombok.Data; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; + +import java.util.concurrent.LinkedBlockingQueue; + +@Service +public class MessageSystem { + private final LinkedBlockingQueue pollQueue; + private final LinkedBlockingQueue uiQueue; + + public MessageSystem() { + pollQueue = new LinkedBlockingQueue<>(); + uiQueue = new LinkedBlockingQueue<>(); + } + + @SneakyThrows + public void putToPollQueue(String message) { + pollQueue.put(message); + } + + @SneakyThrows + public void putToUiQueue(String message) { + uiQueue.put(message); + } + + @SneakyThrows + public String takeFromPollQueue() { + return pollQueue.take(); + } + + @SneakyThrows + public String takeFromUiQueue() { + return uiQueue.take(); + } +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/PollMainForm.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/PollMainForm.java new file mode 100644 index 00000000..521797f0 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/PollMainForm.java @@ -0,0 +1,86 @@ +package ru.otus.ioservice.example.swing; + +import lombok.RequiredArgsConstructor; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +@RequiredArgsConstructor +public class PollMainForm extends JFrame { + + private final JPanel contentPane; + private final JLabel inLabel; + private final JTextField outTextField; + private final JButton outBtn; + + private final MessageSystem ms; + private final AtomicBoolean runFlag; + private final ExecutorService uiFlow; + + public PollMainForm(MessageSystem ms) throws HeadlessException { + super("Poll"); + this.ms = ms; + + runFlag = new AtomicBoolean(true); + uiFlow = Executors.newSingleThreadExecutor(); + + GridLayout layout = new GridLayout(3, 1); + contentPane = new JPanel(layout); + setContentPane(contentPane); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + inLabel = new JLabel(""); + contentPane.add(inLabel); + + outTextField = new JTextField(15); + contentPane.add(outTextField); + + outBtn = new JButton("Ответить"); + outBtn.addActionListener(e -> ms.putToPollQueue(outTextField.getText())); + contentPane.add(outBtn); + setOnCloseHandler(); + setSize(450, 100); + } + + void init() { + setLocationRelativeTo(null); + setVisible(true); + startUiFlow(); + } + + private void startUiFlow() { + uiFlow.execute(() -> { + while (runFlag.get()) { + try { + String msg = ms.takeFromUiQueue(); + synchronized (inLabel) { + inLabel.setText(msg); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + return; + } + } + + }); + } + + private void setOnCloseHandler() { + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + runFlag.set(false); + ms.putToUiQueue("exit"); + uiFlow.shutdown(); + e.getWindow().dispose(); + } + }); + } + + +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/SwingIOService.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/SwingIOService.java new file mode 100644 index 00000000..324f8b97 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/SwingIOService.java @@ -0,0 +1,26 @@ +package ru.otus.ioservice.example.swing; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import ru.otus.ioservice.example.api.IOService; + +@ConditionalOnProperty(name = "use.swing", havingValue = "true") +@RequiredArgsConstructor +@Service +public class SwingIOService implements IOService { + private final MessageSystem ms; + + @SneakyThrows + @Override + public void out(String message) { + ms.putToUiQueue(message); + } + + @SneakyThrows + @Override + public String readString() { + return ms.takeFromPollQueue(); + } +} diff --git a/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/UIConfig.java b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/UIConfig.java new file mode 100644 index 00000000..5eaf2d78 --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/java/ru/otus/ioservice/example/swing/UIConfig.java @@ -0,0 +1,30 @@ +package ru.otus.ioservice.example.swing; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import ru.otus.ioservice.example.poll.PollService; + +import java.awt.*; + +@ConditionalOnProperty(name = "use.swing", havingValue = "true") +@Configuration +public class UIConfig { + + @Bean + public CommandLineRunner starter(PollService pollService, MessageSystem ms) { + return args -> { + EventQueue.invokeLater(() -> { + try { + PollMainForm mainForm = new PollMainForm(ms); + mainForm.init(); + } catch (Exception e) { + e.printStackTrace(); + } + }); + pollService.poll(); + }; + } + +} diff --git a/examples/ioservice-replacing-example/src/main/resources/application.yml b/examples/ioservice-replacing-example/src/main/resources/application.yml new file mode 100644 index 00000000..efc3321a --- /dev/null +++ b/examples/ioservice-replacing-example/src/main/resources/application.yml @@ -0,0 +1 @@ +use.swing: false \ No newline at end of file diff --git a/examples/unit-testing-pure-spring-parallel/README.md b/examples/unit-testing-pure-spring-parallel/README.md new file mode 100644 index 00000000..ac7fe2b4 --- /dev/null +++ b/examples/unit-testing-pure-spring-parallel/README.md @@ -0,0 +1,7 @@ +## Пример "не удачного" и "работающего" варианта тестирования IOService в нескольких потоках + +В примере демонстрируется: +* *возможность выполнения тестов в нескольких потоках (включается в junit-platform.properties\junit.jupiter.execution.parallel.enabled)* +* *частый подход к тестированию сервиса консольного ввода/вывода, через подмену System.in/out (ClosedIOServiceTest)* +* *проблемы возникающие при включении многопоточного режима исполнения тестов* +* *вариант решения проблем с многопоточным исполнением тестов, через получение потоков ввода/вывода в конструкторе сервиса (OpenedIOServiceTest)*