Skip to content

13. [Cours]: Spring ile bir veritabanını web üzerinde sunma MVC

Anahtar kelimeler: çok katmanlı mimari, Spring, bağımlılık enjeksiyonu, web hizmeti / jSON, istemci / sunucu

13.1. Support

 

Bu bölümdeki projeler [support / chap-13] klasöründe bulunur. SQL [dbintrospringdata.sql] komut dosyası, testler için gerekli olan MySQL temelini oluşturmaya olanak tanır.

13.2. Spring MVC'in bir web uygulamasındaki yeri

Spring MVC'in bir web uygulaması geliştirme sürecindeki yerini belirleyelim. Çoğu zaman, bu uygulama aşağıdaki gibi çok katmanlı bir mimari üzerine inşa edilir:

  • [Web] katmanı, web uygulamasının kullanıcısıyla temas halinde olan katmandır. Kullanıcı, bir tarayıcı aracılığıyla görüntülenen web sayfaları vasıtasıyla web uygulamasıyla etkileşime girer. Spring MVC bu katmanda yer alır ve yalnızca bu katmanda bulunur;
  • [métier] katmanı, maaş veya fatura hesaplaması gibi uygulama yönetim kurallarını uygular. Bu katman, [Web] katmanı aracılığıyla kullanıcıdan gelen verileri ve SGBD katmanından [DAO] katmanı aracılığıyla gelen verileri kullanır;
  • [DAO] katmanı (Veri Erişim Nesneleri), [ORM] katmanı (Nesne-İlişkisel Eşleştirici) ve JDBC sürücüsü, SGBD'teki verilere erişimi yönetir. [ORM] katmanı, [DAO] katmanı tarafından işlenen nesneler ile ilişkisel bir veritabanındaki tabloların satırları ve sütunları arasında bir köprü görevi görür. JPA (Java Persistence API) spesifikasyonu, kullanılan ORM katmanının bu spesifikasyonları uyguladığı durumlarda, bu katmandan soyutlaşmaya olanak tanır. Bu durumda, bundan sonra ORM katmanını JPA katmanı olarak adlandıracağız;
  • katmanların entegrasyonu Spring çerçeve yapısı tarafından gerçekleştirilir;

13.3. Spring MVC geliştirme modeli

Spring MVC, MVC olarak adlandırılan mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygular:

Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşir:

  1. istek - istenen URL'ler http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... biçimindedir. [Front Controller], isteği doğru denetleyiciye ve bu denetleyici içindeki doğru eyleme "yönlendirmek" için bir yapılandırma dosyası veya Java açıklamaları kullanır. Bunun için, URL'in [Action] alanını kullanır. URL ile [/param1/param2/...] arasındaki kısım, eyleme iletilecek isteğe bağlı parametrelerden oluşur. Burada MVC'teki C, [Front Controller, Contrôleur, Action] dizesidir. İstenen eylemi işleyebilecek hiçbir denetleyici yoksa, web sunucusu istenen URL'in bulunamadığını bildirir.
  2. İşleme
    • Seçilen eylem, [Front Controller] tarafından kendisine iletilen parami parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
    • URL'in [/param1/param2/...] yolundan,
    • URL'in [p1=v1&p2=v2] parametrelerinden,
    • tarayıcı tarafından istekle birlikte gönderilen parametrelerden;
    • kullanıcının isteğinin işlenmesi sırasında, eylem [métier] ve [2b] katmanlarına ihtiyaç duyabilir. Müşterinin isteği işlendikten sonra, bu işlem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
    • istek düzgün bir şekilde işlenemediğinde bir hata sayfası
    • aksi takdirde bir onay sayfası
    • eylem, belirli bir görünümün görüntülenmesini ister: [3]. Bu görünüm, görünüm modeli olarak adlandırılan verileri gösterecektir. Bu, MVC'teki M'dir. Eylem, bu M şablonunu [2c] oluşturacak ve bir V görünümünün görüntülenmesini isteyecektir [3];
  3. yanıt - seçilen V görünümü, eylemin oluşturduğu M şablonunu kullanarak, müşteriye göndermesi gereken HTML yanıtının dinamik kısımlarını başlatır ve ardından bu yanıtı gönderir.

Bir web hizmeti / jSON için, önceki mimari biraz değiştirilmiştir:

  • [4a]'te, bir Java sınıfı olan şablon, bir kütüphane tarafından jSON dizesine dönüştürülür;
  • [4b]'te, bu jSON dizesi tarayıcıya gönderilir;

Şimdi, MVC web mimarisi ile katmanlı mimari arasındaki bağlantıyı açıklayalım. Modele verilen tanıma göre, bu iki kavram birbiriyle ilişkili olabilir ya da olmayabilir. Tek katmanlı bir Spring web uygulaması olan MVC’i ele alalım:

[Web] katmanını Spring MVC ile uygularsak, bir web mimarisine sahip oluruz, ancak bu çok katmanlı bir mimari değildir. Burada, [web] katmanı her şeyi üstlenecektir: sunum, iş mantığı, veri erişimi. Bu işleri gerçekleştirecek olanlar eylemlerdir.

Şimdi, çok katmanlı bir web mimarisini ele alalım:

[Web] katmanı, herhangi bir çerçeve kullanmadan ve MVC modelini takip etmeden uygulanabilir. Bu durumda gerçekten çok katmanlı bir mimariye sahip oluruz, ancak web katmanı MVC modelini uygulamaz.

Örneğin, .NET ortamında yukarıdaki [Web] katmanıyukarıdaki [Web] katmanı, ASP.NET ve MVC ile uygulanabilir ve böylece MVC türünde bir [Web] katmanına sahip katmanlı bir mimari elde edilir. Bu işlem tamamlandıktan sonra, bu ASP.NET ve MVC katmanını, geri kalan (iş mantığı, DAO, ORM) aynı şekilde koruyarak değiştirebiliriz. Böylece, artık MVC türü olmayan bir [Web] katmanına sahip katmanlı bir mimari elde ederiz.

MVC'te, M modelinin V görünümü olan c.a.d'in modeli olduğunu belirtmiştik. Bu, V görünümü tarafından görüntülenen verilerin tamamıdır. MVC'in M modeline ilişkin başka bir tanım da şöyledir:

Birçok yazar, [Web] katmanının sağındaki kısmın, MVC'in M modelini oluşturduğunu düşünmektedir. Belirsizlikleri önlemek için şunlardan söz edilebilir:

  • [Web] katmanının sağındaki her şeyi ifade ederken alan modelinden
  • bir V görünümü tarafından görüntülenen verileri ifade ederken "görünüm modeli"

Bundan sonra, "M modeli" terimi yalnızca bir V görünümünün modelini ifade edecektir.

13.4. Spring ile bir web projesi / jSON

[http://spring.io/guides] sitesi, Spring ekosistemini keşfetmek için başlangıç kılavuzları sunmaktadır. Bir Spring projesi MVC için gerekli Maven yapılandırmasını öğrenmek üzere bu kılavuzlardan birini takip edeceğiz.

13.4.1. Demo projesi

  • [1]'te, Spring kılavuzlarından birini içe aktarıyoruz;
  • [2]'te, [Rest Service] örneğini seçiyoruz;
  • [3]'te, Maven projesini seçiyoruz;
  • [4]'te kılavuzun son sürümünü seçiyoruz;
  • [5]'te onaylıyoruz;
  • [6]'te, içe aktarılan proje;

Standart URL aracılığıyla erişilebilen ve jSON metni sağlayan web hizmetleri genellikle REST (REpresentational State Transfer) hizmetleri olarak adlandırılır. Bir hizmet, belirli kurallara uyuyorsa Restful olarak adlandırılır.

Şimdi içe aktarılan projeyi, öncelikle Maven yapılandırmasını inceleyelim.

13.4.2. Maven Yapılandırması

[pom.xml] dosyası şu şekildedir:


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

    <groupId>org.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <start-class>hello.Application</start-class>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
  • 6-8. satırlar: Maven projesinin özellikleri. Maven derlemesi sonucunda üretilen dosyanın türünü belirten [<packaging>] etiketi eksik. Bu etiket olmadığında, [jar] türü kullanılır. Dolayısıyla uygulama, konsol türü bir yürütülebilir uygulamadır; paketleme türü [war] olacak bir web uygulaması değildir;
  • 10-14. satırlar: Maven projesinin [spring-boot-starter-parent] adlı bir üst projesi vardır. Projenin bağımlılıklarının büyük kısmını bu üst proje tanımlar. Bu bağımlılıklar yeterli olabilir; bu durumda başka bağımlılık eklenmez, ya da yeterli olmayabilir; bu durumda eksik olan bağımlılıklar eklenir;
  • satır 17-20: [spring-boot-starter-web] artefaktı, oluşturulmuş görünümlerin bulunmadığı bir web hizmeti türü Spring projesi olan MVC için gerekli kütüphaneleri beraberinde getirir. Bu artefakt, gömülü bir Tomcat sunucusuna ait olanlar da dahil olmak üzere çok sayıda kütüphaneyi beraberinde getirir. Uygulama bu sunucu üzerinde çalıştırılacaktır;

Bu yapılandırma ile birlikte gelen kütüphanelerin sayısı oldukça fazladır:

Yukarıda Tomcat sunucusuna ait üç arşiv dosyası görülüyor.

13.4.3. Bir Spring hizmetinin mimarisi [web / jSON]

Bir web hizmeti / jSON için Spring MVC, MVC modelini şu şekilde uygular:

  • [4a]'te, bir Java sınıfı olan model, jSON kütüphanesi tarafından jSON dizesine dönüştürülür;
  • [4b]'e dönüştürüldüğünde, bu jSON dizesi tarayıcıya gönderilir;

13.4.4. C denetleyicisi

  

İçe aktarılan uygulama aşağıdaki denetleyiciye sahiptir:


package hello;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping("/greeting")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}
  • 9. satır: [@RestController] anotasyonu, [GreetingController] sınıfını bir Spring denetleyicisi haline getirir; yani, bu sınıfın yöntemleri URL'leri işlemek üzere kaydedilir. Benzer bir anotasyon olan [@Controller]'i daha önce görmüştük. Bu denetleyicinin yöntemlerinin sonucu, görüntülenecek görünümün adı olan [String] türündeydi. Burada durum farklıdır. [@RestController] türündeki bir denetleyicinin yöntemleri, tarayıcıya gönderilmek üzere serileştirilmiş nesneler döndürür. Uygulanan serileştirme türü, Spring MVC yapılandırmasına bağlıdır. Burada, nesneler jSON olarak serileştirilecektir. Projenin bağımlılıkları arasında bir jSON kütüphanesinin bulunması, Spring Boot'un otomatik yapılandırma yoluyla projeyi bu şekilde yapılandırmasına neden olur;
  • 14. satır: [@RequestMapping] anotasyonu, yöntemin işlediği URL'i belirtir; burada bu, URL ve [/greeting]'tir;
  • 15. satır: [@RequestParam] etiketini daha önce açıklamıştık. Yöntemin döndürdüğü sonuç, [Greeting] türünde bir nesnedir.
  • 12. satır: atomik türde bir uzun tamsayı. Bu, erişim eşzamanlılığını desteklediği anlamına gelir. Birden fazla iş parçacığı, [counter] değişkenini aynı anda artırmak isteyebilir. Bu işlem düzgün bir şekilde gerçekleştirilecektir. Bir iş parçacığı, sayacın değerini ancak onu değiştiren iş parçacığı değişikliğini tamamladıktan sonra okuyabilir.

