Skip to content

3. Eylemler: Yanıt

Bir Spring MVC uygulamasının mimarisini ele alalım:

Bu bölümde, [1] isteğinin, onu işleyecek denetleyiciye ve [2a] eylemine ulaşmasını sağlayan süreci inceliyoruz; bu mekanizmaya yönlendirme denir. Ayrıca, bir eylemin tarayıcıya gönderebileceği çeşitli [3] yanıtlarını da tanıtacağız. Bu yanıtlar, bir V görünümü ([4b]) dışında başka şeyler de olabilir.

3.1. Yeni proje

Yeni bir Spring projesi oluşturuyoruz:

  • [1-2]'te, Spring Boot tabanlı yeni bir proje oluşturuyoruz;
  • [3], Maven proje adı;
  • [4]'te, projenin derleme sonucunun yerleştirileceği Maven grubu;
  • [5], derleme çıktısına verilen ad;
  • [6]'te, projenin açıklaması;
  • [7], projenin çalıştırılabilir sınıfının yerleştirileceği paket;
  • [8]'te, projenin türü. Bu, Thymeleaf görünümlerine sahip bir web projesidir. Burada, Spring Boot projesi tarafından sunulan kullanıma hazır tüm Maven bağımlılıklarını görebiliriz;
  • [9]'te, Maven derlemesinden elde edilen ürünün war değil, jar arşivi olarak paketleneceği belirtilir. Proje, bağımlılıkları arasında yer alacak yerleşik bir Tomcat sunucusu kullanacaktır;
  • [10] adımında, sihirbazın sonraki adımına geçilir;
  • [11]'te, proje klasörü belirtilir;
  • [12]'te, oluşturulan proje;
  • [14-15]'te, [istia.st.springmvc] paketini yeniden adlandırın;
  • [16] olarak, paketin yeni adı;
  • [17], yeni proje;

Şimdi yeni bir sınıf oluşturuyoruz;

  • [1-3]'te yeni bir sınıf oluşturuyoruz;
  • [5] adını veriyoruz ve [4]'te paketini belirliyoruz;
  • [6]'te yeni proje;

Sınıf şimdilik şu şekildedir:


package istia.st.springmvc;

public class ActionsController {

}

Bu kodu şu şekilde geliştiriyoruz:


package istia.st.springmvc;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class ActionsController {

}
  • 6. satır: [@RestController] anotasyonu iki şeyi belirtir:
    • bu şekilde etiketlenen [ActionsController] sınıfı, bir Spring denetleyicisi (MVC) olduğundan, istemcilerin URL'lerini işleyen eylemler içerir;
    • bu eylemlerin sonucu istemciye gönderilir;

Karşılaştığımız diğer [@Controller] anotasyonu ise farklıdır: bu şekilde anotasyonlanmış bir denetleyicinin eylemleri, görüntülenmesi gereken görünümün adını döndürür. Bu durumda, müşteriye gönderilen yanıtı sağlayan, bu görünüm ile eylem tarafından bu görünüm için oluşturulan modelin birleşimidir.

Projemizin yapısındaki değişiklik, projemizin yapılandırmasında da bir değişikliğe yol açar:

  

[Application] sınıfı şu şekilde değişir:


package istia.st.springmvc.main;

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

@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • 9. satır: [ComponentScan] anotasyonu, Spring Boot'un Spring bileşenlerini araması gereken paket adlarından oluşan bir diziyi parametre olarak kabul eder. Burada, [@RestController] ile anotlanmış denetleyicinin bulunabilmesi için bu diziye [istia.st.springmvc.controllers] paketini ekliyoruz;

Kontrolörde, temel özelliklerini göstermek amacıyla çeşitli eylemler oluşturacağız. Öncelikle, görünüm içermeyen bir uygulamada bir eylemin verebileceği çeşitli yanıt türlerine odaklanacağız.

3.2. [/a01, /a02] - Hello world

İlk eylemimiz şu şekilde olacaktır:


@RestController
public class ActionsController {
    // ----------------------- merhaba dünya ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • 4. satır: [RequestMapping] anotasyonu, anotasyonlu eylem tarafından işlenen isteği tanımlar:
    • [value] özniteliği, işlenen URL'tir,
    • [method] özniteliği kabul edilen yöntemi belirler;

Böylece [a01] yöntemi, HTTP [GET /a01] isteğini işler.

