Skip to content

TheIncorrectClock/20170420-szkolenie

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Spring Framework - ćwiczenia

Ten projekt zawiera przykłady (ćwiczenia oraz rozwiązania ćwiczeń) do szkolenia "Tworzenia aplikacji internetowych z wykorzystaniem Spring Framework"

Ćwiczenia

Celem zadań będzie napisanie aplikacji która pobiera tłumaczenia słów wpisanych z konsoli oraz wyświetla je na ekranie. Wraz z rozwojem aplikacji (i poznawaniem Spring Framework) będziemy w kolejnych ćwiczeniach dodawać kolejne funkcjonalności.

To jest bazowa wersja programu; aplikacja zawiera podstawową konfigurację aplikacji w Springu oraz prosty kontroler umożliwiający podawanie komand z linii poleceń.

Zadanie 1 – serwis tłumaczący

Dodaj serwis pobierający tłumaczenia angielskiego słowa z serwisu http://www.dict.pl dostępny pod komendą: search [słowo]. Możesz bezpośrednio wykorzystać klasę TranslationService, którą należy odpowiednio rozbudowac o adnotację Spring Framework. Metoda TranslationService#getTranslationsForWord()) zwraca listę obiektów DictionaryWord (tupli) zawierających parę: słowo polskie, słowo angielskie.

Tłumaczenie mogą zostać wyświetlone na ekranie.

Zadanie 2 – testy jednostkowe

Napisany przez nas w ćwiczeniu 1 serwis należałoby przetestować, najlepiej w sposób automatyczny. Bazując na istniejącym ExampleConfigurationTests napisz nowy test dla serwisu.

Wiedząc o tym co mówi konwencja Spring Framework nt. plików konfiguracyjnych dla testów, utwórz nową konfigurację XML dla testu albo wykorzystaj wewnętrzną klasę JavaConfig.

Zadanie 2a

Test korzystający bezpośrednio z klasy TranslationService nie jest najlepszy – odwołuje się do zasobów w Internecie, przez co (w przypadku braku dostępu do Internetu nie zadziała – nie będzie działał stabilnie). Co więcej, ze względu na wykonywanie połączenia ze zdalnym serwerem, test działa nieporównywalnie dłużej niż jakby wywoływał się w całości lokalnie (albo w całości w pamięci).

Aby uniknąć takiego typu zachowania, należałoby zastąpić bezpośrednie odwołanie do Internetu, np. uniezależniając serwis TranslationService od konkretnego adresu URL (i przekazując go jako parametr, zmienną klasy – poprzez adnotację @Value).

W Spring Framework najłatwiej uzyskać to poprzez dodanie zewnętrznego pliku properties, zdefiniowanie adresu URL, a następnie odwołanie się do niego w konfiguracjach XML albo JavaConfig

<context:property-placeholder location="classpath:META-INF/spring/dict.properties"/>
@Configuration
@ComponentScan(value = { "com.example.dictionary", "com.example.helloworld" })
@PropertySource("META-INF/spring/dict.properties")
public static class AppConfiguration {

	@Bean
	public PropertySourcesPlaceholderConfigurer properties() {
		return new PropertySourcesPlaceholderConfigurer();
	}

}

Do lokalnego pliku można dostać się bezpośrednio poprzez ClassPath this.getClass().getResource("/words/book.html").toExternalForm();

Zadanie 3 – walidacja parametrów

Do serwisu TranslationService dodaj metodą sprawdzającą poprawność przekazanych parametrów. Dla serwisu wyszukującego poprawne komenda to: search [słowo do znalezienia]

Do walidacji wykorzystaj standard Bean Validation (walidacja przez adnotacje). W tym celu dodaj do projektu następujące zależności.

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-validator</artifactId>
	<version>5.2.4.Final</version>
</dependency>
Tip
walidacja bazuje na obiektach (innych niż String) więc na początek zmień parametry (komendy) na obiekt. Dodatkowo, przeprowadź refaktoryzację klasy Command i TranslationService aby logikę przetwarzania parametrów wejściowych zawierać w jednym miejscu.