13.4.5. M Modeli

Önceki yöntemle üretilen M modeli, aşağıdaki [Greeting] nesnesidir:

  

package hello;

public class Greeting {

    private final long id;
    private final String content;

    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }

    public long getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

Bu nesnenin jSON dönüşümü, {"id":n,"content":"metin"} karakter dizisini oluşturacaktır. Sonuç olarak, denetleyici yöntemi tarafından üretilen jSON dizisi şu şekilde olacaktır:

{"id":2,"content":"Hello, World!"}

veya

{"id":2,"content":"Hello, John!"}

13.4.6. Çalıştırma

  

[Application.java] sınıfı, projenin yürütülebilir sınıfıdır. Kodu şöyledir:


package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Bu kodu önceki örnekte daha önce görmüş ve açıklamıştık.

13.4.7. Projenin çalıştırılması

Projeyi çalıştıralım:

 

Aşağıdaki konsol günlükleri elde edilir:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.9.RELEASE)

2014-11-28 15:22:55.005  INFO 3152 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 3152 (started by ST in D:\data\istia-1415\spring mvc\dvp-final\gs-rest-service)
2014-11-28 15:22:55.046  INFO 3152 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@62e136d3: startup date [Fri Nov 28 15:22:55 CET 2014]; root of context hierarchy
2014-11-28 15:22:55.762  INFO 3152 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-11-28 15:22:56.567  INFO 3152 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-11-28 15:22:56.738  INFO 3152 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2014-11-28 15:22:56.740  INFO 3152 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.56
2014-11-28 15:22:56.869  INFO 3152 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-11-28 15:22:56.870  INFO 3152 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1827 ms
2014-11-28 15:22:57.478  INFO 3152 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-11-28 15:22:57.481  INFO 3152 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-11-28 15:22:57.685  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.879  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public hello.Greeting hello.GreetingController.greeting(java.lang.String)
2014-11-28 15:22:57.884  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.885  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.906  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] türündeki işleyiciye
2014-11-28 15:22:57.907  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] türündeki işleyiciye
2014-11-28 15:22:58.231  INFO 3152 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-11-28 15:22:58.318  INFO 3152 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-11-28 15:22:58.319  INFO 3152 --- [           main] hello.Application                        : Started Application in 3.788 seconds (JVM running for 4.424)
  • 13. satır: Tomcat sunucusu 8080 numaralı bağlantı noktasında başlatılır (12. satır);
  • 17. satır: [DispatcherServlet] servleti mevcut;
  • 20. satır: [GreetingController.greeting] yöntemi tespit edildi;

Web uygulamasını test etmek için URL ve [http://localhost:8080/greeting]'e erişilir:

 

Beklenen jSON dizesi başarıyla alınır. Sunucu tarafından gönderilen HTTP başlıklarını incelemek ilginç olabilir. Bunun için, [Advanced Rest Client] adlı Chrome uzantısını kullanacağız (Chrome / Ctrl-T / [Applications] Menüsü / [Advanced Rest Client] - bkz. Ekler, paragraf 22.5):

  • [1]'te, istenen URL;
  • [2]'te, GET yöntemi kullanılmaktadır;
  • [3]'te, yanıt jSON;
  • [4]'te, sunucu jSON biçiminde bir yanıt gönderdiğini belirtmiştir;
  • [5]'te, aynı URL isteniyor, ancak bu sefer POST ile;
  • [7]'te, bilgiler sunucuya [urlencoded] biçiminde gönderilir;
  • [6]'te, name parametresi ve değeri;
  • [8]'te, tarayıcı sunucuya [urlencoded] bilgilerini gönderdiğini bildirir;
  • [9]'te, sunucunun jSON yanıtı;

13.4.8. Çalıştırılabilir bir arşiv oluşturma

Şimdi bir yürütülebilir arşiv oluşturuyoruz:

  • [1]'te: bir Maven hedefi çalıştırıyoruz;
  • [2]'te: iki hedef (goal) vardır: [clean], Maven projesinden [target] klasörünü silmek için; [package] ise bu klasörü yeniden oluşturmak içindir;
  • [3]'te: Oluşturulan [target] klasörü bu klasörde oluşturulacaktır;
  • [4]'te: hedef oluşturulur;

Konsolda görüntülenen günlüklerde, [spring-boot-maven-plugin] eklentisinin göründüğünden emin olun. Yürütülebilir arşivi oluşturan bu eklentidir.

[INFO] --- spring-boot-maven-plugin:1.1.0.RELEASE:repackage (default) @ gs-rest-service ---

Bir konsol kullanarak oluşturulan klasöre girin:


D:\Temp\wksSTS\gs-rest-service\target>dir
 ...
11/06/2014  15:30    <DIR>          classes
11/06/2014  15:30    <DIR>          generated-sources
11/06/2014  15:30        11 073 572 gs-rest-service-0.1.0.jar
11/06/2014  15:30             3 690 gs-rest-service-0.1.0.jar.original
11/06/2014  15:30    <DIR>          maven-archiver
11/06/2014  15:30    <DIR>          maven-status
...
  • 5. satır: oluşturulan arşiv;

Bu arşiv şu şekilde çalıştırılır:


D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.0.RELEASE)

2014-06-11 15:32:47.088  INFO 4972 --- [           main] hello.Application
                  : Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...

Artık web uygulaması başlatıldığına göre, bir tarayıcıyla erişilebilir:

 

13.4.9. Uygulamayı bir Tomcat sunucusuna dağıtma

Önceki projede olduğu gibi, [pom.xml] dosyasını şu şekilde değiştiriyoruz:


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

    <groupId>org.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>
    <packaging>war</packaging>

    ...
</project>
  • 9. satır: bir war arşivi (Web ARchive) oluşturulacağını belirtmek gerekir;

Ayrıca web uygulamasını da yapılandırmamız gerekiyor. [web.xml] dosyası bulunmadığından, bu işlem [SpringBootServletInitializer] sınıfından miras alan bir sınıfla gerçekleştirilir:

  

[ApplicationInitializer] sınıfı şu şekildedir:


package hello;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

public class ApplicationInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

}
  • 6. satır: [ApplicationInitializer] sınıfı, [SpringBootServletInitializer] sınıfını genişletir;
  • 9. satır: [configure] yöntemi yeniden tanımlanmıştır (8. satır);
  • 10. satır: Projeyi yapılandıran sınıf belirtilir;

Projeyi çalıştırmak için şu adımları uygulayabilirsiniz:

  • [1-2]'te, projeyi Eclipse'te kayıtlı sunuculardan birinde çalıştırın;

Bu işlem tamamlandıktan sonra, bir tarayıcıda URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] adresini girebilirsiniz:

 

13.4.10. Sonuç

Web uygulamasının tarayıcıya bir akış gönderdiği bir tür Spring projesi tanıttık. Şimdi, [Introduction à Spring Data] eğitiminde incelediğimiz [dbintrospringdata] veritabanını web üzerinde sunmak için bir web uygulaması / jSON geliştireceğiz.

13.5. [dbintrospringdata] veritabanını web üzerinde yayınlama

13.5.1. Web hizmeti mimarisi / jSON

Aşağıdaki mimariyi kuracağız:

[DAO] ve [JPA] katmanları, [Introduction à Spring Data] eğitiminde yazılan uygulama tarafından uygulanmaktadır.

13.5.2. Veritabanının kurulumu

  

SQL ve [dbintrospringdata.sql] komut dosyaları, testler için gerekli olan MySQL veritabanını oluşturmaya olanak tanır.

13.5.3. Web hizmeti Eclipse projesi / jSON

Web hizmeti / jSON'in Eclipse projesi şu şekildedir:

  

Bu bir Maven projesidir ve [pom.xml] dosyası şu şekildedir:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.webjson</groupId>
    <artifactId>intro-server-webjson01</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>intro-server-webjson01</name>
    <description>démo spring mvc</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>istia.st.springdata</groupId>
            <artifactId>intro-spring-data-01</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 11-15. satırlar: [DAO] katmanı için halihazırda kullanılan üst Maven projesi;
  • 18-22. satırlar: [DAO] katmanına olan bağımlılık;
  • 23-26. satırlar: [spring-boot-starter-web] artefaktına olan bağımlılık. Bu artefakt, bir web hizmeti / jSON oluşturmak için gerekli tüm bağımlılıkları beraberinde getirir. Aynı zamanda gereksiz kütüphaneleri de beraberinde getirir. Bu nedenle daha ayrıntılı bir yapılandırma gerekli olacaktır. Ancak bu yapılandırma başlangıç için kullanışlıdır;
  • 28-30. satırlar: [spring-boot-starter] artefaktına olan bağımlılık, Spring Boot anotasyonlarının yönetilmesini sağlar;

Bu yapılandırmanın getirdiği bağımlılıklar şunlardır:

  • [1]'te, Eclipse'in [intro-spring-data-01] proje arşivine olan bağımlılığı algıladığı görülmektedir;

Yukarıdaki bağımlılıklar hem [DAO] katmanına hem de [web] katmanına aittir.

13.5.3.1. [web] katmanının yapılandırılması

[web] katmanı, [AppConfig] dosyası ile yapılandırılır:

  

[WebConfig] sınıfı, [web] katmanını yapılandırır:


package spring.webjson.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    // -------------------------------- [web] katmanı yapılandırması
    @Autowired
    private ApplicationContext context;

    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
        return servlet;
    }

    @Bean
    public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new ServletRegistrationBean(dispatcherServlet, "/*");
    }

    @Bean
    public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
        return new TomcatEmbeddedServletContainerFactory("", 8080);
    }

    // filtreler jSON
    @Bean(name = "jsonMapper")
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

    @Bean(name = "jsonMapperCategorieWithProduits")
    public ObjectMapper jsonMapperCategorieWithProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithCategorie")
    public ObjectMapper jsonMapperProduitWithCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperCategorieWithoutProduits")
    public ObjectMapper jsonMapperCategorieWithoutProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithoutCategorie")
    public ObjectMapper jsonMapperProduitWithoutCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }
}
  • 18. satır: [@EnableWebMvc] anotasyonu, Spring MVC çerçeve için otomatik yapılandırmalar oluşturur;
  • 19. satır: [WebConfig] sınıfı, Spring [WebMvcConfigurerAdapter] sınıfını genişleterek bazı bean'leri yeniden tanımlar (26-40. satırlar);
  • 22-23. satırlar: Spring bağlamının enjeksiyonu;
  • satır 25-29: Spring çerçevesinin MVC servlet'inin tanımı; bu servlet, HTTP isteklerini doğru denetleyiciye ve doğru yönteme yönlendirir. [DispatcherServlet] bir Spring sınıfıdır;
  • satır 31-34: Bu servletin tüm URL isteklerini işlediği belirtilir;
  • 36-39. satırlar: Proje arşivlerinde bulunan Tomcat sunucusunu etkinleştirecek olan bu bean'dir. Sunucu, 8080 numaralı bağlantı noktasında istekleri bekleyecektir;
  • satır 42-91: jSON filtrelerini yönetmek için kullanılacak bean'ler;
  • 42-45. satırlar: filtre içermeyen bir jSON eşleştirici;
  • satır 47-57: Bir kategoriyi ürünleriyle birlikte görüntülemeyi sağlayan jSON eşleştiricisi. Bir kategoriyi ürünleriyle birlikte talep edildiğinde, hem [Categorie] sınıfının jSON filtresini hem de [Produit] sınıfının jSON filtresini yapılandırmak gerektiğine dikkat edilmelidir. Bu durum her zaman böyledir. Bir sınıfı jSON'te serileştirirken/deserileştirirken, sınıfın jSON filtresini ve bu sınıfa dahil edilecek tüm bağımlılıkların filtrelerini yapılandırmak gerekir;
  • 59-69. satırlar: Bir ürünü kendi kategorisiyle birlikte elde etmeyi sağlayan jSON eşleştiricisi;
  • 71-80. satırlar: Ürünler olmadan bir kategoriyi görüntülemeyi sağlayan jSON eşleştiricisi;
  • satır 82-91: bir ürünü kategorisi olmadan görüntülemeyi sağlayan jSON eşleştiricisi;

[AppConfig] sınıfı, uygulamanın tamamını, yani [web] ve [DAO] katmanlarını yapılandırır:


package spring.webjson.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

import spring.data.config.DaoConfig;

@ComponentScan(basePackages = { "spring.webjson" })
@Import({ DaoConfig.class, WebConfig.class})
public class AppConfig {

}
  • 9. satır: [DAO] katmanındaki ve [web] katmanındaki bean'ler içe aktarılır;
  • 8. satır: diğer Spring bean'lerinin hangi paketlerde bulunduğunu belirtir;

Hiçbir yerde [@EnableAutoConfiguration] anotasyonunu kullanmadığımızı belirtmek isteriz. Yapılandırmayı kendimiz kontrol etmeyi tercih ettik.

13.5.4. Uygulama modeli

  

[ApplicationModel] sınıfı şu şekildedir:


package spring.webjson.models;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;

@Component
public class ApplicationModel implements IDao {

    // katman [DAO]
    @Autowired
    private IDao dao;

    @Override
    public void addProduits(List<Produit> produits) {
        dao.addProduits(produits);
    }

    @Override
    public void deleteAllProduits() {
        dao.deleteAllProduits();
    }

    @Override
    public void updateProduits(List<Produit> produits) {
        dao.updateProduits(produits);
    }

    @Override
    public List<Produit> getAllProduits() {
        return dao.getAllProduits();
    }

    @Override
    public void addCategories(List<Categorie> categories) {
        dao.addCategories(categories);
    }

    @Override
    public void deleteAllCategories() {
        dao.deleteAllCategories();
    }

    @Override
    public void updateCategories(List<Categorie> categories) {
        dao.updateCategories(categories);
    }

    @Override
    public List<Categorie> getAllCategories() {
        return dao.getAllCategories();
    }

    @Override
    public Produit getProduitByIdWithCategorie(Long idProduit) {
        return dao.getProduitByIdWithCategorie(idProduit);
    }

    @Override
    public Produit getProduitByNameWithCategorie(String nom) {
        return dao.getProduitByNameWithCategorie(nom);
    }

    @Override
    public Categorie getCategorieByIdWithProduits(Long idCategorie) {
        return dao.getCategorieByIdWithProduits(idCategorie);
    }

    @Override
    public Categorie getCategorieByNameWithProduits(String nom) {
        return dao.getCategorieByNameWithProduits(nom);
    }

    @Override
    public Produit getProduitByIdWithoutCategorie(Long idProduit) {
        return dao.getProduitByIdWithoutCategorie(idProduit);
    }

    @Override
    public Categorie getCategorieByIdWithoutProduits(Long idCategorie) {
        return dao.getCategorieByIdWithoutProduits(idCategorie);
    }

    @Override
    public Produit getProduitByNameWithoutCategorie(String nom) {        
        return dao.getProduitByNameWithoutCategorie(nom);
    }

    @Override
    public Categorie getCategorieByNameWithoutProduits(String nom) {
        return dao.getCategorieByNameWithoutProduits(nom);
    }

}
  • 12. satır: sınıf, bir Spring singleton'udur;
  • 13. satır: [DAO] katmanının [IDao] arayüzünü uygular;
  • 16-17. satırlar: [DAO] katmanına bir referans enjeksiyonu;
  • satır 19-99: [IDao] arayüzünün uygulanması;

Web katmanının mimarisi şu şekilde gelişmektedir:

  • [2b]'te, kontrolörlerin yöntemleri [ApplicationModel] tekil nesnesiyle iletişim kurar;

Bu strateji, olası bir önbelleğin yönetimi konusunda esneklik sağlar. [ApplicationModel] sınıfı, [DAO] katmanından elde edilen bilgileri veya yapılandırma verilerini depolamak için kullanılabilir. Bu, [DAO] katmanının kontrolünün elimizde olmadığı durumlarda yararlı olabilir. Bu önbellek stratejisi zaman içinde değişebilir. Yapılacak değişiklikler, denetleyici(ler)in kodunu hiçbir şekilde etkilemeyecektir.

Image

13.5.5. denetleyicisi

 
  

Burada tek bir denetleyici var: [MyController] sınıfı.

13.5.5.1. Bu denetleyici tarafından sunulan URL'ler