  • 5. satır: [a01] yöntemi, [String] türünde bir değer döndürür ve bu değer olduğu gibi istemciye gönderilir;
  • 6. satır: döndürülen dize;

Uygulamayı daha önce birçok kez yaptığımız gibi çalıştıralım, ardından [Advanced Rest Client] istemcisiyle, URL ve [/a01]'i, GET ve [1-2] ile birlikte talep edelim:

  • [3]'te, sunucunun yanıtı;
  • [4]'e, yanıtın başlıkları olan HTTP'e. Kullanılan kodlamanın [ISO-8859-1] olduğu görülüyor. UTF-8 kodlamasını tercih edebilirsiniz. Bu ayar yapılabilir;
  • [5] olarak ayarladıktan sonra, Chrome tarayıcısıyla aynı URL adresini tekrar ziyaret edelim;

[ActionsController] denetleyicisine aşağıdaki [/a02] eylemini ekliyoruz (böylece bazen URL ile bunu işleyen yöntemi eylem adı altında karıştırabiliriz):


    // ----------------------- aksanlı karakterler - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • 2. satır: [produces="text/plain;charset=UTF-8"] özniteliği, eylemin [UTF-8] biçiminde kodlanmış karakterler içeren bir metin akışı gönderdiğini belirtir. Bu biçim, özellikle aksanlı karakterlerin kullanımına olanak tanır;

Bu yeni eylemi dikkate almak için uygulamayı yeniden başlatmamız gerekir:

 

Sonuç şu şekildedir:

  • [1] formatında, sunucu tarafından gönderilen belgenin türünü görebiliriz;
  • [2-3]'te aksanlı karakterler doğru şekilde görünüyor;

3.3. [/a03]: XML akışını oluşturmak

Aşağıdaki [/a03] eylemini ekliyoruz:


    // ----------------------- text/xml ------------------------
    @RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a03() {
        String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
        return greeting;
}
  • 2. satır: [produces="text/xml;charset=UTF-8"] özniteliği, eylemin XML akışını [UTF-8] biçiminde kodlanmış karakterlerle gönderdiğini belirtir;

Yürütüldüğünde şu sonuç ortaya çıkar:

  • [1]'te, HTTP başlığı, gönderilen belgenin HTML olduğunu belirtir;
  • [2]'te, Chrome tarayıcısı alınan metni biçimlendirmek için bu bilgiyi kullanır;

Chrome'da, geliştirici penceresinden (Ctrl-Shift-I) istemci ile sunucu arasındaki HTTP iletişimlerine erişilebildiğini hatırlatalım:

Image

Bundan böyle, istemci ile sunucu arasındaki HTTP iletişimlerinin ekran görüntülerini her zaman almayacağız. Bazen, bu iletişimlerin metnini belirtmekle yetineceğiz.

3.4. [/a04, /a05]: jSON akışını oluşturma

Aşağıdaki [/a04] eylemini ekliyoruz:


    // ----------------------- jSON üretme ------------------------
    @RequestMapping(value = "/a04", method = RequestMethod.GET)
    public Map<String, Object> a04() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "un");
        map.put("2", new int[] { 4, 5 });
        return map;
}
  • 3. satır: Eylem, bir sözlük olan [Map] türünü döndürür. [@RestController] türündeki bir denetleyiciyle, eylemin sonucu müşteriye gönderilen yanıt olduğunu hatırlayalım. HTTP protokolü bir metin satırları alışveriş protokolü olduğundan, müşterinin yanıtı bir karakter dizisi olarak serileştirilmelidir. Bunun için Spring MVC, çeşitli [Objet <---> chaîne de caractères] dönüştürücüler kullanır. Belirli bir nesnenin bir dönüştürücüyle eşleştirilmesi, yapılandırma yoluyla gerçekleştirilir. Burada Spring Boot’un otomatik yapılandırması, projenin bağımlılıklarını inceleyecektir:
 