Na przykład, wprowadź klasę CommandParameters o następującej strukturze, używaj jej do przekazywania parametrów CLI oraz na jej bazie przeprowadź walidację.

public class CommandParameters {
	private String commandName;
	private String[] attributes;
}

Zadanie 4 – audit logs

Odwołanie się do aplikacji zewnętrznej (w naszym przypadku do serwisu http://dict.pl) jest niezmierni ważne z biznesowego punktu widzenia. Wyobraźmy sobie sytuację że to nie jest zwykły słownik a zaawansowany Web Service, za którego wywołania pobierana jest opłata. Co miesiąc przychodzi faktura od dostawcy za wszystkie wywołania serwisu.

Aby zweryfikować taką fakturę, należy upewnić się ile razy serwis był wywoływany.

Jako że nie jest to wymaganie typowe dla naszego serwisu (można powiedzieć że jest to typowe wymaganie nie funkcjonalne) zaimplementuj je w sposób nie zmieniający głównego algorytmu działania serwisu TranslationService

Do implementacji logowania użyj komponentów Spring AOP

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
</dependency>

Zaloguj wywołanie publicznej metody serwisu, zaloguj informację o wywołaniu wraz z przekazywanym parametrem.

Zadanie 5 – zapisywanie wyników

Dotychczas zupełnie pomijaliśmy kwestie zwracanych wyników – wyświetlaliśmy je na ekranie. Pora to zmienić.

Zacznijmy od wyświetlenia wszystkich słów na ekranie, w formie listy, a następnie wprowadźmy komendę zapisującą konkretne tłumaczenia: save [numer wyników z listy]

Zapisane tłumaczenie póki co przechowujmy w dowolnym miejscu w systemie (np. jako listy w kontrolerze).

Tip
aby poprawnie obsługiwać listy należy przechować wyniki wyszukiwania oraz osobno listę zapisanych słów. Co powoduje konieczność implementacji kolejnych komend poza save: show-saved oraz show-found

Zadanie 5a - repozytorium

„Pamięć” zaimplementowana w poprzednim ćwiczeniu jest rozwiązaniem stosunkowo naiwnym. Wszystkie dane przechowujemy w kontrolerze co czyni go grubym (antywzorzec Fat Controller). Aby to naprawić utwórzmy dodatkowy komponent obsługujący przechowywanie danych.

public interface Repository {

	public List<DictionaryWord> getSavedWords();

	public void addWord(DictionaryWord word);

	public void printSavedWords();
}

Zadanie 6 – zapis słów do bazy danych

Nasze repozytorium przechowuje dane w pamięci. Nic nie stoi na przeszkodzie abyśmy zaczęli zapisywać je do bazy danych. W tym celu utwórzmy tabelę words

create table words (
	id int identity primary key,
	polish_word varchar(100),
	english_word varchar(100)
);

Do zapisywania danych użyjmy klasy JdbcTemplate

Tip
Konfiguracja bazy danych wymaga dodania sterownika HSQLDB, MySQL lub PostreSQL.
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.3.2</version>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>5.1.26</version>
</dependency>
<dependency>
	<groupId>postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>9.1-901.jdbc3</version>
</dependency>

Dodatkowo należy dodać bibliotekę Spring odpowiedzialną za połączenie za bazą danych.

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
</dependency>

Przydatne informację dot. połączenia

driver = com.mysql.jdbc.Driver
url = "jdbc:hsqldb:file:/tmp/testdb" //(1)
url = "jdbc:hsqldb:mem:testmemdb" //(2)
  1. Baza w pliku

  2. Baza w pamięci

Zadanie 6a – wykorzystanie mappera obiektowego

Pobierając dane z bazy danych możemy skorzystać z kolejnych dobrodziejstw Spring Framework, klasy RowMapper, umożliwiającej automatyczne mapowanie kolejnych elementów ResultSet na obiekt (w naszym przypadku DictionaryWord).

Zadanie 7 – wykorzystanie JPA

Bardzo często spotykanym sposobem połączenia z bazą danych jest wykorzystanie standardu JPA. W przypadku naszej prostej aplikacji jest to niezwykle proste, wszakże posługujemy się już obiektem domenowym DictionaryWord który moglibyśmy zapisać bezpośrednio w bazie danych.

Zmodyfikuj klasę DictionaryWord oraz dodaj nowe repozytorium JpaRepository – w ten sposób korzystając z JPA do zapisu danych do bazy

Przydatne biblioteki do dołączenia do projektu:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>5.2.6.Final</version>
</dependency>

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.6.4</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-log4j12</artifactId>
	<version>1.6.4</version>
</dependency>

Zadanie 8 – transakcje

Nowym wymaganiem w naszym systemie, jest zapisywanie tłumaczeń także do pliku. Przed zapisem do bazy danych należy parę słów zapisać także do pliku o losowej nazwie. Dopiero w kolejnym kroku można zapisać dane w bazie. Jeżeli transakcja w bazie danych się nie powiedzie, należy usunąć uprzedni utworzony plik.

Plik może znajdować się w katalogu tymczasowym a jego nazwa może być dowolnie generowana, np.:

public String createFile(String data) {
    UUID id = UUID.randomUUID();
    String filename = "/tmp/wordfile-" + id.toString();

    try (PrintWriter out = new PrintWriter(filename)) {
        out.println(data);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    log.info("Saved file: "  + filename);
    return filename;
}

Wykorzystując klasy Spring TransactionSynchronisationManager oraz interfejs TransactionSynchronisation zaimplementuj poprawną obsługę transakcji i błędów.

Zadanie 9 - Spring WebMVC

Kolejnym krokiem będzie rozbudowa aplikacji o cześć serwerową - dodanie usługi www, umożliwiającej wykonanie tych samych operacji poprzez webservice REST.

Webservice ma udostępniać następujące metody

GET /show-saved 		(1)
GET /search/{word}		(2)
POST /search/{word}/{n}		(3)
  1. Wyświetlenie wszystkich zapisanych słóœ

  2. Wyświetlenie tłumaczeń dla słowa {word}

  3. Zapisanie wybranego tłumaczenia słow {word}, będącego {n}-tym elementem listy

Aby ułatwić sobie pracę, wykorzystajmy już istniejącą aplikację (projekt Spring). W tym celu najlepiej utworzyć nowy projekt zawierający następujące zależności

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
		http://maven.apache.org/maven-v4_0_0.xsd"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>web</artifactId>
	<version>1.0-SNAPSHOT</version>
	<packaging>war</packaging>

	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<failOnMissingWebXml>false</failOnMissingWebXml>
	</properties>

	<dependencyManagement>
	    <dependencies>
	        <dependency>
	            <groupId>io.spring.platform</groupId>
	            <artifactId>platform-bom</artifactId>
	            <version>2.0.0.RELEASE</version>
	            <type>pom</type>
	            <scope>import</scope>
	        </dependency>
	    </dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
		    <groupId>com.example</groupId>
		    <artifactId>app</artifactId>		(1)
		    <version>1.0-SNAPSHOT</version>
		</dependency>

		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-webmvc</artifactId>
		</dependency>
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-core</artifactId>
		    <version>2.5.0</version>
		</dependency>
		<dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>2.5.0</version>
		</dependency>

		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>javax.servlet-api</artifactId>
		    <version>3.0.1</version>
		    <scope>provided</scope>
		</dependency>
	</dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>
</project>
  1. Zależność od bazowego projektu

Samą aplikację możemy skonfigurować zarówno poprzez plik web.xml jak i poprzez adnotacje i JavaConfig

DispatcherConfig.java
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.example.web")
public class DispatcherConfig {

}
WebInitializer.java
public class WebInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext =
                new AnnotationConfigWebApplicationContext();
        rootContext.register(AppJavaConfig.AppConfiguration.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext =
                new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(DispatcherConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher =
                container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*");
    }

}

Do pełni działającej aplikacji potrzeba już jedynie odpowiedniej konfiguracji kontrolerów.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • HTML 40.1%
  • JavaScript 31.5%
  • CSS 24.6%
  • Java 3.8%