Bu denetleyici tarafından sunulan URL sınıfları şunlardır:


    @RequestMapping(value = "/addProduits",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String addProduits(HttpServletRequest request) {
...
    }
Veritabanına ürünler eklenir. Bu ürünler yayınlanır. Yanıt, birincil anahtarlarıyla birlikte eklenen ürünlerin listesini içeren jSON dizesidir.

    @RequestMapping(value = "/deleteAllProduits",
method = RequestMethod.GET)
    public String deleteAllProduits() {
..
    }
Veritabanındaki tüm ürünleri siler.

    @RequestMapping(value = "/updateProduits",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String updateProduits(HttpServletRequest request) {
..
    }

Veritabanındaki ürünleri günceller. Bu ürünler yayınlanır. Yanıt, güncellenen ürün listesinden alınan jSON dizesidir.

    @RequestMapping(value = "/getAllProduits",
method = RequestMethod.GET)
    public String getAllProduits() {
..
    }

Tüm ürünlerden jSON dizesini alır.

    @RequestMapping(value = "/addCategories",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String addCategories(HttpServletRequest request) {
..
    }

Veritabanına kategoriler ekler. Bunlar yayınlanır. Yanıt, birincil anahtarlarıyla birlikte eklenen kategorilerin listesini içeren jSON dizesidir. Kategoriler ürün içeriyorsa, bunlar da veritabanına eklenir.

    @RequestMapping(value = "/deleteAllCategories",
method = RequestMethod.GET)
    public String deleteAllCategories() {
...
    }

Veritabanındaki tüm kategorileri ve bunlara ait tüm ürünleri siler. İşlem tamamlandığında veritabanı boş kalır.

    @RequestMapping(value = "/updateCategories",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String updateCategories
(HttpServletRequest request) {
...
    }

Veritabanındaki kategorileri günceller. Bunlar gönderilir. Yanıt, güncellenen kategorilerin listesidir. Kategoriler ürün içeriyorsa, bunlar da veritabanında güncellenir. Değiştirilen kategorilerin jSON dizesini döndürür;

    @RequestMapping(value = "/getAllCategories",
method = RequestMethod.GET)
    public String getAllCategories() {
...
    }

Tüm kategorilerden jSON dizesini alır.

    @RequestMapping(value = "/getProduitByIdWithCategorie/{idProduit}",
method = RequestMethod.GET)
    public String getProduitByIdWithCategorie
(@PathVariable("idProduit") Long idProduit) {
...
    }

ID'siyle belirtilen bir ürünün, kategorisi ile birlikte jSON dizesini alır.

    @RequestMapping(value = "/getProduitByIdWithoutCategorie/{idProduit}",
method = RequestMethod.GET)
    public String getProduitByIdWithoutCategorie
@PathVariable("idProduit") Long idProduit) {
...
    }

ID'siyle belirtilen bir ürünün, kategorisi hariç olmak üzere jSON dizesini alır.

    @RequestMapping(value = "/getProduitByNameWithCategorie/{nom}",
method = RequestMethod.GET)
    public String getProduitByNameWithCategorie(
@PathVariable("nom") String nom) {
...
    }

Adıyla belirtilen bir ürünün, kategorisi ile birlikte jSON dizesini alır.

    @RequestMapping(value = "/getProduitByNameWithoutCategorie/{nom}",
method = RequestMethod.GET)
    public String getProduitByNameWithoutCategorie
(@PathVariable("nom") String nom) {
...
    }

Kategorisiz, adıyla belirtilen bir üründen jSON dizesini alır.

    @RequestMapping(value = "/getCategorieByIdWithProduits/{idCategorie}",
 method = RequestMethod.GET)
    public String getCategorieByIdWithProduits
@PathVariable("idCategorie") Long idCategorie) {
...
    }

ID'siyle belirtilen bir kategoriden, o kategorideki ürünlerle birlikte jSON dizesini alır.

    @RequestMapping(value = "/getCategorieByNameWithProduits/{nom}",
method = RequestMethod.GET)
    public String getCategorieByNameWithProduits
(@PathVariable("nom") String nom) {
...
    }

Adıyla belirtilen bir kategoriden, ürünleriyle birlikte jSON dizesini alır.

    @RequestMapping(value = "/getCategorieByNameWithoutProduits/{nom}",
method = RequestMethod.GET)
    public String getCategorieByNameWithoutProduits(
@PathVariable("nom") String nom) {
...
    }

Adıyla belirtilen bir kategoriden, ürünleri hariç olmak üzere jSON dizesini alır.

    @RequestMapping(value = "/getCategorieByIdWithoutProduits/{idCategorie}",
method = RequestMethod.GET)
    public String getCategorieByIdWithoutProduits(
@PathVariable("idCategorie") Long idCategorie) {
...
    }

ID'siyle belirtilen bir kategoriden, ürünleri hariç olmak üzere jSON dizesini alır.

Gösterilen URL kodları, [IDao] arayüzünün [DAO] katmanındaki yöntemlere karşılık gelir. /jSON web hizmetinin yöntemlerinin tümü aynı şablona göre oluşturulmuştur. Bunlardan birkaçını inceleyeceğiz.

13.5.5.2. Denetleyicinin iskeleti

Denetleyicinin iskeleti şu şekildedir:


package spring.webjson.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;

import spring.data.dao.DaoException;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
import spring.webjson.models.ApplicationModel;
import spring.webjson.models.Response;

@Controller
public class MyController {

    // Spring bağımlılıkları
    @Autowired
    private ApplicationModel application;

    // filtreler jSON
    @Autowired
    @Qualifier("jsonMapper")
    private ObjectMapper jsonMapper;
    @Autowired
    @Qualifier("jsonMapperCategorieWithProduits")
    private ObjectMapper jsonMapperCategorieWithProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithCategorie")
    private ObjectMapper jsonMapperProduitWithCategorie;
    @Autowired
    @Qualifier("jsonMapperCategorieWithoutProduits")
    private ObjectMapper jsonMapperCategorieWithoutProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithoutCategorie")
    private ObjectMapper jsonMapperProduitWithoutCategorie;

    // [MyController] sınıfı bir singleton'dur ve bean yalnızca bir kez örneklenir
    
    public MyController() {
        // System.out.println("MyController");
    }

    @RequestMapping(value = "/addProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String addProduits(HttpServletRequest request) throws JsonProcessingException {
        ...
    }

  • 28. satır: [@Controller] anotasyonu, sınıfı bir Spring bileşeni haline getirir;
  • 32-33. satırlar: [ApplicationModel] sınıfına bir referans enjeksiyonu;
  • 36-50. satırlar: jSON eşleştiricilerine referans enjeksiyonları;
  • 58. satır: Açıklanan URL, [/addProduits]'tir. İstemci, isteğini yapmak için [POST] yöntemini kullanmalıdır (method = RequestMethod.POST). Gönderilen değeri jSON biçiminde bir dize olarak göndermelidir (consumes = "application/json; charset=UTF-8"). Yöntem, yanıtı müşteriye kendisi geri gönderir (satır 59). Bu, bir karakter dizisi olacaktır (satır 60). HTTP [Content-type : application/json; charset=UTF-8] başlığı, müşteriye bir jSON dizesi alacağını belirtmek için gönderilecektir (satır 58);
  • 60. satır: [addProduits] yöntemi, veritabanına eklenen ürün listesinden jSON dizesini döndürür;

13.5.5.3. Denetleyicinin yöntemlerinin yanıtı

Denetleyicinin tüm yöntemleri, aşağıdaki gibi [Response] türünde bir yanıt döndürür:

  

package spring.webjson.service;

import java.util.List;

public class Response<T> {

    // ----------------- özellikler
    // işlem durumu
    private int status;
    // olası hata mesajları
    private List<String> messages;
    // yanıtın gövdesi
    private T body;

    // yapıcılar
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // getter ve setter'lar
    ...
}
  • 5. satır: yanıt, T türünü içerir;
  • 13. satır: T türündeki yanıt;
  • 9-11. satırlar: Bir yöntemin bir istisna ile karşılaşması mümkündür. Bu durumda, aşağıdaki şekilde bir yanıt döndürür:
    • 9. satır: status!=0;
    • satır 11: karşılaşılan hataların listesi;

13.5.5.4. L'URL [/addProduits]

L'URL [/addProduits] aşağıdaki yöntemle işlenir:


@RequestMapping(value = "/addProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String addProduits(HttpServletRequest request) throws JsonProcessingException {
        // yanıt
        Response<List<Produit>> response;
        try {
            // gönderilen değer alınır
            String body = CharStreams.toString(request.getReader());
            List<Produit> produits = jsonMapperProduitWithoutCategorie.readValue(body, new TypeReference<List<Produit>>() {
            });
            // ürünler ve kategoriler arasındaki bağlantıyı yeniden kurulur
            for (Produit produit : produits) {
                produit.setCategorie(application.getCategorieByIdWithoutProduits(produit.getIdCategorie()));
            }
            // ürünleri kalıcı hale getirme
            application.addProduits(produits);
            response = new Respon    se<List<Produit>>(0, null, produits);
        } catch (DaoException e1) {
            response = new Response<List<Produit>>(1000, e1.getErreurs(), null);
        } catch (Exception e2) {
            response = new Response<List<Produit>>(1000, getErreursForException(e2), null);
        }
        // yanıt jSON
        return jsonMapperProduitWithoutCategorie.writeValueAsString(response);
    }
  • 3. satır: Yöntem, istemcinin isteğiyle ilgili tüm bilgileri içeren [HttpServletRequest request] parametresini kabul eder;
  • 5. satır: müşteriye gönderilecek yanıt: bir ürün listesi;
  • 8. satır: Gönderilen değer alınır. [CharStreams] sınıfı, [Google Guava] kütüphanesine aittir ve bu kütüphaneye ait referans, [pom.xml] dosyasına eklenmiştir. Müşteri tarafından gönderilen jSON dizesini elde ederiz. Bu diziyi işlemek için deserialize etmemiz gerekir;
  • 8-10. satırlar: Deserializasyon işlemi gerçekleştirilir. Her bir ürünün [categorie=null] alanına sahip olduğu bir ürün listesi elde edilir;
  • 12-14. satırlar: listedeki tüm ürünlerin [categorie] alanı sıfırlanır. Bunun için, önceden başlatılmış olan ürünün [idCategorie] alanı kullanılır;
  • 16. satır: Ürünler veritabanına eklenir;
  • 17. satır: [response] nesnesi, ürün listesiyle başlatılır;
  • 18-19. satırlar: Yöntemin [DAO] katmanından bir istisna ile karşılaştığı durumlar. Yanıt, [status=1000] (hata kodu) [messages=e1.getMessages()] ile başlatılır; yani sunucu tarafında karşılaşılan hataların listesi istemciye iletilir;
  • 20-21. satırlar: Yöntemin başka bir istisna türüyle karşılaştığı durum. Yanıt, [status=1000] (hata kodu) [messages=getErreursForException(e)]; burada [getErreursForException], e'nin istisna yığınındaki istisnalarla ilişkili hata listesini döndüren sınıfın özel bir yöntemidir ve [body=null];
  • 24. satır: yanıt olarak jSON dizesi döndürülür;

13.5.5.5. URL [/getAllProduits]

URL [/getAllProduits] aşağıdaki yöntemle işlenir:


    @RequestMapping(value = "/getAllProduits", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String getAllProduits() throws JsonProcessingException {
        // yanıt
        Response<List<Produit>> response;
        try {
            response = new Response<List<Produit>>(0, null, application.getAllProduits());
        } catch (DaoException e1) {
            response = new Response<List<Produit>>(1003, e1.getErreurs(), null);
        } catch (Exception e2) {
            response = new Response<List<Produit>>(1003, getErreursForException(e2), null);
        }
        // yanıt jSON
        return jsonMapperProduitWithoutCategorie.writeValueAsString(response);
}
  • 1. satır: URL [/getAllProduits], bir [GET] işlemiyle talep edilir. Bu işlem, jSON'i üretir;
  • 2. satır: Yöntem, jSON yanıtını kendisi müşteriye gönderir;
  • 5. satır: Yöntem, [Response<List<Produit>>] türündeki jSON dizesini gönderir;
  • 7. satır: Ürünler, kategorileri belirtilmeden istenir;
  • satır 8-12: hata durumunda, yanıt bir hata kodu ve hata mesajlarıyla başlatılır;
  • 14. satır: Yanıtın jSON dizesi müşteriye gönderilir;

13.5.5.6. Conclusion

Denetleyicinin diğer yöntemlerini burada ele almayacağız. Bunlar, az önce sunduğumuz iki yöntemden birine benziyor.

13.5.6. Web hizmetinin yürütme sınıfı / jSON

  

[Boot] sınıfı, projenin yürütülebilir sınıfıdır:


package spring.webjson.boot;

import org.springframework.boot.SpringApplication;

import spring.webjson.server.config.AppConfig;

public class Boot {

    public static void main(String[] args) {
        SpringApplication.run(AppConfig.class, args);
    }
}
  • 10. satır: [SpringApplication.run] statik yöntemi yürütülür. [SpringApplication] sınıfı, [Spring Boot] projesine ait bir sınıftır (3. satır). Bu sınıfa iki parametre aktarılır:
    • [AppConfig.class]: Uygulamanın tamamını yapılandıran sınıf;
    • [args]: 9. satırdaki [main] yöntemine aktarılan olası argümanlar. Bu parametre burada kullanılmamaktadır;

Bu sınıf çalıştırıldığında aşağıdaki günlük kayıtları elde edilir:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.2.RELEASE)

2015-03-24 16:22:46.608  INFO 9492 --- [           main] spring.webjson.server.boot.Boot          : Starting Boot on Gportpers3 with PID 9492 (D:\data\istia-1415\eclipse\intro-web-json\intro-webjson-server-02\target\classes started by ST in D:\data\istia-1415\eclipse\intro-web-json\intro-webjson-server-02)
2015-03-24 16:22:46.654  INFO 9492 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d7acb34: startup date [Tue Mar 24 16:22:46 CET 2015]; root of context hierarchy
2015-03-24 16:22:47.521  INFO 9492 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-03-24 16:22:47.569  INFO 9492 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'entityManagerFactory': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.data.config.DaoConfig; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class spring.data.config.DaoConfig] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]]
2015-03-24 16:22:48.137  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$405db6ba] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.162  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.172  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.178  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.586  INFO 9492 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-03-24 16:22:48.850  INFO 9492 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-03-24 16:22:48.852  INFO 9492 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.20
2015-03-24 16:22:48.992  INFO 9492 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-03-24 16:22:48.992  INFO 9492 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2342 ms
2015-03-24 16:22:49.645  INFO 9492 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-03-24 16:22:49.650  INFO 9492 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-03-24 16:22:49.651  INFO 9492 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-03-24 16:22:50.380  INFO 9492 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-24 16:22:50.392  INFO 9492 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2015-03-24 16:22:50.478  INFO 9492 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-24 16:22:50.480  INFO 9492 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2015-03-24 16:22:50.483  INFO 9492 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2015-03-24 16:22:50.697  INFO 9492 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-24 16:22:50.806  INFO 9492 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2015-03-24 16:22:51.058  INFO 9492 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2015-03-24 16:22:52.581  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d7acb34: startup date [Tue Mar 24 16:22:46 CET 2015]; root of context hierarchy
2015-03-24 16:22:52.654  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/addProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.addProduits(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/updateProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.updateProduits(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.getAllProduits()
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.getAllCategories()
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/addCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.addCategories(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/updateCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.updateCategories(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByNameWithoutProduits/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByNameWithoutProduits(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByNameWithoutCategorie/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByNameWithoutCategorie(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByNameWithCategorie/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByNameWithCategorie(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByIdWithCategorie/{idProduit}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByIdWithCategorie(java.lang.Long)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByNameWithProduits/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByNameWithProduits(java.lang.String)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByIdWithProduits/{idCategorie}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByIdWithProduits(java.lang.Long)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/deleteAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.lang.Void> spring.webjson.server.service.Controller.deleteAllCategories()
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByIdWithoutProduits/{idCategorie}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByIdWithoutProduits(java.lang.Long)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/deleteAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.lang.Void> spring.webjson.server.service.Controller.deleteAllProduits()
2015-03-24 16:22:52.658  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByIdWithoutCategorie/{idProduit}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByIdWithoutCategorie(java.lang.Long)
2015-03-24 16:22:52.659  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.659  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.691  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] türündeki işleyiciye
2015-03-24 16:22:52.692  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] QZXW2HTML türündeki işleyici BW2NsYXNzIG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQucmVzb3VyY2UuUmVzb3VyY2VIdHRwUmVxdWVzdEhhbmRsZXJdZQX
2015-03-24 16:22:52.742  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-03-24 16:22:53.001  INFO 9492 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-03-24 16:22:53.106  INFO 9492 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-24 16:22:53.108  INFO 9492 --- [           main] spring.webjson.server.boot.Boot          : Started Boot in 6.752 seconds (JVM running for 7.433)
  • 17-19. satırlar: / jSON web hizmetini çalıştıracak Tomcat sunucusunun başlatılması;
  • satır 25-33: [DAO] katmanının oluşturulması;
  • 32-51. satırlar: Açığa çıkan URL'ler tespit edilir;

13.5.7. / jSON web hizmetinin testleri

Testleri yapmak için, SQL [dbintrospringdata.sql] komut dosyasından MySQL [dbintrospringdata] veritabanını oluşturuyoruz:

  

Bunu yaptıktan sonra, / jSON web hizmeti tarafından sunulan URL verilerini sorgulamak için [Advanced Rest Client] istemcisini kullanıyoruz tarafından sunulan URL dosyalarını sorgulamak için kullanıyoruz (/ jSON web hizmeti başlatılmış olmalıdır).

  • [1-3]'te, URL ve [/getAllCategories]'i HTTP ve GET komutları aracılığıyla talep ediyoruz;

Aşağıdaki yanıtı alıyoruz:

  • [1]'te, istemcinin HTTP isteği;
  • [2]'e, sunucunun HTTP yanıtı;
  • [3]'e, [200 OK] durumu, sunucunun isteği başarıyla işlediğini gösterir;
  • [4]'te, sunucunun jSON yanıtı;

jSON yanıtının tamamı şöyledir:


{"status":0,"messages":null,"body":[{"id":415,"version":0,"nom":"categorie0","produits":[{"id":1849,"version":0,"nom":"produit00","idCategorie":415,"prix":100.0,"description":"desc00"},{"id":1850,"version":0,"nom":"produit01","idCategorie":415,"prix":101.0,"description":"desc01"},{"id":1851,"version":0,"nom":"produit02","idCategorie":415,"prix":102.0,"description":"desc02"},{"id":1852,"version":0,"nom":"produit03","idCategorie":415,"prix":103.0,"description":"desc03"},{"id":1853,"version":0,"nom":"produit04","idCategorie":415,"prix":104.0,"description":"desc04"}]},{"id":416,"version":0,"nom":"categorie1","produits":[{"id":1856,"version":0,"nom":"produit12","idCategorie":416,"prix":112.0,"description":"desc12"},{"id":1857,"version":0,"nom":"produit13","idCategorie":416,"prix":113.0,"description":"desc13"},{"id":1858,"version":0,"nom":"produit14","idCategorie":416,"prix":114.0,"description":"desc14"},{"id":1854,"version":0,"nom":"produit10","idCategorie":416,"prix":110.0,"description":"desc10"},{"id":1855,"version":0,"nom":"produit11","idCategorie":416,"prix":111.0,"description":"desc11"}]}]}
  • status:0, sunucu tarafında hata olmadığını gösterir;
  • mesajlar: null, hata mesajı olmadığı anlamına gelir;
  • body: yanıtın gövdesidir; burada, ürünleri ile birlikte kategori listesi yer almaktadır. Her biri 5 ürün içeren iki kategori bulunmaktadır;

[categorie1] kategorisine [produit15] ürününü ekleyeceğiz. Bunun için aşağıdaki koda sahip URL ve [/addCategories] kodlarını kullanacağız:


@RequestMapping(value = "/addCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
    @ResponseBody
    public String addCategories(HttpServletRequest request) throws JsonProcessingException {
        Response<List<Categorie>> response;
        ObjectMapper mapper = context.getBean(ObjectMapper.class);
        // kategoriler korunur
        try {
            // kaydedilen değer alınır
            String body = CharStreams.toString(request.getReader());
            mapper.setFilters(jsonFilterCategorieWithProduits);
            List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
            });
            // ürünler ve kategoriler arasındaki bağlantı yeniden kurulur
            for (Categorie categorie : categories) {
                Set<Produit> produits = categorie.getProduits();
                if (produits != null) {
                    for (Produit produit : categorie.getProduits()) {
                        produit.setCategorie(categorie);
                    }
                }
            }
            // kategoriler kalıcı hale getiriliyor
            application.addCategories(categories);
            response = new Response<List<Categorie>>(0, null, categories);
        } catch (Exception e) {
            response = new Response<List<Categorie>>(1004, getErreursForException(e), null);
        }
        // yanıt jSON
        return mapper.writeValueAsString(response);
    }
  • 1. satır: müşteri POST oluşturmalı ve gönderilen değer jSON dizesi olmalıdır;
  • 9-12. satırlar: gönderilen değer, ilgili ürünleriyle birlikte bir kategori listesi olmalıdır;

[categorie2] adlı bir kategori ve [produit21] adlı bir ürün oluşturacağız. Bu durumda gönderilecek jSON dizesi şu şekildedir:

[{"id":null,"version":0,"nom":"categorie2","produits":[{"id":null,"version":0,"nom":"produit21","idCategorie":null,"prix":111.0,"description":"desc21"}]}]

/jSON web hizmetine yapılan istek şu şekilde gerçekleştirilir:

  • [1]'te, istenen URL;
  • [2]'te, POST işlemi aracılığıyla talep edilir;
  • [3]'te, jSON dizesi gönderilir;
  • [4]'te, sunucuya jSON'i göndereceğimiz belirtilir;

Sunucunun yanıtı şöyledir:

  • [1]'te, hem kategorinin hem de ürünün artık bir birincil anahtara sahip olduğu görülüyor; bu da muhtemelen veritabanına eklendiklerini gösteriyor. Bunu URL ve [/getCategorieByNameWithProduits/categorie2]'i kullanarak doğrulayacağız:

Aşağıdaki sonucu elde ediyoruz:

[categorie2] kategorisini ve bu kategorideki tek ürünü olan [produit21]'i doğru bir şekilde elde ettik. Yalnızca ürünü de sorgulayabiliriz. Bunun için URL ve [/getProduitByIdWithoutCategorie/1859]'i kullanalım:

Şu sonucu elde ediyoruz:

Tüm [GET] işlemleri basit bir tarayıcıda gerçekleştirilebilir:

 

Okuyucunun, web hizmeti / json'daki diğer URL işlemlerini denemesi önerilir.

13.6. / jSON web hizmeti için programlanmış bir istemci

Artık [dbintrospringdata] veritabanı web üzerinde mevcut olduğuna göre, bunu kullanan bir uygulama yazacağız. Böylece şu istemci / sunucu mimarisine sahip olacağız:

İstemci uygulaması iki katmandan oluşacak:

  • veritabanını sunan web uygulaması / jSON ile iletişim kurmak için bir [DAO] [2] katmanı;
  • istemci ve sunucunun düzgün çalıştığını doğrulamak için bir test katmanı (JUnit [1]);

13.6.1. Eclipse Projesi

Müşterinin Eclipse projesi şu şekildedir:

  
  • [src/main/java] klasörü, [DAO] katmanını uygular;
  • [src/test/java] klasörü, JUnit testlerini uygular;

13.6.2. Projenin Maven Yapılandırması

Proje, aşağıdaki [pom.xml] dosyası ile yapılandırılmış bir Maven projesidir:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.webjson</groupId>
    <artifactId>intro-client-webjson-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Client console du serveur web / jSON</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <!-- Spring tarafından kullanılan jSON kütüphanesi -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <!-- Spring tarafından kullanılan bileşen RestTemplate -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <!-- Google Guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>16.0.1</version>
            <scope>test</scope>
        </dependency>
        <!-- günlük kütüphanesi -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <!-- Spring Boot Testi -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <!-- eklentiler -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

    <name>intro-client-webjson-01</name>
</project>
  • 14-18. satırlar: [spring-boot-starter-parent] adlı üst Maven projesi, sürümleri belirtilmeden bir dizi bağımlılığı tanımlamamıza olanak tanır; sürümler üst projede tanımlanmıştır;
  • 22-25. satırlar: bir web uygulaması yazmıyor olsak da, bir web uygulamasıyla / jSON ile kolayca arayüz oluşturmamızı sağlayan [RestTemplate] sınıfını beraberinde getiren [spring-web] bağımlılığına ihtiyacımız var;
  • 27-34. satırlar: bir jSON kütüphanesi;
  • satır 36-39: İstemcinin HTTP isteklerine bir timeout eklememizi sağlayacak bir bağımlılık. timeout, sunucudan yanıt alınması için maksimum bekleme süresidir. Bu süre aşıldığında, istemci bir istisna oluşturarak timeout hatasını bildirir;
  • 41-46. satırlar: Testte kullanılan Google Guava kütüphanesi JUnit. Bu nedenle, kapsamını [test] olarak belirledik (satır 45). Bu, söz konusu bağımlılığın yalnızca [src/test/java] dalındaki kodlar çalıştırıldığında dahil edileceği anlamına gelir;
  • 48-51. satırlar: günlük kütüphanesi;
  • satır 52-63: JUnit test bağımlılığı. Bu bağımlılık, özellikle testler için gerekli olan JUnit 4 kütüphanesini içerir. Bu bağımlılıklar, yalnızca test aşaması için gerekli olduklarını belirten [<scope>test</scope>] özniteliğine sahiptir. Bunlar, projenin nihai arşivine dahil edilmez;

13.6.3. [DAO] katmanının uygulanması

  
  • [spring.client.config] paketi, [DAO] katmanının Spring yapılandırmasını içerir;
  • [spring.client.dao] paketi, [DAO] katmanının uygulamasını içerir;
  • [spring.client.entities] paketi, /jSON web hizmeti ile alışverişi yapılan nesneleri içerir;

13.6.3.1. Configuration

  

[DaoConfig] sınıfı, [DAO] katmanının Spring yapılandırmasını gerçekleştirir. Kod şöyledir:


package spring.client.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;

@ComponentScan({ "spring.client.dao" })
public class DaoConfig {

    // sabitler
    static private final int TIMEOUT = 1000;
    static private final String URL_WEBJSON = "http://localhost:8080";

    @Bean
    public RestTemplate restTemplate(int timeout) {
        // bileşen oluşturma RestTemplate
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
        RestTemplate restTemplate = new RestTemplate(factory);
        // iletişim zaman aşımı
        factory.setConnectTimeout(timeout);
        factory.setReadTimeout(timeout);
        // sonuç
        return restTemplate;
    }

    @Bean
    public int timeout() {
        return TIMEOUT;
    }

    @Bean
    public String urlWebJson() {
        return URL_WEBJSON;
    }

    // filtreler jSON
    @Bean(name = "jsonMapper")
    public ObjectMapper jsonMapper() {
        return new ObjectMapper();
    }

    @Bean(name = "jsonMapperCategorieWithProduits")
    public ObjectMapper jsonMapperCategorieWithProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithCategorie")
    public ObjectMapper jsonMapperProduitWithCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(
                new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
                        .addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperCategorieWithoutProduits")
    public ObjectMapper jsonMapperCategorieWithoutProduits() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
                SimpleBeanPropertyFilter.serializeAllExcept("produits")));
        // sonuç
        return mapper;
    }

    @Bean(name = "jsonMapperProduitWithoutCategorie")
    public ObjectMapper jsonMapperProduitWithoutCategorie() {
        // eşleştirici jSON
        ObjectMapper mapper = new ObjectMapper();
        // filtreler
        mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
                SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
        // sonuç
        return mapper;
    }
}
  • 13. satır: Sınıf, bir Spring yapılandırma sınıfıdır — Spring bileşenleri [spring.client.dao] paketinde aranmalıdır;
  • 17. satır: timeout değeri bir saniye (1000 ms) olarak belirlenir;
  • 32-35. satırlar: bu değeri döndüren bean;
  • 18. satır: web hizmetinin URL / jSON;
  • satır 37-40: bu değeri döndüren bean;
  • satır 20-30: / jSON web hizmeti ile iletişimi sağlayan [RestTemplate] sınıfının yapılandırması. Yapılandırılması gerekmediğinde, kodda basit bir [new RestTemplate()] ile kullanılabilir. Burada, /jSON web hizmeti ile veri alışverişi için timeout değerini belirlemek istiyoruz. 36. satırdaki [timeout] bean'i, 24. satırdaki [restTemplate] yöntemine parametre olarak aktarılmıştır;
  • 23. satır: [HttpComponentsClientHttpRequestFactory] bileşeni, iletişimde timeout değerini belirlememizi sağlayan bileşendir (29-30. satırlar);
  • 24. satır: [RestTemplate] sınıfı bu bileşenle oluşturulmuştur. Bu sınıf, /jSON web hizmeti ile iletişim kurmak için bu bileşene dayandığından, veri alışverişleri timeout'e tabi olacaktır;
  • istemci ve sunucu arasında metin satırları alışverişi yapılacaktır. Bir dönüştürücü, bir nesneyi metne serileştirir ve tersine, bir metni nesneye deserileştirir. [RestTemplate] sınıfıyla ilişkili birden fazla dönüştürücü olabilir ve belirli bir anda hangisinin seçileceği, sunucu tarafından gönderilen HTTP başlıklarına bağlıdır. Burada herhangi bir dönüştürücü kullanmayacağız. Dolayısıyla, [RestTemplate] bileşeni, aşağıdaki iki öğeyi hiçbir şekilde dönüştürmeye çalışmayacaktır:
    • gönderilen metin;
    • yanıt olarak alınan metin;

Bu metinler, jSON dizeleri olacak ve dolayısıyla [RestTemplate] bileşeni tarafından olduğu gibi bırakılacaktır. Gerekli jSON serileştirme/deserileştirme işlemlerini biz geliştiriciler yapacağız. Bunun nedeni, gönderilen değere ve alınan yanıta uygulanacak filtrelerin farklı olabilmesidir; deneyimler, [RestTemplate] bileşenini doğru jSON dönüştürücüsünü kullanacak şekilde yapılandırmaya çalışmaktansa, bunları kendimiz yönetmenin daha kolay olduğunu göstermektedir;

  • satır 42-92: jSON filtrelerini tanımlar. Bunlar, 13.5.3.1 paragrafında sunulan ve açıklanan sunucu filtreleriyle aynıdır;
  • 43-46. satırlar: filtre içermeyen bir jSON eşleştiricisi;
  • 64-68. satırlar: Ürünleri içermeyen bir kategori oluşturmak için jSON eşleştiricisi;
  • 48-58. satırlar: ürünleriyle birlikte bir kategoriye sahip olmak için jSON eşleştiricisi;
  • satır 83-92: bir ürünün kategorisi olmadan görüntülenmesi için jSON eşleştiricisi;
  • 60-70. satırlar: bir ürünün kendi kategorisiyle birlikte gösterilmesi için jSON eşleştiricisi;

Tüm bu bean'ler, [DAO] katmanındaki kodlarda ve Junit testinde kullanılabilir olacaktır.

13.6.3.2. Varlıklar

  

[DAO] katmanı tarafından işlenen varlıklar, bu katmanın /jSON web hizmeti ile alışveriş yaptığı varlıklardır. Bunlar, ürünler ve ürün kalemleridir. Sunucu tarafında, bu varlıklar JPA kalıcılık anotasyonlarına sahipti. Burada bu anotasyonlar kaldırılmıştır. Hatırlatma amacıyla varlıkların kodunu tekrar veriyoruz:

[AbstractEntity]


package spring.client.entities;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public abstract class AbstractEntity {
    // özellikler
    protected Long id;
    protected Long version;

    // oluşturucular
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // [equals] ve [hashcode] yeniden tanımlaması
    @Override
    public int hashCode() {
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return id != null && this.id == other.id.longValue();
    }

    // imza jSON
    public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    // alıcı ve ayarlayıcılar
...
}

[Categorie]


package spring.client.entities;

import java.util.HashSet;
import java.util.Set;

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("jsonFilterCategorie")
public class Categorie extends AbstractEntity {

    // özellikler
    private String nom;

    // ilgili ürünler
    public Set<Produit> produits = new HashSet<Produit>();

    // yapıcılar
    public Categorie() {

    }

    public Categorie(String nom) {
        this.nom = nom;
    }

    // yöntemler
    public void addProduit(Produit produit) {
        // ürünü ekliyoruz
        produits.add(produit);
        // kategorisi belirlenir
        produit.setCategorie(this);
    }

    // getter ve setter'lar
    ...
}

[Produit]


package spring.webjson.client.entities;

import com.fasterxml.jackson.annotation.JsonFilter;

@JsonFilter("jsonFilterProduit")
public class Produit extends AbstractEntity {

    // ad
    private String nom;
   // kategori numarası
    private Long idCategorie;
   // fiyat
    private double prix;
   // açıklama
    private String description;

    // kategori
    private Categorie categorie;

    // üreticiler
    public Produit() {

    }

    public Produit(String nom, double prix, String description) {
        this.nom = nom;
        this.prix = prix;
        this.description = description;
    }

    // getter ve setter'lar
...
}

13.6.3.3. [DaoException] sınıfı

 

[DAO] katmanı bir hatayla karşılaştığında, [DaoException] türünde bir istisna oluşturur. Bu sınıf, sunucu tarafında kullanılan ve 11.3.7. paragrafında açıklanan sınıftır.

13.6.3.4. [DAO] katmanının arayüzü

 

[DAO] katmanı, 11.3.7. paragrafında açıklanan [IDao] arayüzünü sunar.


package spring.client.dao;

import java.util.List;

import spring.client.entities.Categorie;
import spring.client.entities.Produit;

public interface IDao {

    // ürün listesinin eklenmesi
    public List<Produit> addProduits(List<Produit> produits);

    // tüm ürünlerin silinmesi
    public void deleteAllProduits();

    // ürün listesinin güncellenmesi
    public List<Produit> updateProduits(List<Produit> produits);

    // tüm ürünleri alma
    public List<Produit> getAllProduits();

    // kategori listesi ekleme
    public List<Categorie> addCategories(List<Categorie> categories);

    // tüm kategorilerin silinmesi
    public void deleteAllCategories();

    // kategori listesinin güncellenmesi
    public List<Categorie> updateCategories(List<Categorie> categories);

    // tüm kategorileri alma
    public List<Categorie> getAllCategories();

    // belirli bir ürün
    public Produit getProduitByIdWithCategorie(Long idProduit);

    public Produit getProduitByIdWithoutCategorie(Long idProduit);

    public Produit getProduitByNameWithCategorie(String nom);

    public Produit getProduitByNameWithoutCategorie(String nom);

    // belirli bir kategori
    public Categorie getCategorieByIdWithProduits(Long idCategorie);

    public Categorie getCategorieByIdWithoutProduits(Long idCategorie);

    public Categorie getCategorieByNameWithProduits(String nom);

    public Categorie getCategorieByNameWithoutProduits(String nom);

}

13.6.3.5. Web hizmetinin yanıtı / jSON

  

/ jSON web hizmetinin tüm URL öğelerinin, 13.5.5.3. paragrafında tanımlanan [Response] türünü döndürdüğünü gördük. Bu sınıfı burada tekrar veriyoruz:


package spring.client.dao;

import java.util.List;

public class Response<T> {

    // ----------------- özellikler
    // işlemin durumu
    private int status;
    // olası hata mesajları
    private List<String> messages;
    // yanıt gövdesi
    private T body;

    // yapıcılar
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // alıcı ve ayarlayıcılar
    ...
}

13.6.3.6. Web hizmeti / jSON ile veri alışverişinin uygulanması

  

[AbstractDao] sınıfı , web hizmeti / jSON ile veri alışverişini gerçekleştirir:


package spring.client.dao;

import java.net.URI;
import java.net.URISyntaxException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.web.client.RestTemplate;

public abstract class AbstractDao {

    // veri
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;

    // genel istek
    protected String getResponse(String url, String jsonPost) {

        // URL: URL ile iletişime geçin
        // jsonPost: gönderilecek jSON değeri
        try {
            // isteğin yürütülmesi
            RequestEntity<?> request;
            if (jsonPost != null) {
                // POST isteği
                request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
            } else {
                // GET isteği
                request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
                        .accept(MediaType.APPLICATION_JSON).build();
            }
            // sorgu çalıştırılıyor
            return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
            }).getBody();
        } catch (URISyntaxException e1) {
            throw new DaoException(20, e1);
        } catch (RuntimeException e2) {
            throw new DaoException(21, e2);
        }
    }

}

  • 15-16. satırlar: sunucu ile iletişimi sağlayan [RestTemplate] bileşeninin eklenmesi;
  • 17-18. satırlar: web hizmeti / jSON'e ait URL bileşeninin eklenmesi;