Yukarıdaki Jackson bağımlılıkları, nesneleri jSON dizelerine serileştirme/deserileştirme kütüphaneleridir. Spring Boot, eylemler tarafından döndürülen nesneleri serileştirmek/deserileştirmek için bu kütüphaneleri kullanacaktır. Java nesnelerini jSON formatında serileştirme/deserileştirmeye yönelik bir Java kod örneği 9.7. paragrafında yer almaktadır.

  1. satırda, gönderilen yanıtın türünü belirtmediğimize dikkat edin. Gönderilecek varsayılan türü inceleyeceğiz.

Chrome'da [1-3] ile elde edilen sonuçlar şunlardır:

Şimdi şu [/a05] eylemini ekleyelim:


    // ----------------------- jSON üretme - 2 ------------------------
    @RequestMapping(value = "/a05", method = RequestMethod.GET)
    public Personne a05() {
        return new Personne(1,"carole",45);
}

[Personne] sınıfı şöyledir:

  

package istia.st.sprinmvc.models;

public class Personne {

    // kimlik
    private Integer id;
    // ad
    private String nom;
    // yaş
    private int age;

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

    }

    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }

    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }

    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }

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

Çalıştırıldığında şu sonuçlar elde edilir:

  • [1]'te, sunucu gönderdiği belgenin jSON olduğunu belirtir;
  • [2]'te, alınan belge jSON'tir;

3.5. [/a06]: boş bir akış döndürme

Aşağıdaki [/a06] eylemini ekliyoruz:


    // ----------------------- akışı boşalt ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • 3. satırda, [/a06] eylemi hiçbir şey döndürmez. Spring MVC, bu durumda istemciye boş bir yanıt üretecektir;

Çalıştırma sonucunda şu sonuçlar elde edilir:

 

Yukarıda, yanıtta yer alan HTTP [Content-Length] özniteliği, sunucunun boş bir belge gönderdiğini gösterir.

3.6. [/a07, /a08, /a09]: [Content-Type] ile akış türü

Aşağıdaki [/a07] eylemini ekliyoruz:


    // ----------------------- text/html ------------------------
    @RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
    public String a07() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • 2. satırda, [/a07] eylemi bir HTML akışı döndürür [text/html] ;
  • 4. satır: bir HTML dizesi;

Çalıştırma sonucunda şu sonuçlar elde edilir:

  • [1]'te, Chrome'un HTML <h1> etiketini yorumlayarak içeriğini büyük harflerle görüntülediğini görüyoruz;

Şimdi aynı işlemi aşağıdaki [/a08] eylemiyle de yapalım:


    // ----------------------- HTML sonucunu text/plain olarak ------------------------
    @RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String a08() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • 2. satır: eylemin yanıtı [text/plain] türündedir;

Sonuçlar şu şekildedir:

  • [1] örneğinde, Chrome, HTML <h1> etiketini yorumlamadı çünkü sunucu, ona [text/plain] [2] akışını gönderdiğini bildirdi;

Şimdi, aşağıdaki [/a09] eylemiyle benzer bir şeyi tekrar deneyelim:


    // ----------------------- HTML sonucunu text/xml olarak ------------------------
    @RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a09() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • 2. satır: [text/xml] türünde bir akış gönderilir;

Sonuçlar şu şekildedir:

  • [1]'te, Chrome, HTML <h1> etiketini yorumlamadı çünkü sunucu, ona bir [text/xml] [2] akışı gönderdiğini bildirdi. Bunun üzerine <h1> etiketini bir XML etiketi olarak işledi;

Bu örneklerden, sunucunun yanıtındaki HTTP [Content-Type] başlığının önemi anlaşılmaktadır. Tarayıcı, aldığı belgeyi nasıl yorumlayacağını belirlemek için bu başlığı kullanır;

3.7. [/a10, /a11, /a12]: istemciyi yeniden yönlendirme

Yeni bir denetleyici oluşturuyoruz [RedirectController]:

 

[RedirectCntroller] kodunun şimdilik içeriği şu şekilde olacaktır:


package istia.st.springmvc.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class RedirectController {
}
  • 7. satır: [@Controller] etiketini kullanıyoruz; bu sayede artık varsayılan olarak eylem sonuçlarının [String] türü, bir eylemin veya görünümün adını belirtir;

Aşağıdaki [/a10] eylemini oluşturuyoruz:


    // ------------ üçüncü taraf eylemine yönlendirme -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • 4. satır: Sonuç olarak bir eylemin adı olan 'a01' değerini döndürüyoruz. Böylece, müşteriye yanıtı gönderen bu eylem olacaktır;

İşte bir örnek:

  • [2]'te, [/a01] eyleminin akışını aldık;
  • [3]'te, tarayıcı [/a10] eyleminin URL'ini görüntüler;

Şimdi şu [/a11] eylemini oluşturuyoruz:


    // ------------ üçüncü taraf eylemine geçici 302 yönlendirmesi -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Şu sonuçları elde ediyoruz:

  • Chrome günlüklerinde [1-2]'te iki istek görülüyor; biri [/a11]'e, diğeri [/a01]'e yöneliktir;
  • [3] adresinde, sunucu, istemci tarayıcısından [302] koduyla yanıt verir ve tarayıcıyıURL adresine yönelmesini ister. HTTP, [Location:] ve [4] başlıklarında belirtilen adrese. [302] kodu geçici bir yönlendirme kodudur;

Tarayıcı daha sonra yönlendirme adresi olan URL'e ikinci isteği gönderir:

  • [5]'e, yani istemcinin ikinci isteğine;
  • [6]'e yönlendirilir; istemci tarayıcısı, yönlendirme isteğinin URL kodunu görüntüler;

Kalıcı bir yönlendirme belirtmek istenebilir; bu durumda, istemciye aşağıdaki HTTP başlığı gönderilmelidir:

HTTP/1.1 301 Moved Permanently 

Bu, yönlendirmenin kalıcı olduğu anlamına gelir. Geçici (302) ve kalıcı (301) yönlendirme arasındaki bu fark, bazı arama motorları tarafından dikkate alınır.

Bu kalıcı yönlendirmeyi gerçekleştirecek [/a12] eylemini yazıyoruz:


    // ------------ üçüncü taraf eylemine kalıcı 301 yönlendirmesi----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • 3. satır: Spring'den MVC'e, müşteriye gönderilen yanıtı kapsayan [HttpServletResponse] nesnesini enjekte etmesini istiyoruz;
  • 4. satır: Yanıtın [status]'i, başlığın [301]'i ve HTTP'i belirlenir:
HTTP/1.1 301 Moved Permanently
  • 5. satır: Aşağıdaki HTTP başlığını manuel olarak oluşturulur:
Location: /a01 

Bu, yönlendirme başlığı olan URL'tir.

Çalıştırıldığında şu sonuçlar elde edilir:

Bu örnekten öğrenilecek nokta şudur:

  • HTTP yanıt durumunu oluşturmak;
  • yanıtta HTTP başlığını ekleme;

3.8. [/a13]: tam yanıtı oluşturmak

[ResponsesController] sınıfındaki aşağıdaki eylemde gösterildiği gibi, yanıtı tamamen kontrol etmek mümkündür:

  

    // ----------------------- yanıtın tamamen oluşturulması ------------------------
    @RequestMapping(value = "/a13")
    public void a13(HttpServletResponse response) throws IOException {
        response.setStatus(666);
        response.addHeader("header1", "qq chose");
        response.addHeader("Content-Type", "text/html;charset=UTF-8");
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        response.getWriter().write(greeting);
}
  • 3. satır: eylemin sonucu [void]'tir. Bu durumda, istemciye boş olmayan bir yanıt göndermek için Spring tarafından sağlanan [HttpServletResponse response] nesnesini kullanmak gerekir MVC;
  • 4. satır: Yanıtta, istemci tarafından tanınmayacak bir durum atarız;
  • 5. satır: İstemci tarafından tanınmayacak bir HTTP başlığı eklenir;
  • 6. satır: Gönderilecek akış türünü belirtmek için HTTP [Content-Type] başlığı eklenir; burada HTML;
  • 7-8. satırlar: yanıtta HTTP başlıklarının ardından gelecek olan belge;

Sonuçlar şu şekildedir:

  • [1]'te yanıtımızdaki öğeleri tanıyoruz;
  • [2-3]'te, Chrome'un şu gerçeği göz ardı ettiği görülüyor:
    • yanıtın HTTP durumunun tanınan bir HTTP durumu olmadığını,
    • [header1] başlığı, tanınan bir HTTP başlığı değildi;

İstemci bir tarayıcı değil de programlanmış bir istemci ise, istenilen durum kodları ve başlıklar serbestçe kullanılabilir.