Sunucuyla iletişim yöntemlerinin uygulaması, [getResponse] yönteminde faktörleştirilmiştir:

  • 21. satır: Yöntem 2 parametre alır:
    • [url]: istenen URL;
    • [jsonPost]: gönderilecek jSON dizesi, aksi takdirde null. [jsonPost==null] ise, URL'e yönelik istek bir GET ile yapılır, aksi takdirde bir POST ile yapılır;
  • 38. satır: Sunucuya isteği gönderen ve yanıtını alan komut. [RestTemplate] bileşeni, sunucuyla iletişim kurmak için çok sayıda yöntem sunar. Burada [exchange] yöntemini seçtik, ancak başka yöntemler de mevcuttur;
  • 27-36. satırlar: [RequestEntity] türünde bir istek oluşturmamız gerekiyor. Bu istek, istek yapmak için GET mi yoksa POST mi kullanıldığına göre farklılık gösterir;
  • 30-31. satırlar: GET için sorgu. [RequestEntity] sınıfı, GET, POST, HEAD... sorgularını oluşturmak için statik yöntemler sunar. [RequestEntity.get] yöntemi, bunu oluşturan çeşitli yöntemleri zincirleyerek bir GET isteği oluşturulmasına olanak tanır:
    • [RequestEntity.get] yöntemi, hedef URL'i bir URI örneği biçiminde parametre olarak kabul eder,
    • [accept] yöntemi, HTTP ve [Accept] başlık öğelerini tanımlamaya olanak tanır. Burada, sunucunun göndereceği [application/json] türünü kabul ettiğimizi belirtiriz;
    • [build] yöntemi, bu farklı bilgileri kullanarak isteğin [RequestEntity] türünü oluşturur;
  • 34-35. satırlar: bir POST için istek. [RequestEntity.post] yöntemi, bunu oluşturan çeşitli yöntemleri zincirleyerek bir POST isteği oluşturulmasına olanak tanır:
    • [RequestEntity.post] yöntemi, hedef URL'i bir URI örneği biçiminde parametre olarak kabul eder,
    • [header] yöntemi, bir HTTP başlığını tanımlar. Burada, sunucuya [Content-Type: application/json] başlığını göndererek, gönderilen değerin jSON dizesi biçiminde ulaşacağını bildiririz;
    • [accept] yöntemi, sunucunun göndereceği [application/json] türünü kabul ettiğimizi belirtmemizi sağlar;
    • [body] yöntemi, gönderilen değeri belirler. Bu değer, genel [getResponse] yönteminin (1. satır) 4. parametresidir;
  • 38. satır: [RestTemplate].exchange yöntemi, sunucunun yanıtının tamamını (HTTP başlıkları ve belge gövdesi) kapsayan bir [ResponseEntity<String>] türü döndürür. [ResponseEntity].getBody() yöntemi, sunucunun yanıtını temsil eden bu gövdeye (burada bir karakter dizisi) erişilmesini sağlar;

13.6.3.7. [IDao] arayüzünün uygulanması

  

[Dao] sınıfı, [IDao] arayüzünü uygular:


package spring.client.dao;

import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import spring.client.entities.Categorie;
import spring.client.entities.Produit;

@Component
public class Dao extends AbstractDao implements IDao {

    @Autowired
    private ApplicationContext context;

    // filtreler jSON
    @Autowired
    @Qualifier("jsonMapper")
    private ObjectMapper jsonMapper;
    @Autowired
    @Qualifier("jsonMapperCategorieWithProduits")
    private ObjectMapper jsonMapperCategorieWithProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithCategorie")
    private ObjectMapper jsonMapperProduitWithCategorie;
    @Autowired
    @Qualifier("jsonMapperCategorieWithoutProduits")
    private ObjectMapper jsonMapperCategorieWithoutProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithoutCategorie")
    private ObjectMapper jsonMapperProduitWithoutCategorie;

    @Override
    public List<Produit> addProduits(List<Produit> produits) {
        // ----------- ürünleri (kategorileri olmadan) ekle
        ...
}
  • 17. satır: [Dao] sınıfı, içine diğer Spring bileşenlerinin enjekte edilebildiği bir Spring bileşenidir;
  • 18. satır: [Dao] sınıfı, az önce incelediğimiz [AbstractDao] sınıfını genişletir ve [IDao] arayüzünü uygular;
  • 20-21. satırlar: Bean'lere erişebilmek için Spring bağlamı enjekte edilir;
  • 24-38. satırlar: 13.6.2. paragrafında tanıtılan [AppConfig] sınıfında tanımlanan jSON eşleştiricilerinin eklenmesi;

[IDao] arayüzünün çeşitli yöntemlerinin uygulamaları, hepsi aynı şemayı izler. Burada, biri [POST] işlemine, diğeri ise [GET] işlemine dayanan iki yöntemi ele alacağız.

[GET] örneği: [getCategorieByNameWithProduits]


@Override
    public Categorie getCategorieByNameWithProduits(String nom) {
        // ----------- adıyla belirtilen kategoriyi, içindeki ürünlerle birlikte al
        try {
            // sorgu
            Response<Categorie> response = jsonMapperCategorieWithProduits.readValue(
                    getResponse(String.format("/getCategorieByNameWithProduits/%s", nom), null),
                    new TypeReference<Response<Categorie>>() {
                    });
            // hata mı?
            if (response.getStatus() != 0) {
                // 1 istisna atılıyor
                throw new DaoException(response.getStatus(), response.getMessages());
            } else {
                // sunucu yanıtının ana kısmı döndürülüyor
                return response.getBody();
            }
        } catch (DaoException e1) {
            throw e1;
        } catch (RuntimeException | IOException e2) {
            throw new DaoException(113, e2);
        }
    }
  • 7. satır: üst sınıftaki [getResponse] yöntemi çağrılır. Web hizmeti / jSON ile iletişimi sağlayan bu yöntemdir. Parametreleri şunlardır:

getResponse(String.format("/getCategorieByNameWithProduits/%s", nom), null)
  • (devam)
    • sorgulanan hizmetin URL'i [/getCategorieByNameWithProduits/nom];
    • gönderilen değer. Burada böyle bir değer yoktur;

[getResponse] yöntemi, sunucu tarafından gönderilen jSON yanıtını içeren bir String türü döndürür. Bu jSON yanıtını şu şekilde deserialize ederiz:


jsonMapperCategorieWithProduits.readValue(
                    jsonResponse,
                    new TypeReference<Response<Categorie>>() {
});

çünkü jSON dizesi, bir [Response<Categorie>] türünün serileştirilmesidir;

  • 11-17. satırlar: yanıtın durumu kontrol edilir. Durum 0'dan farklıysa, sunucu tarafında bir hata meydana gelmiştir. Bu durumda, yanıtta yer alan bilgileri (durum ve hata mesajları listesi) kullanarak bir istisna atılır (13. satır);
  • 16. satır: Sunucu tarafında hata oluşmamışsa, [Response<Categorie>] türündeki gövde, yani istenen kategori döndürülür;
  • satır 18-19: 16. satırda atılan istisna işlenir;
  • 20-22. satırlar: diğer tüm istisnaları işler;

[POST] örneği: [addCategories]


@Override
    public List<Categorie> addCategories(List<Categorie> categories) {
        // ----------- kategoriler ekleniyor (ürünleriyle birlikte)
        try {
            // istek
            Response<List<Categorie>> response = jsonMapperCategorieWithProduits.readValue(
                    getResponse("/addCategories", jsonMapperCategorieWithProduits.writeValueAsString(categories)),
                    new TypeReference<Response<List<Categorie>>>() {
                    });
            // hata mı?
            if (response.getStatus() != 0) {
                // 1 istisna tetikleniyor
                throw new DaoException(response.getStatus(), response.getMessages());
            } else {
                // sunucunun yanıtının ana kısmı döndürülüyor
                return response.getBody();
            }
        } catch (DaoException e1) {
            throw e1;
        } catch (RuntimeException | IOException e2) {
            throw new DaoException(104, e2);
        }
    }
  • 2. satır: [addCategories] yöntemi, parametre olarak geçirilen kategorileri veritabanına kaydetmek için kullanılır. Bu yöntem, söz konusu kategorileri birincil anahtarlarıyla zenginleştirir. Kategoriler ürünlerle birlikte geçirilirse, bu ürünler de veritabanına kaydedilir;
  • 7. satır: Web hizmeti / jSON ile veri alışverişi yapmak için üst sınıfın [getResponse] yöntemini çağırır;
    • 1. parametre URL [/addCategories]'tir;
    • ikinci parametre, gönderilen değerdir; burada kalıcı hale getirilecek kategorilerin listesi yer alır;

getResponse("/addCategories", jsonMapperCategorieWithProduits.writeValueAsString(categories))

Elde edilen jSON dizesi daha sonra, beklenen [Response<List<Categorie>] türünü elde etmek için deserialize edilir:


Response<List<Categorie>> response = jsonMapperCategorieWithProduits.readValue(
                    jsonResponse,
                    new TypeReference<Response<List<Categorie>>>() {
                    });
  • 11-17. satırlar: sunucu yanıtının yönetimi (hata olup olmadığı);
  • 20-22. satırlar: istisnaların yönetimi;

Diğer tüm yöntemler, sunulan bu iki yöntemin şablonunu takip eder.

13.6.4. JUnit testi

Şu anda oluşturulmakta olan istemci/sunucu mimarisine geri dönelim:

[DAO] [4] katmanıyla aynı arayüze sahip bir [DAO] [2] katmanı oluşturduk. Dolayısıyla, [DAO] [2] katmanını test etmek için, [DAO] [4] katmanını test etmekte kullanılan JUnit testini kullanabiliriz. Hatırlatmak gerekirse, bu test şu şekildedir:

  

package spring.client.junit;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;

import spring.client.config.DaoConfig;
import spring.client.dao.DaoException;
import spring.client.dao.IDao;
import spring.client.entities.Categorie;
import spring.client.entities.Produit;

@SpringApplicationConfiguration(classes = DaoConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

    // [DAO] katmanı
    @Autowired
    private IDao dao;

    // jSON filtreleri
    @Autowired
    @Qualifier("jsonMapper")
    private ObjectMapper jsonMapper;
    @Autowired
    @Qualifier("jsonMapperCategorieWithProduits")
    private ObjectMapper jsonMapperCategorieWithProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithCategorie")
    private ObjectMapper jsonMapperProduitWithCategorie;
    @Autowired
    @Qualifier("jsonMapperCategorieWithoutProduits")
    private ObjectMapper jsonMapperCategorieWithoutProduits;
    @Autowired
    @Qualifier("jsonMapperProduitWithoutCategorie")
    private ObjectMapper jsonMapperProduitWithoutCategorie;

    @Before
    public void cleanAndFill() {
        // her testten önce veritabanı temizlenir
        log("Vidage de la base de données", 1);
        // [CATEGORIES] tablosu boşaltılır - buna bağlı olarak [PRODUITS] tablosu da boşaltılacaktır
        dao.deleteAllCategories();
        // --------------------------------------------------------------------------------------
        log("Remplissage de la base", 1);
        // tablolar doldurulur
        List<Categorie> categories = new ArrayList<Categorie>();
        for (int i = 0; i < 2; i++) {
            Categorie categorie = new Categorie(String.format("categorie%d", i));
            for (int j = 0; j < 5; j++) {
                categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
                        String.format("desc%d%d", i, j)));
            }
            categories.add(categorie);
        }
        // kategori eklenir - zincirleme olarak ürünler de eklenecektir
        categories = dao.addCategories(categories);
    }

    @Test
    public void showDataBase() throws BeansException, JsonProcessingException {
        // kategori listesi
        log("Liste des catégories", 2);
        List<Categorie> categories = dao.getAllCategories();
        affiche(categories, jsonMapperCategorieWithoutProduits);
        // ürün listesi
        log("Liste des produits", 2);
        List<Produit> produits = dao.getAllProduits();
        affiche(produits, jsonMapperProduitWithoutCategorie);
        // bazı kontroller
        Assert.assertEquals(2, categories.size());
        Assert.assertEquals(10, produits.size());
        Categorie categorie = findCategorieByName("categorie0", categories);
        Assert.assertNotNull(categorie);
        Produit produit = findProduitByName("produit03", produits);
        Assert.assertNotNull(produit);
        Long idCategorie = produit.getIdCategorie();
        Assert.assertEquals(categorie.getId(), idCategorie);
    }

    @Test
    public void getCategorieByNameWithProduits() {
        log("getCategorieByNameWithProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
        Assert.assertNotNull(categorie1);
        Assert.assertEquals(5, categorie1.getProduits().size());
    }

    @Test
    public void getCategorieByNameWithoutProduits() {
        log("getCategorieByNameWithoutProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithoutProduits("categorie1");
        Assert.assertNotNull(categorie1);
        Assert.assertEquals("categorie1", categorie1.getNom());
    }

    @Test
    public void getCategorieByIdWithProduits() {
        log("getCategorieByIdWithProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
        Categorie categorie2 = dao.getCategorieByIdWithProduits(categorie1.getId());
        Assert.assertNotNull(categorie2);
        Assert.assertEquals(categorie1.getId(), categorie2.getId());
        Assert.assertEquals(categorie1.getNom(), categorie2.getNom());
    }

    @Test
    public void getCategorieByIdWithoutProduits() {
        log("getCategorieByIdWithoutProduits", 1);
        Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
        Categorie categorie2 = dao.getCategorieByIdWithoutProduits(categorie1.getId());
        Assert.assertNotNull(categorie2);
        Assert.assertEquals(categorie1.getNom(), categorie2.getNom());
    }

    @Test
    public void getProduitByNameWithCategorie() {
        log("getProduitByNameWithCategorie", 1);
        Produit produit = dao.getProduitByNameWithCategorie("produit03");
        Assert.assertNotNull(produit);
        Assert.assertNotNull(produit.getCategorie());
    }

    @Test
    public void getProduitByNameWithoutCategorie() {
        log("getProduitByNameWithoutCategorie", 1);
        Produit produit = dao.getProduitByNameWithoutCategorie("produit03");
        Assert.assertNotNull(produit);
        Assert.assertEquals("produit03", produit.getNom());
    }

    @Test
    public void getProduitByIdWithCategorie() {
        log("getProduitByNameWithCategorie", 1);
        Produit produit = dao.getProduitByNameWithCategorie("produit03");
        Produit produit2 = dao.getProduitByIdWithCategorie(produit.getId());
        Assert.assertNotNull(produit2);
        Assert.assertEquals(produit2.getNom(), produit.getNom());
        Assert.assertEquals(produit2.getId(), produit.getId());
        Assert.assertEquals(produit.getCategorie().getId(), produit2.getCategorie().getId());
    }

    @Test
    public void getProduitByIdWithoutCategorie() {
        log("getProduitByIdWithoutCategorie", 1);
        Produit produit = dao.getProduitByNameWithCategorie("produit03");
        Produit produit2 = dao.getProduitByIdWithoutCategorie(produit.getId());
        Assert.assertNotNull(produit2);
        Assert.assertEquals(produit2.getNom(), produit.getNom());
        Assert.assertEquals(produit2.getId(), produit.getId());
    }

    @Test
    public void doInsertsInTransaction() {
        log("Ajout d'une catégorie [cat1] avec deux produits de même nom", 1);
        // ekleme işlemi yapılıyor
        Categorie categorie = new Categorie("cat1");
        categorie.addProduit(new Produit("x", 1.0, ""));
        categorie.addProduit(new Produit("x", 1.0, ""));
        // kategori ekleniyor - buna bağlı olarak ürünler de sırayla eklenecek
        try {
            categorie = dao.addCategories(Lists.newArrayList(categorie)).get(0);
        } catch (DaoException e) {
            show("Les erreurs suivantes se sont produites :", e.getErreurs());
        }
        // kontroller
        List<Categorie> categories = dao.getAllCategories();
        Assert.assertEquals(2, categories.size());
        List<Produit> produits = dao.getAllProduits();
        Assert.assertEquals(10, produits.size());
    }

    @Test
    public void updateDataBase() {
        log("Mise à jour du prix des produits de [categorie1]", 1);
        Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
        Categorie categorie1Saved = dao.getCategorieByNameWithProduits("categorie1");
        Set<Produit> produits = categorie1.getProduits();
        for (Produit produit : produits) {
            produit.setPrix(1.1 * produit.getPrix());
        }
        List<Produit> produits2 = Lists.newArrayList(produits);
        produits2 = dao.updateProduits(produits2);
        // kontroller
        List<Produit> produitsSaved = Lists.newArrayList(categorie1Saved.getProduits());
        for (Produit produit2 : produits2) {
            Produit produit = findProduitByName(produit2.getNom(), produitsSaved);
            Assert.assertEquals(produit2.getPrix(), produit.getPrix() * 1.1, 1e-6);
        }
    }

    @Test
    public void addProduits() throws BeansException, JsonProcessingException {
        log("Ajout de deux produits de catégorie [categorie0]", 1);
        Categorie categorie0 = dao.getCategorieByNameWithoutProduits("categorie0");
        Long idCategorie = categorie0.getId();
        Produit p1 = new Produit("x", 1, "");
        p1.setIdCategorie(idCategorie);
        p1.setCategorie(categorie0);
        Produit p2 = new Produit("y", 1, "");
        p2.setIdCategorie(idCategorie);
        p2.setCategorie(categorie0);
        List<Produit> produits = new ArrayList<Produit>();
        produits.add(p1);
        produits.add(p2);
        produits = dao.addProduits(produits);
        // doğrulama
        affiche(produits, jsonMapperProduitWithoutCategorie);
    }

    // -------------- özel yöntemler
    private Produit findProduitByName(String nom, List<Produit> produits) {
        for (Produit produit : produits) {
            if (produit.getNom().equals(nom)) {
                return produit;
            }
        }
        return null;
    }

    private Categorie findCategorieByName(String nom, List<Categorie> categories) {
        for (Categorie categorie : categories) {
            if (categorie.getNom().equals(nom)) {
                return categorie;
            }
        }
        return null;
    }

    // T türünde bir öğenin görüntülenmesi
    static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
        System.out.println(jsonMapper.writeValueAsString(element));
    }

    // T türündeki öğelerin listesini görüntüleme
    static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
        for (T element : elements) {
            affiche(element, jsonMapper);
        }
    }

    private static void log(String message, int mode) {
        // mesaj görüntüleme
        String toPrint = null;
        switch (mode) {
        case 1:
            toPrint = String.format("%s --------------------------------", message);
            break;
        case 2:
            toPrint = String.format("-- %s", message);
            break;
        }
        System.out.println(toPrint);
    }

    private static void show(String title, List<String> messages) {
        // başlık
        System.out.println(String.format("%s : ", title));
        // mesajlar
        for (String message : messages) {
            System.out.println(String.format("- %s", message));
        }
    }

}

Çalıştırma başarılı oldu ve konsolda şu sonuçları verdi:


Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Ajout de deux produits de catégorie [categorie0] --------------------------------
{"id":6285,"version":0,"nom":"x","idCategorie":1319,"prix":1.0,"description":""}
{"id":6286,"version":0,"nom":"y","idCategorie":1319,"prix":1.0,"description":""}
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Mise à jour du prix des produits de [categorie1] --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByIdWithoutProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithoutCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByNameWithProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByNameWithoutProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByIdWithoutCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
-- Liste des catégories
{"id":1337,"version":0,"nom":"categorie0"}
{"id":1338,"version":0,"nom":"categorie1"}
-- Liste des produits
{"id":6367,"version":0,"nom":"produit00","idCategorie":1337,"prix":100.0,"description":"desc00"}
{"id":6368,"version":0,"nom":"produit01","idCategorie":1337,"prix":101.0,"description":"desc01"}
{"id":6369,"version":0,"nom":"produit02","idCategorie":1337,"prix":102.0,"description":"desc02"}
{"id":6370,"version":0,"nom":"produit03","idCategorie":1337,"prix":103.0,"description":"desc03"}
{"id":6371,"version":0,"nom":"produit04","idCategorie":1337,"prix":104.0,"description":"desc04"}
{"id":6372,"version":0,"nom":"produit10","idCategorie":1338,"prix":110.0,"description":"desc10"}
{"id":6373,"version":0,"nom":"produit11","idCategorie":1338,"prix":111.0,"description":"desc11"}
{"id":6374,"version":0,"nom":"produit12","idCategorie":1338,"prix":112.0,"description":"desc12"}
{"id":6375,"version":0,"nom":"produit13","idCategorie":1338,"prix":113.0,"description":"desc13"}
{"id":6376,"version":0,"nom":"produit14","idCategorie":1338,"prix":114.0,"description":"desc14"}
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByIdWithProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Ajout d'une catégorie [cat1] avec deux produits de même nom --------------------------------
Les erreurs suivantes se sont produites : 
- org.hibernate.exception.ConstraintViolationException: could not execute statement
- could not execute statement
- Duplicate entry 'x' for key 'NOM'
11:24:37.650 [Thread-1] INFO  o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@f8c1ddd: startup date [Fri Nov 20 11:24:34 CET 2015]; root of context hierarchy