Spring Web MVC Nedir? Kurulumu ve Kullanımı

Spring framework içerisinde yer alan Spring MVC nedir kurulumu ve kullanımı örneklerle birlikte yer alıyor.

Spring Web MVC nedir?

Spring framework içerisinde Servlet ve Spring modülleri ile birlikte MVC mimarisine göre web tabanlı uygulamalar geliştirmek için kullanılan bir modüldür.

Servlet hakkında detaylı bilgi almak için Java Servlet yazıma bakmalısın.

Spring framework hakkında detaylı bilgi almak için Spring Framework yazıma bakmalısın.

MVC nedir?

MVC veya Model-View-Controller geliştirmenin farklı parçalara ayrılarak kolay yönetilmesini sağlayan tasarım mimarisidir.

Model – Verilerin modellendiği genellikle sıradan Java sınflarının(POJO) kullanıldığı bölümdür.

View – Sonucun gösterildiği bölümdür.

Controller – Gelen isteğe göre genellikle model içerisinde yer alan verileri kullanarak işlem yapan ve sonucu view katmanına ileten bölümdür.

NOT: Spring Web MVC veya daha bilinen adıyla Spring MVC geliştirme sırasında MVC tasarım mimarisini kullanarak kolay, anlaşılır uygulama geliştirmeyi sağlar.

Spring MVC kurulumu

Maven içerisinde yer alan archetype özelliğini kullanarak web projesi oluşturalım.

mvn archetype:generate 
-DgroupId=com.yusufsezer 
-DartifactId=SpringWebMVC 
-DarchetypeArtifactId=maven-archetype-webapp 
-DinteractiveMode=false

Maven hakkında detaylı bilgi almak için Maven yazıma bakmalısın.

Paket, arayüz ve sınıfların yer aldığı spring-webmvc kütüphanesi pom.xml eklenerek Spring MVC kurulumu tamamlanır.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

Spring MVC JCP tarafından belirlenen JSR 369(Servlet), JSR 380(Bean Validation), JSR 330(Dependency Injection) gibi şartnameleri kullanır.

Her bir şartnameye ait paketin tek tek eklenmesi yerine JEE 8 paketinin projeye eklenmesi karmaşıklığı azaltacaktır.

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>8.0</version>
    <scope>provided</scope>
</dependency>

Spring MVC için gerekli olan diğer spring modülleri maven tarafından projeye dahil edilecektir.

Spring MVC kullanımı

Spring MVC uygulama ayarlarını XML tabanlı(web.xml) ve Java Tabanlı(WebApplicationInitializer) olarak yapmayı destekler.

Geniş kullanım desteğinden dolayı XML tabanlı olarak basit bir “Merhaba Spring MVC!” uygulaması hazırlayalım.

İlk olarak web.xml dosyasına Spring MVC ayarlarını ekleyelim.

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!--<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>-->

    <servlet>
        <servlet-name>SpringWebMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>SpringWebMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

ContextLoaderListener ile belirtilen ayar dosyasında tüm uygulama için gerekli olan bean tanımları(veri kaynağı, güvenlik vb.) yer alır ve kullanımı zorunlu değildir.

ContextLoaderListener RootContext bean ayarlarını varsayılan olarak WEB-INF dizininde applicationContext.xml olarak arayacaktır.

DispatcherServlet ile belirtilen ayar dosyalarında ise url-pattern ile belirtilen isteklerde çalışacak bean tanımları(controller, viewresolver vb.) yer alır.

DispatcherServlet bean ayarlarını varsayılan olarak WEB-INF dizininde [servlet-name]-servlet.xml olarak arayacaktır.

Bir uygulama içerisinde birden fazla DispatcherServlet yer alabilir.

Bir DispatcherServlet diğer DispatcherServlet için tanımlanan ayarlara erişmezken ContextLoaderListener ile belirtilen ayarlara tüm DispatcherServlet erişebilir.

Ayar dosyasını koşula uygun olarak SpringWebMVC-servlet.xml dosyasında hazırlayalım.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd 
                            http://www.springframework.org/schema/mvc 
                            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <mvc:annotation-driven />
    <context:component-scan base-package="com.yusufsezer" />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

mvc:annotation-driven – MVC desteğini aktif hale getirir.

context:component-scanbase-package ile belirtilen paket içerisinde stereotype ifadelerini(@Component, @Controller, @Service, @Repository) arar.

InternalResourceViewResolver – View katmanında kullanılacak olan görüntüleleme motorunu(template engine) ayarlar.

İlk olarak modellemeyi model paketinde Mesaj sınıfı olarak yapalım.

public class Mesaj {

    private String mesaj;

    public String getMesaj() {
        return mesaj;
    }

    public void setMesaj(String mesaj) {
        this.mesaj = mesaj;
    }

}

Gelen istekleri yönetmek için controller paketinde HomeController sınıfını hazırlayalım.

@Controller
public class HomeController {

    @GetMapping
    public String index(ModelMap modelMap) {
        Mesaj mesaj = new Mesaj();
        mesaj.setMesaj("Merhaba Spring MVC!");
        modelMap.addAttribute("mesaj", mesaj.getMesaj());
        return "index";
    }

}

NOT: RequestMapping, GetMapping vb. ifadelerin kullanımı RequestMappingHandlerMapping tarafından sağlanır.

Son olarak verilerin gösterileceği görünüm dosyasını WEB-INF/views/ dizine index.jsp olarak hazırlayalım.

<html>
<body>
<h1>${mesaj}</h1>
</body>
</html>

Sayfaya ulaştığımızda Mesaj sınıfı ile oluşturulup ModelMap#addAttribute metodu ile View katmanına gönderilen mesaj görünecektir.

Spring MVC nasıl çalışır

Servlet container tarafından uygulamaya ait ayarlar XML(web.xml) veya Java Tabanlı(ServletContainerInitializer) olarak alınır ve işlem başlatılır.

Servlet başlangıcında ServletContextListener arayüzünü uygulayan bir sınıf(ContextLoaderListener) belirtilirse(listener-class, addListener) Servlet başlatıldığında(contextInitialized) Servlet context alanına Spring MVC RootContext yüklemesi yapar.

RootContext varsayılan olarak ContextLoader.properties dosyasında belirtilen context sınıfını(XmlWebApplicationContext) kullanır.

RootContext oluşturulduktan sonra ayar dosyasında belirtilen servlet(servlet-class, addServlet) sınıfının(DispatcherServlet) bir örneği oluşturularak Servlet yaşam döngüsü çalıştırılır.

DispatcherServlet

DispatcherServlet Spring MVC tarafından Front-Controller olarak kullanılan sıradan bir Servlet sınıfıdır. (DispatcherServlet->FrameworkServlet->HttpServletBean->HttpServlet)

Servlet başlatıldığında Servlet yaşam döngüsü(HttpServletBean#init) çalıştırılarak Spring MVC ayarları yapılır.

Servlet yaşam döngüsü başlatıldığında çalışan init metodu parametreleri(contextClass, contextConfigLocation vb.) Spring MVC içerisinde kullanmak üzere yükler ve context oluşturma işlemi(FrameworkServlet#initServletBean) başlatılır.

Context başlatma sırasında Spring MVC işlemlerinde kullanılacak olan sınıflar-çözümleyiciler yüklenir. (DispatcherServlet#initStrategies)

initMultipartResolver(context);  // upload işlemleri
initLocaleResolver(context);  // i18n işlemleri
initThemeResolver(context);  // theme işlemleri
initHandlerMappings(context);  // istek mapleme işlemleri
initHandlerAdapters(context);  // istek bağlama işlemleri
initHandlerExceptionResolvers(context);  // istisna işlemleri
initRequestToViewNameTranslator(context);  // otomatik view işlemleri
initViewResolvers(context);  // view işlemleri
initFlashMapManager(context);  // tek-kullanımlık mesaj işlemleri

Metotlar kullanılacak sınıf tanımını bean tanımlarında([servlet-name]-servlet.xml vb.) arayarak DispatcherServlet sınıfında yer alan değerlere atar.

Bean tanımında yer almayan sınıflar varsayılan olarak DispatcherServlet.properties dosyasında tanımlı değerleri kullanır.

Örneğin; Yukarıdaki initViewResolvers metodu view katmanında yer alan görüntüleme motorunu belirlemek için kullanılır.

Spring MVC kullanımı başlığındaki XML bean tanımında InternalResourceViewResolver bean tanımı yapılmayıp controller içerisinde index yerine /WEB-INF/views/index.jsp kullanıldığında DispatcherServlet varsayılan sınıfı kullanarak benzer sonucu verecektir.

Servlet yaşam döngüsü tamamlandıktan sonra her eşleşen(url-pattern, addMapping) istek DispatcherServlet tarafından yukarıda atanan sınıflar-çözümleyiciler ile işlenerek sonuç döndürülür.

HandlerMapping

Gelen isteklerin hangi kurala göre işleneceği(mapleneceğini) belirlemek için kullanılan sınıflardır.

Örneğin; BeanNameUrlHandlerMapping sınıfı ters eğik çizgi(/) ile başlayan bean adlandırmasına göre eşleme yapmak için kullanılır.

<bean name="/giris" class="com.yusufsezer.controller.GirisController" />

Bean tanımlamasına göre /giris adresine gelen isteklerin karşılanacağı GirisController sınıfını hazırlayalım.

public class GirisController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        ModelMap modelMap = new ModelMap();
        Mesaj mesaj = new Mesaj();
        mesaj.setMesaj("Merhaba BeanNameUrlHandlerMapping!");
        modelMap.addAttribute("mesaj", mesaj.getMesaj());
        return new ModelAndView("index", modelMap);
    }

}

İsteklerin işlenebilmesi için sınıfın AbstractController soyut sınıfınından kalıtım alarak handleRequestInternal metodunun düzenlemesi gerekir.

Spring MVC içerisinde BeanNameUrlHandlerMapping sınıfına benzer varsayılan olarak gelen veya tanım ile kullanılan çeşitli HandlerMapping sınıfları yer alır.

Örnekte olduğu gibi her bir adres için ayrı bean tanımının yapılması ve sınıfların AbstractController kullanımı Spring MVC bağımlılığın artmasına neden olacaktır.

Bundan dolayı varsayılan olarak gelen, esnek ve geniş kullanım desteği sunan annotation tabanlı RequestMappingHandlerMapping kullanımı faydalı olacaktır.

Annotation hakkında detaylı bilgi almak için Java Annotations yazıma bakmalısın.

@Controller
public class GirisController {

    @GetMapping("/giris")
    protected String giris(ModelMap modelMap) {
        Mesaj mesaj = new Mesaj();
        mesaj.setMesaj("Merhaba RequestMappingHandlerMapping!");
        modelMap.addAttribute("mesaj", mesaj.getMesaj());
        return "index";
    }

}

Spring MVC, Servlet 3.0 ile birlikte servlet ayarlarını(web.xml), bean tanımlarını([servlet-name]-servlet.xml) XML kullanmadan annotation ile kullanımı sağlar.

Spring MVC anotation kullanımı

Servlet 3.0 ile birlikte Servlet ayarlarını Java tabanlı olarak kullanımı için SPI(Service Provider Interface) sağlar.

Özelliğin kullanımı META-INF/services dizininde arayüz dosyası(javax.servlet.ServletContainerInitializer) içerisinde arayüzü kullanan sınıf yolunun(org.springframework.web.SpringServletContainerInitializer) belirtilmesi yeterli olacaktır.

Spring SPI özelliğini kullanarak Java tabanlı olarak Servlet ayarlarını yapmayı destekler.

Servlet tarafından SpringServletContainerInitializer sınıfı @HandlesTypes(WebApplicationInitializer.class) ifadesiyle WebApplicationInitializer arayüzünü uygulayan tüm sınıflar onStartup metoduna parametre olarak gönderilir.

Metot tüm WebApplicationInitializer arayüzünü uygulayan sınıfları tarayarak onStartup metodunu çalıştırır.

Spring MVC kurulumunda yer alan örneği annotation yapısına çevirmek için spring context oluşturduktan sonra DispatcherServlet örneği oluşturarak ServletContext ile eklemek yeterli olacaktır.

public class Initializer implements
        WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext)
            throws ServletException {
        AnnotationConfigWebApplicationContext context;
        context = new AnnotationConfigWebApplicationContext();
        context.register(WebConfig.class);
        context.setServletContext(servletContext);

        //servletContext.addListener(new ContextLoaderListener(context));
        DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic servlet;
        servlet = servletContext.addServlet("SpringWebMVC", dispatcherServlet);
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/");
    }

}

Spring bean tanımlarının yer aldığı sınıf(WebConfig) aşağıdaki gibi annotation tabanlı olarak kullanılır.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yusufsezer")
public class WebConfig {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

}

NOT: Context oluşturma işlemi AnnotationConfigWebApplicationContext yerine XmlWebApplicationContext ile XML ayarları kullanılabilir.

Context tanımı, Servlet tanımı ve diğer ayarları ayrı ayrı yapmak yerine Spring MVC ile gelen AbstractAnnotationConfigDispatcherServletInitializer soyut sınıfının kullanımı faydalı olacaktır.

public class Initializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null; // data, security ayarları
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

Soyut sınıf Context oluşturma, Servlet tanımı ve diğer ayarları içsel olarak yapar ve çeşitli metotlar üzerinden özelleştirilebilir tanımlı değerleri alarak işlemi tamamlar.

XML ayarları(web.xml) kullanılmadan oluşturulan war dosyalarındaki uyarıyı gidermek için pom.xml dosyasına aşağıdaki değerin eklenmesi gerekebilir.

<properties>
    <failOnMissingWebXml>false</failOnMissingWebXml>
</properties>

@RequestMapping

RequestMappingHandlerMapping gelen istekleri yönetmek için @RequestMapping annotation ifadesini kullanır.

@Controller
@RequestMapping("/ozel")
public class HomeController {

    @RequestMapping("/sayfa")
    public ResponseEntity<String> sayfa() {
        return ResponseEntity.ok("Merhaba Spring MVC!");
    }

}

Annotation aşağıda yer alan özellikleri kullanarak gelen istekleri sınıf ve metotlarla eşler.

  • value & path – eşleşecek istek adres(leri)
  • headers – üst bilgi
  • consumes – istek formatı
  • produces – cevap formatı
  • method – HTTP yöntemi
  • name – istek adlandırması
  • params – istek parametresi

Aşağıdaki ifadeler /sayfa adresine gelen istekleri karşılamak için kullanılır.

@RequestMapping("/sayfa")
@RequestMapping(value = "/sayfa")
@RequestMapping(path = "/sayfa")

İstekleri yönetmek için wilcards-joker karakter(*) veya regex-düzenli ifadeler(\w, \d, + vb.) kullanılabilir.

@RequestMapping("/sayfa*") // sayfa ile başlayan istekler
@RequestMapping("/*sayfa") // sayfa ile biten istekler
@RequestMapping("/*sayfa*") // sayfa geçen istekler
@RequestMapping("/sayfa{sayfa:[1-9]}") // sayfa1, sayfa2, ... vb. istekler

NOT: Düzenli ifadeler geniş kullanıma sahiptir.

Düzenli ifadeler ile tanımlanan istekler dinamik URL olarak adlandırılır ve değeri almak için @PathVariable ifadesi kullanılır.

@RequestMapping({"/sayfa", "/sayfa/{id}"})
public ResponseEntity<String> sayfa(
        @PathVariable(value = "id", required = false) Long id
) {
    return ResponseEntity.ok(String.valueOf(id));
}

@RequestMapping ifadesindeki değer(id) ile metot parametresindeki değer(id) aynı ise @PathVariable ifadesine herhangi bir değer belirtmeden kullanılabilir.

@RequestMapping({"/sayfa", "/sayfa/{id}"})
public ResponseEntity<String> sayfa(@PathVariable(required = false) Long id) {
    return ResponseEntity.ok(String.valueOf(id));
}

Birden fazla isteğin tek metot tarafından yönetilmesi için adresler dizi olarak eklenir.

@RequestMapping({"anasayfa", "homepage", "home*"})

Üst bilgi değerine göre eşleme yapmak için headers kullanılır.

@RequestMapping(path = "/sayfa", headers = "ozel=bilgi")
@RequestMapping(path = "/sayfa", headers = {"ozel=bilgi", "Content-Type=text/html"})

İstek formatına(Content-Type) göre eşleme yapmak için consumes kullanılır.

@RequestMapping(path = "/sayfa", consumes = "text/plain")
@RequestMapping(path = "/sayfa", consumes = MediaType.TEXT_HTML_VALUE)
@RequestMapping(path = "/sayfa", consumes = {"text/plain", MediaType.TEXT_HTML_VALUE})

İsteğin kabul ettiğin formata(Accept) göre eşleme yapmak için produces kullanılır.

@RequestMapping(path = "/sayfa", produces = "text/plain")
@RequestMapping(path = "/sayfa", produces = MediaType.TEXT_HTML_VALUE)
@RequestMapping(path = "/sayfa", produces = {"text/plain", MediaType.TEXT_HTML_VALUE})

consumes ve produces özellikleri ayrıca headers özelliğine Content-Type, Accept eklenerek kullanılabilir.

NOT: Yazım hatalarının önüne geçmek için standart MIME tanımlarının yer aldığı MediaType sınıfını kullanmak faydalı olacaktır.

HTTP yöntemine göre eşleme yapmak için method ve RequestMethod kullanılabilir.

@RequestMapping(path = "/sayfa", method = RequestMethod.HEAD)
@RequestMapping(path = "/sayfa", method = {RequestMethod.GET, RequestMethod.POST})

Spring MVC okunabilirliği arttırmak için HTTP yöntemlerine(GET, POST, PUT vb) göre adlandırılan aşağıdaki ifadelere sahiptir.

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

NOT: Oluşturulan annotation ifadeleri içsel olarak @RequestMapping(method = RequestMethod.GET) gibi @RequestMapping ifadesini kullanılır.

NOT: İfadeler sadece metotlarda kullanılır.

İstekleri adlandırmak için name kullanılır.

@RequestMapping(path = "/sayfa", name = "sayfaController")

İstek paremetresine(?id=1, ?id=1&sort=name) göre eşleme yapmak için params kullanılır.

@RequestMapping(path = "/sayfa", params = "id")
@RequestMapping(path = "/sayfa", params = {"id", "sort"})

Parametleri almak için @RequestParam kullanılır.

@RequestMapping("/sayfa")
public ResponseEntity<String> sayfa(
        @RequestParam(value = "id", required = false, defaultValue = "1453") Long id
) {
    return ResponseEntity.ok(String.valueOf(id));
}

Parametre değeri(id) ile metot parametre değeri(id) aynı ise @RequestParam ifadesine herhangi bir değer belirtmeden kullanılabilir.

@RequestParam ifadesinde yer alan required özelliği parametrenin gerekliğini, defaultValue ile herhangi bir değer verilmediğinde alacağı değeri belirtir.

Birden fazla değer göndermek için kullanılan ve matrix olarak adlandırılan anahtar=değer; parametreleri için @MatrixVariable kullanılır.

Özelliği aktif etmek için XML

<mvc:annotation-driven enable-matrix-variables="true" />

veya Java tabanlı ayarların yapılması gerekir.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yusufsezer")
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }

    // diğer ayarlar

}

@MatrixVariable ifadesi value, pathVar, required, defaultValue gibi çeşitli özelliklere sahiptir.

@RequestMapping("/sayfa/{meyve}")
public ResponseEntity<String> sayfa(
        @MatrixVariable(value = "meyve", required = false, defaultValue = "Yok") String meyve
) {
    return ResponseEntity.ok(malzeme);
}

sayfa/meyve=elma,armut,karpuz gibi bir istek metodun meyve parametresine aktarılacaktır.

@RequestMapping("/sayfa/{malzeme}")
public ResponseEntity<String> sayfa(
        @MatrixVariable String malzeme, @MatrixVariable int sayi
) {
    return ResponseEntity.ok(malzeme + " " + String.valueOf(sayi));
}

sayfa/meyve=elma,armut,karpuz;sayi=3 gibi bir istek metodun meyve ve sayi parametresine aktarılacaktır.

Tüm değerlere erişmek için MultiValueMap kullanılabilir.

@RequestMapping("/sayfa/{malzeme}")
public ResponseEntity<String> sayfa(
        @MatrixVariable MultiValueMap<String, String> tumdegerler
) {
    System.out.println(tumdegerler);
    return ResponseEntity.ok("");
}

@RequestMapping method arguments

@RequestMapping ile belirtilen metotlar Spring Core Dependency Injection özelliği sayesinde Spring MVC ve Servlet tarafından sağlanan sınflara erişerek çeşitli işlemleri yapabilir.

HttpServletRequest veya HttpServletResponse erişerek Servlet ile yapılan session, cookie, parametre, header gibi bilgileri alma, düzenleme işlemleri yapılabilir.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(HttpServletRequest request) {
    String collect = Collections.list(request.getHeaderNames())
            .stream()
            .map(p -> p.concat(": ").concat(request.getHeader(p)))
            .collect(Collectors.joining("<br />"));
    return ResponseEntity.ok(collect);
}

Session işlemleri için HttpServletRequest#getSession metodu kullanılabileceği gibi doğrudan HttpSession arayüzü de kullanılabilir.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(HttpServletRequest request, HttpSession httpSession) {
    boolean esit = request.getSession().equals(httpSession);
    return ResponseEntity.ok("Eşit" + (esit ? "tir" : "değildir."));
}

Parametre olarak kullanılan bazı arayüz ve sınıflar;

  • WebRequest
  • NativeWebRequest
  • HttpEntity
  • UriComponentsBuilder
  • Principal
  • HttpMethod
  • Locale
  • TimeZone
  • InputStream
  • Reader
  • OutputStream
  • Writer
  • ve Map, Model, ModelMap
  • ve Errors, BindingResult

Arayüz ve sınıf kullanılarak yapılan işlemler HttpServletRequest ile HttpServletResponse üzerinden de yapılabilir.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(HttpServletRequest request, InputStream inputStream)
        throws Exception {
    boolean esit = request.getInputStream().equals(inputStream);
    return ResponseEntity.ok("Eşit" + (esit ? "tir" : "değildir."));
}

Spring MVC bu işlemleri kolaylaştırmak için çeşitli annotation ifadelerine sahiptir ve tür dönüşümü(type conversion) ile bu bilgilere doğrudan erişim sağlar.

@RequestHeader

Header bilgilerine erişim için @RequestHeader ifadesi kullanılır.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestHeader("user-agent") String userAgent) {
    return ResponseEntity.ok(userAgent);
}

Header ifadesi(örn; accept) ile değişken aynı ada sahipse parametresiz olarak kullanılabilir.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestHeader String accept) {
    return ResponseEntity.ok(accept);
}

Tüm değerlere erişim için @RequestHeader ile birlikte Map türü(Map, MultiValueMap, HttpHeaders vb.) kullanılabilir.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestHeader HttpHeaders httpHeaders) {
    System.out.println(httpHeaders);
    return ResponseEntity.ok(httpHeaders.toString());
}

NOT: Benzer durum bir çok Spring MVC annotation ifadesinde de geçerlidir.

@RequestParam

Form veya adres(?firstName=Yusuf) ile gönderilen değerleri almak için @RequestParam kullanılır.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestParam MultiValueMap<String, String> multiValueMap) {
    return ResponseEntity.ok(multiValueMap.toString());
}

Form veya adres ile gönderilen tüm değerler multiValueMap değerine atanacaktır.

@CookieValue

Çerez değerlerine erişmek için @CookieValue ifadesi kullanılır.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(
        @CookieValue String JSESSIONID,
        @CookieValue("JSESSIONID") String cerez
) {
    return ResponseEntity.ok(JSESSIONID + "<br />" + cerez);
}

Çerez işlemleri Cookie sınıfı ile birlikte HttpServletResponse#addCookie metodu kullanılır.

@SessionAttribute

Session değerlerine erişmek için @SessionAttribute kullanılır.

Spring MVC ile session erişimi için ilk olarak HttpSession ile session oluşturalım ve @SessionAttribute ifadesi ile oluşturulan session değerine ulaşalım.

@GetMapping("/olustur")
public ResponseEntity<?> olustur(HttpSession httpSession) {
    httpSession.setAttribute("web", "www.yusufsezer.com");
    return ResponseEntity.ok("Session oluşturuldu.");
}

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@SessionAttribute String web) {
    return ResponseEntity.ok(web);
}

@RequestAttribute

İstek sırasında Request alanına atanan değerlere erişim için @RequestAttribute kullanılır.

Spring MVC DispatcherServlet sınıfını yüklerken oluşturulan sınıflara @RequestAttribute ile erişebiliriz.

@GetMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT") WebApplicationContext wac) {
    return ResponseEntity.ok(Arrays.deepToString(wac.getBeanDefinitionNames()));
}

Spring MVC dosya yükleme(upload) işlemlerinde @RequestParam veya @RequestPart ile birlikte MultipartFile veye Part sınıfı kullanılabilir.

@PostMapping("/yukle")
public ResponseEntity<?> yukle(@RequestParam("dosya") MultipartFile multipartFile) {
    return ResponseEntity.ok(multipartFile.getOriginalFilename());
}

NOT: Dosya yükleme işlemi için MultipartResolver ayarlarının yapılması gerekir.

@RequestBody

Gelen istekleri Java sınıfları ile eşlemek için @RequestBody kullanılır.

@PostMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestBody Mesaj mesaj) {
    return ResponseEntity.ok(mesaj.getMesaj());
}

Spring MVC header(Content-Type) bilgisine bakıp HttpMessageConverter ile ayrıştırarak sınıf oluşturur.

Örneğin; Content-Type değeri application/json ile gelen {“mesaj”: “Yusuf SEZER”} değerini işleyebilmek için jackson paketinin projeye eklenmesi gerekir.

@RequestBody ile form verilerine erişmek için MultiValueMap kullanılır.

@PostMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestBody MultiValueMap<String, String> multiValueMap) {
    return ResponseEntity.ok(multiValueMap.toString());
}

Gelen tüm istekleri ayrıştırmadan doğrudan erişmek için String veri türü kullanmak yeterli olacaktır.

@PostMapping("/sayfa")
public ResponseEntity<?> sayfa(@RequestBody String gelenVeri) {
    return ResponseEntity.ok(gelenVeri);
}

NOT: Spring MVC REST API oluştururken @RequestBody kullanımı faydalı olacaktır.

HttpEntity

Gönderilen veriler ile birlikte header bilgisini almak için HttpEntity sınıfı kullanılabilir.

@PostMapping("/sayfa")
public ResponseEntity<?> sayfa(HttpEntity<Mesaj> httpEntity) {
    System.out.println(httpEntity.getHeaders());
    System.out.println(httpEntity.getBody());
    return ResponseEntity.ok(httpEntity);
}

HttpEntity sınıfı @RequestBody ifadesine ek olarak header bilgisine erişim imkanı verir.

@ModelAttribute

Spring MVC içerisinde geniş kullanımı olan @ModelAttribute ifadesi @RequestParam, @PathVariable, Model  vb. ifadelerin yerine kullanılabilir.

@RequestMapping("/sayfa/{merhaba}")
public ResponseEntity<String> sayfa(
        @ModelAttribute(value = "merhaba") Long id
) {
    return ResponseEntity.ok(String.valueOf(id));
}

/sayfa/1, /sayfa/2 vb. istekler @ModelAttribute tarafından alınacaktır.

@RequestMapping("/sayfa")
public ResponseEntity<String> sayfa(
        @ModelAttribute(value = "id") Long id
) {
    return ResponseEntity.ok(String.valueOf(id));
}

/sayfa?id=1, /sayfa?id=2 vb. istekler @ModelAttribute tarafından alınacaktır.

@ModelAttribute ifadesi view katmanına değer göndermek için kullanılan Model, ModelMap gibi de kullanılabilir.

Spring kullanımı örneğinde ModelMap yerine @ModelAttribute ifadesini kullanalım.

@Controller
public class HomeController {

    @GetMapping
    public String index() {
        return "index";
    }

    @ModelAttribute("mesaj")
    public String modelOlustur() {
        return "Merhaba Spring MVC!";
    }

}

Spring MVC ilk olarak @ModelAttribute ifadesi ile belirtilen metodu çalıştıracak ve sonucu Model, ModelMap değerlerinin saklandığı alana mesaj olarak kayıt edecektir.

Kayıt edilen değere view içerisinden ${mesaj} ile, diğer tüm değerlere ise ${requestScope} ile erişim sağlanacaktır.

NOT: View türüne göre erişim farklılık gösterebilir.

@ModelAttribute form verilerini Java sınıfları ile eşlemek içinde kullanılır.

Aşağıdaki dosyayı form.jsp olarak kayıt edelim.

<html>
<body>
    <form method="post">
        <label>Mesaj: </label>
        <input name="mesaj" />
        <input type="submit" />
    </form>
</body>
</html>

NOT: Form giriş elamanının name özelliği ile Java sınıf elemanının aynı ada sahip olduğuna dikkat edin!

@Controller
public class HomeController {

    @GetMapping
    public String index() {
        return "form";
    }

    @PostMapping
    public ResponseEntity<?> sayfa(@ModelAttribute Mesaj mesaj) {
        return ResponseEntity.ok(mesaj.getMesaj());
    }

}

@ModelAtrribute ifadesi gönderilen form verilerini Java sınıfı ile eşleyecektir.

Spring MVC ayrıca @ModelAttribute kullanmadan doğrudan Java sınıfını maplemeyi de destekler.

@PostMapping
public ResponseEntity<?> sayfa(Mesaj mesaj) {
    return ResponseEntity.ok(mesaj.getMesaj());
}

@RequestMapping return values

Spring MVC @RequestMapping ile ifade edilen metotlar çeşitli değerleri bağlayabileceği gibi metot tarafından döndürülen değerleri de işleyebilmektedir.

@ResponseBody

Metot tarafından döndürülen değeri dönüştürme(HttpMessageConverter) işlemi ile yazdırır.

@GetMapping
@ResponseBody
public String index() {
    return "Merhaba Spring MVC!";
}

@ResponseBody sınıflar içinde kullanılabilir.

@GetMapping
@ResponseBody
public Mesaj index() {
    Mesaj mesaj = new Mesaj();
    mesaj.setMesaj("Merhaba Spring MVC!");
    return mesaj;
}

Uygun tür dönüşümü bulunmadığında(HttpMessageConverter) hata verecektir.

Hatayı gidermek için çıktı olarak kullanılacak formata göre Jackson JSON, Jackson XML gibi kütüphanelerin eklenmesi gerekir.

HttpEntity, ResponseEntity

@ResponseBody ifadesine ek olarak header eklemeyi sağlar.

@GetMapping
public HttpEntity<String> index() {
    return new HttpEntity<String>("Merhaba Spring MVC!");
}

ResponseEntity sınıfı HttpEntity sınıfına ek olarak builder tasarım desenini kullanarak isteğe yanıt olarak header bilgisi, HTTP durum kodu gibi bilgileri eklemeyi sağlar.

@GetMapping
public ResponseEntity<?> index() {
    //return ResponseEntity.badRequest().body("Bad Request");
    //return ResponseEntity.noContent().build();
    //return ResponseEntity.notFound().build();
    //return ResponseEntity.accepted().build();
    //return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).build();
    return ResponseEntity.ok("Merhaba Spring MVC!");
}

Geniş kullanım desteği sunan ResponseEntity REST API işlemlerinde sıklıkla kullanılan durum kodları için özel statik metotlara sahiptir.

HttpHeaders

Sadece header göndermek için kullanılır.

@GetMapping
public HttpHeaders index() {
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.setLocation(URI.create("https://www.yusufsezer.com"));
    httpHeaders.set("ozel", "bilgi");
    return httpHeaders;
}

String, View, ModelAndView

MVC tasarım mimarisindeki view katmanını derleyip döndürmek için kullanılır.

Spring kullanımı başlığındaki gibi bir görüntüleme motoru ayarı yapıldığında ilgili görüntünün derlenmesini sağlar.

@GetMapping
public ModelAndView index(ModelMap modelMap) {
    modelMap.addAttribute("mesaj", "Merhaba Spring MVC!");
    return new ModelAndView("index", modelMap, HttpStatus.OK);
}

PDF, Excel, XML vb. özel ve ayrı görüntüleme motoru için View arayüzünün kalıtım alan AbstractPdfView, AbstractXlsxView, XsltView, AbstractRssFeedView sınıfların düzenlenmesi yeterli olacaktır.

Model

Spring MVC model verilerini view katmanına aktarmak için Model, Map, ModelMap, ModelAndView gibi arayüz ve sınıflar kullanılır.

Model

Verileri view katmanına göndermek için kullanılan en temel arayüzdür.

@GetMapping
public String index(Model model) {
    model.addAttribute("mesaj", "Merhaba Spring MVC!");
    return "index";
}

NOT: Arayüz olduğundan parametre olarak kullanılır.

Map

Verileri view katmanına göndermek için kullanılan ModelMap ve ModelAndView için temel oluşturan Java arayüzüdür.

@GetMapping
public String index(Map map) {
    map.put("mesaj", "Merhaba Spring MVC!");
    return "index";
}

ModelMap

Verileri view katmanına göndermek için kullanılan Map arayüzünü uygulayan sınıftır.

@GetMapping
public String index(ModelMap modelMap) {
    modelMap.put("mesaj", "Merhaba Spring MVC!");
    //ModelMap modelMap = new ModelMap();
    //modelMap.addAttribute("mesaj", "Merhaba Spring MVC!");
    return "index";
}

ModelAndView

Verileri view katmanına göndermek için içsel olarak View, ModelMap ve HttpStatus kullanan sınıftır.

@GetMapping
public ModelAndView index(ModelMap modelMap) {
    modelMap.addAttribute("mesaj", "Merhaba Spring MVC!");
    return new ModelAndView("index", modelMap, HttpStatus.OK);
}

View

Spring MVC gelen isteklere uygun sonuç üretmek için Thymeleaf, FreeMarker, JSP, PDF, Excel gibi görüntüleme motoru(template-view engine) desteğine sahiptir.

Görüntüleme motoruna ait sınıf-çözümleyici ayarları yapılarak kullanılabilir hale getirilir.

JSP için;

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/views/");
    viewResolver.setSuffix(".jsp");
    return viewResolver;
}

FreeMarker için;

@Bean
public ViewResolver viewResolver() {
    FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    resolver.setCache(true);
    resolver.setPrefix("");
    resolver.setSuffix(".ftl");
    return resolver;
}

NOT: Görüntüleme motoru ayarları WebMvcConfigurer sınıfı ile kolayca yapılabilir.

JSP bir çok Servlet Container ve Application Server tarafından desteklendiği için kullanımı yaygındır.

Spring JSP etiketleri

Spring MVC JSP ile yapılan işlemleri kolaylaştırmak ve hızlandırmak için çeşitli JSP etiketlerine sahiptir.

Etiketlerin kullanımı için aşağıdaki JSP direktif etiketlerinin eklenmesi yeterli olacaktır.

<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

Java JSP hakkında detaylı bilgi almak için Java JSP yazıma bakmalısın.

Spring etiketleri temel(mesaj, tema gibi özel işlemler) ve form etiketleri olmak üzere ikiye ayrılır.

<spring:message code="mesaj.kodu" />
<spring:theme code="sayfa.arkaplani" />

Spring içerisinde yer alan bir çok etiket Spring bağlantısı sağlayarak standart JSP etiketleri(JSTL) gibi çalışır.

Yukarıda HTML kullanılarak yapılan örneği Spring form etiketleri ile yapalım.

Aşağıdaki dosyayı form.jsp olarak kayıt edelim.

<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<body>
    <form:form modelAttribute="mesaj">
        <form:label path="mesaj">Mesaj: </form:label>
        <form:input path="mesaj" />
        <form:button>Submit</form:button>
    </form:form>
</body>
</html>

Spring JSP etiketleri HTML etiketindeki niteliklere ek olarak model bağlama için modelAttribute, model ait özelliği bağlamak için path gibi niteliklere sahiptir.

@Controller
public class HomeController {

    @GetMapping
    public String index(@ModelAttribute("mesaj") Mesaj mesaj) {
        return "form";
    }

    @PostMapping
    public ResponseEntity<?> sayfa(@ModelAttribute("mesaj") Mesaj mesaj) {
        return ResponseEntity.ok(mesaj.getMesaj());
    }

}

NOT: Spring form etiketi modelAttribute ile @ModelAttribute ifadesinin aynı değere sahip olduğuna dikkat edin.

NOT: Spring form etiketi modelAttribute özelliğine herhangi bir değer verilmediğinde varsayılan olarak command değerini kullanır.

Bazı spring form etiketleri;

  • <form:hidden>
  • <form:password>
  • <form:textarea>
  • <form:checkbox>
  • <form:checkboxes>
  • <form:select>
  • <form:option>
  • <form:radiobutton>
  • <form:radiobuttons>

Spring MVC Config

Spring MVC DispatcherServlet ile gelen varsayılan ayarları özelleştirmek için annotation, bean tanımlama(convention) ve arayüzlere sahiptir.

@EnableWebMvc

Uygulamaya MVC desteği ve ayarlarını eklemek için kullanılır.

@Configuration
@EnableWebMvc
public class WebConfig { }

İçsel olarak @Import(DelegatingWebMvcConfiguration.class) ifadesi ile DelegatingWebMvcConfiguration sınıfındaki varsayılan MVC ayarlarını kullanır.

Bean adlandırma

Spring MVC, Spring Core içerisinde yer alan Conventions olarak adlandırılan isimlendirme kuralına veya belirli bir türe ait bean tanımlamasını bağlama özelliğine sahiptir.

Örneğin; Aşağıdaki gibi bir bean tanımında Spring Core ViewResolver ile görüntüleme motoru çıkarımı yaparak ayarları yapacaktır.

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    viewResolver.setPrefix("/WEB-INF/views/");
    viewResolver.setSuffix(".jsp");
    return viewResolver;
}

WebMvcConfigurer

Arayüz Spring MVC ayarlarını kolay bir şekilde yapmak için ön tanımlı çeşitli metotlara sahiptir.

Yukarıda yer alan bean tanımını yapmak yerine ViewResolverRegistry sınıfını kullanmak yeterli olacaktır.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yusufsezer")
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

}

Arayüz sürekli olarak kullanılan temel ayarları hızlı ve kolay bir şekilde yapmayı sağlar.

NOT: Parametre olarak geçirilen(ViewResolverRegistry, CorsRegistry, InterceptorRegistry, FormatterRegistry vb.) sınıflar içsel olarak bean tanımlamasını yapar.

Diğer

Hem @EnableWebMvc hem de WebMvcConfigurer arayüzü kullanımı yerine sadece DelegatingWebMvcConfiguration aşağıdaki gibi kullanılarak düzenlenebilir.

@Configuration
@ComponentScan(basePackages = "com.yusufsezer")
public class WebConfig extends DelegatingWebMvcConfiguration {

    @Override
    protected void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }

}

Functional endpoints

Spring MVC Java 8 ile birlikte gelen fonksiyonel programlama özelliğini kullanarak annotation ifadelerine alternatif olarak lambda fonksiyonları ile eşlemeyi sağlar.

İstekleri işleyen lambda fonksiyonları ServerRequest arayüzünü parametre olarak alır ve geriye ServerResponse arayüzünü döndürür.

public ServerResponse home(ServerRequest request) {
    return ServerResponse
            .ok()
            .contentType(MediaType.TEXT_HTML)
            .body("Merhaba Spring MVC!");
}

ServerRequest arayüzünde yer alan param, params, headers, cookies, session, pathVariable, pathVariables vb. metotlar kullanılarak istek ile ilgili değerlere erişilir.

public ServerResponse profile(ServerRequest request) {
    return ServerResponse
            .ok()
            //.contentType(MediaType.TEXT_HTML)
            .body(request.pathVariable("id"));
}

ServerResponse arayüzündeki builder tasarım deseni(BodyBuilder) kullanan ok, notFound, accepted, badRequest, noContent, status, created metotları ile isteğe cevap verilir.

Metotların gelen istekle eşleşmesi için @Configuration ile belirtilen sınıfta bean tanımının(RouterFunction) yapılması yeterli olacaktır.

@Bean
public RouterFunction<ServerResponse> routerFunction() {
    MainHandler handler = new MainHandler();
    return route()
            .GET("/", handler::home)
            .GET("/profile/{id}", handler::profile)
            .build();
}

Gelen istekler MainHandler sınıfındaki home, profile metotları ile eşleşecektir.

İsteklere filtre eklemek için filter metodu kullanılır.

@Bean
public RouterFunction<ServerResponse> routerFunction() {
    MainHandler handler = new MainHandler();
    return route()
            .filter((request, next) -> {
                return request.headers().header("ozelbilgi").size() > 0
                        ? next.handle(request)
                        : ServerResponse.badRequest().build();
            })
            .GET("/", handler::home)
            .GET("/profile/{id}", handler::profile)
            .build();
}

Filtre ile gelen istek header bilgisinde ozelbilgi varsa tanımlanan metotlar çalışacaktır.

Functional endpoints özelliği @RequestMapping ile yapılan tüm işlemleri yapmayı sağlar.

NOT: Functional endpoints özelliği Node.js web isteklerini yönetmek için kullanılan Express.js modülü gibi çalışmaktadır.

i18n desteği

Spring MVC WebApplicationContext içerisinde yer alan MessageSource ile mesaj, uluslararasılaşma, i18n desteği sağlar.

MessageSource özelliği tanımlı mesajları uluslararasılaşma(internationalization-i18n) ile birlikte kullanmayı sağlar.

Özelliğin kullanımı için ilk olarak mesajlar .properties uzantılı olarak maven için src/main/resources/ dizininde oluşturulur.

mesajlar.properties

welcome.message=Welcome {0}

i18n desteğini(çok dilli) sağlamak için dosyaadı_ülkekodu olarak mesaj dosyaları oluşturulur.

mesajlar_tr.properties

welcome.message=Hoşgeldiniz {0}

Dosyalar hazırlandıktan sonra MessageSource arayüzünü uygulayan sınıfın(ResourceBundleMessageSource veya ReloadableResourceBundleMessageSource) bean tanımı yapılır.

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource;
    messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:mesajlar");
    messageSource.setDefaultEncoding("UTF-8");
    messageSource.setUseCodeAsDefaultMessage(true);
    return messageSource;
}

Diğer değer atamaları da yapılarak MessageSource işlemi özelleştirilebilir.

NOT: resources dizininin karmaşık hale gelmemesi için mesajları classpath:lang/mesajlar gibi lang dizinine eklemek faydalı olacaktır.

NOT: Bean isimlendirmesi olarak messageSource kullanıldığına dikkat edilmelidir.

Bean tanımları yapıldıktan sonra MessageSource arayüzünde yer alan aşağıdaki metotlar ile mesajlara ulaşılır.

  • String getMessage(String code, Object[] args, String defaultMessage, Locale locale)
  • String getMessage(String code, Object[] args, Locale locale)
  • String getMessage(MessageSourceResolvable resolvable, Locale locale)

Metotlarda yer alan

  • code mesaj dosyasındaki kodu,
  • args mesajda yer alan parametre({0}, {1}) değerini,
  • defaultMessage mesaj yoksa kullanılacak varsayılan mesajı
  • locale belirli bir dile ait mesajı,
  • MessageSourceResolvable ifadesi code, args, defaultMessage ifade eder.

Mesajlar controller içerisinde aşağıdaki gibi kullanılabilir.

@Controller
public class HomeController {

    @Autowired
    private MessageSource messageSource;

    @GetMapping
    public String index(@ModelAttribute("mesaj") Mesaj mesaj, Locale locale) {
        String message = messageSource.getMessage(
                "welcome.message",
                new Object[]{"Yusuf"},
                locale);
        System.out.println(message);
        return "form";
    }

    // diğer metotlar

}

Mesajları view içerisinde kullanmak için Spring MVC spring:message etiketi kullanılabilir.

<spring:message code="welcome.message" arguments="${mesaj.getMesaj()}" />

MessageSource varsayılan veya özel olarak belirtilen(AcceptHeaderLocaleResolver) mesajları kullanır.

Kullanıcının dinamik olarak dil değiştirmesi için Interceptor özelliği kullanılır.

Interceptor

Bir istek controller tarafına gelmeden önce gelen istek ve istek değerine göre çeşitli ayarları yapmak için Interceptor özelliği kullanılır.

Interceptor özelliği için HandlerInterceptor arayüzünde yer alan istek controllera ulaşmadan(preHandle), ulaştıktan sonra(postHandle) ve tamamlandığında(afterCompletion) çalışacak metotlar ile yapılır.

Spring MVC içerisinde temel işlemleri yapmak için ön tanımlı çeşitli Interceptor sınıfları yer alır.

Örneğin; dinamik olarak dil değişikliği yapmak için LocaleChangeInterceptor, tema değişikliği için ThemeChangeInterceptor, güvenlik için SecurityInterceptor sınıfı kullanılabilir.

Sınıfların kullanımı sıradan Java sınıflarının kullanımı gibidir.

@Configuration
@ComponentScan(basePackages = "com.yusufsezer")
public class WebConfig extends DelegatingWebMvcConfiguration {

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
        // varsayılan LocaleChangeInterceptor ayarları
        registry.addInterceptor(new LocaleChangeInterceptor());
    }

}

LocaleChangeInterceptor sınıfı varsayılan olarka ?locale ifadesi ile çalışır.

Bu varsayılan değeri değiştirmek için setParamName metodu kullanılabilir.

@Override
protected void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("language");
    registry.addInterceptor(localeChangeInterceptor);
}

NOT: Varsayılan ayarları kullanmak standart oluşturduğu için faydalı olacaktır.

Spring MVC dil belirleme için varsayılan olarak DispatcherServlet.properties dosyasındaki tanımlı AcceptHeaderLocaleResolver sınıfını kullanır.

AcceptHeaderLocaleResolver sınıfı header bilgisine(Accept-Language) bakarak kullanılacak olan dili belirler.

Spring MVC içerisinde yer alan SessionLocaleResolver, CookieLocaleResolver, FixedLocaleResolver ile dil belirleme işlemi için session, çerez veya sabit tanımlı dil kullanımı ayarlanabilir.

@Bean
public LocaleResolver localeResolver() {
    return new SessionLocaleResolver();
}

SessionLocaleResolver bean tanımı ile Spring MVC dil belirleme işlemi için session kullanacaktır.

Tema desteği

Spring MVC tema desteğini varsayılan olarak DispatcherServlet.properties dosyasında tanımlı olan FixedThemeResolver sınıfını kullanır.

FixedThemeResolver sınıfı ise varsayılan olarak src/main/resources/ dizininde yer alan theme.properties dosyasını kullanır.

Özelliğin kullanımı için ilk olarak theme.properties dosyası src/main/resources/ dizininde oluşturulur.

background=whitesmoke
css=https://www.yusufsezer.com/css/bootstrap.css

Tema değerlerini view içerisinde kullanmak için Spring MVC spring:theme etiketi kullanılır.

<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code="css" />" >
</head>
<body style="background-color: <spring:theme code="background" />">
// form kodları
</body>
</html>

Kullanıcının dinamik olarak tema değiştirmesi için farklı ThemeSource, ThemeResolver ile Interceptor özelliği kullanılır.

@Bean
public ThemeSource themeSource() {
    ResourceBundleThemeSource themeSource = new ResourceBundleThemeSource();
    themeSource.setBasenamePrefix("theme/");  // temayı theme dizininde ara
    return themeSource;
}

@Bean
public ThemeResolver themeResolver() {
    return new SessionThemeResolver(); // tema ayarlarını session kayıt et
}

@Override
protected void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    registry.addInterceptor(new ThemeChangeInterceptor());
}

Spring MVC içerisinde yer alan SessionThemeResolver, CookieThemeResolver, FixedThemeResolver ile tema belirleme işlemi için session, çerez veya sabit tanımlı dil kullanımı ayarlanabilir.

SessionThemeResolver, CookieThemeResolver, FixedThemeResolver sınıfları varsayılan olarak theme.properties dosyasını kullanır.

Bu varsayılan değeri değiştirmek için setDefaultThemeName metodu kullanılabilir.

@Bean
public ThemeResolver themeResolver() {
    SessionThemeResolver sessionThemeResolver = new SessionThemeResolver();
    sessionThemeResolver.setDefaultThemeName("cerulean");  // varsayılan tema cerulean
    return sessionThemeResolver;
}

NOT: Varsayılan ayarları kullanmak standart oluşturduğu için faydalı olacaktır.

ThemeChangeInterceptor sınıfı varsayılan olarak ?theme ifadesi ile çalışır.

Bu varsayılan değeri değiştirmek için setParamName metodu kullanılabilir.

@Override
protected void addInterceptors(InterceptorRegistry registry) {
    super.addInterceptors(registry);
    ThemeChangeInterceptor themeChangeInterceptor = new ThemeChangeInterceptor();
    themeChangeInterceptor.setParamName("style");
    registry.addInterceptor(themeChangeInterceptor);
}

NOT: Varsayılan ayarları kullanmak standart oluşturduğu için faydalı olacaktır.

Tema özelliğini kullanmak için sırayla aşağıdaki dosyaları theme dizine oluşturalım.

cerulean.properties

style-url=https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cerulean/bootstrap.min.css

cosmo.properties

style-url=https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css

theme.properties

style-url=https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css

Temayı view içerisinde kullanalım.

<%@taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='style-url' />" >
</head>
<body>
<div class="jumbotron">
  <h1 class="display-4">Merhaba Spring MVC!</h1>
  <p class="lead">www.yusufsezer.com</p>
  <hr class="my-4">
  <p>Yusuf SEZER.</p>
  <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
</div>
</body>
</html>

Sayfaya ?theme=cosmo, ?theme=cerulean veya ?theme=theme ile erişerek tema değişikliği görüntülenebilir.

Spring MVC tema desteği içerisinde dil desteğini kullanmayı destekler.

Tema adlandırması olarak temaadı_ülkekodu.properties olarak dosyalar oluşturulduğunda ve dil belirleme(AcceptHeaderLocaleResolver, SessionLocaleResolver vb.) ile sayfaya istek geldiğinde Spring MVC belirtilen dile ait tema dosyasını kullanacaktır.

Dosya yükleme

Spring MVC dosya yükleme işlemini yapabilmek için MultipartResolver ayarının yapılması gerekir.

Dosya yükleme işlemi MultipartResolver arayüzünü kullanan Apache Commons(CommonsMultipartResolver) veya Servlet 3.0(StandardServletMultipartResolver) ile birlikte standart olarak gelen sınıf kullanılabilir.

Apache Commons FileUpload ek kütüphaneye ihtiyaç duyduğundan dolayı standart Servlet 3.0 dosya yükleme sınıfını kullanalım.

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

Servlet dosya yükleme işlemi ile ilgili ayarları Servlet ayarı olarak eklemek gerekir.

AbstractAnnotationConfigDispatcherServletInitializer sınıfında yer alan customizeRegistration metodu ile bu işlemi yapalım.

@Override
protected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement(""));
}

MultipartConfigElement sınıfı dosya yükleme sırasında dosyanın geçici olarak saklanacağı yol, dosya boyutu, istek boyutu gibi ayarları yapmaya olanak saklar.

new MultipartConfigElement("/temp", 1048576, 10485760, 0);

Tanımlar yapıldıktan sonra Spring MVC MultipartFile arayüzü veya Servlet Part arayüzünde yer alan metotlarla dosya yükleme işlemleri yapılır.

<html>
<body>
    <form method="post" enctype="multipart/form-data">
        <label>Dosya: </label>
        <input type="file" name="dosya" />
        <input type="submit" />
    </form>
</body>
</html>

Spring MVC arayüzü(MultipartFile) yerine standart servlet(Part) arayüzünü kullanmak standart kullanım için faydalı olacaktır.

@Controller
public class UploadController {

    @GetMapping
    public String index() {
        return "form";
    }

    @PostMapping
    public ResponseEntity<?> yukle(Part dosya) {
        return ResponseEntity.ok(dosya.getSubmittedFileName());
    }

}

NOT: Form niteliği(enctype=”multipart/form-data”), dosya yükleme(name=”dosya”) ve parametre(Part dosya) değerlerine dikkat edilmelidir.

Validation

Spring MVC girilen değerleri kontrol etmek için içsel(Validator, @Validated) ve JSR 303, 349, 380 standart validation-doğrulama özelliğine sahiptir.

JSR tarafından belirtilen standartlar için referans implementasyon olan hibernate validator kütüphanesini pom.xml dosyasına ekleyelim.

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.6.Final</version>
</dependency>

Modeli bean validation tarafından tanımlanan ifadelere(@Size, @NotBlank, @NotEmpty vb.) göre düzenleyelim.

public class Mesaj {

    @Size(min = 10, max = 20)
    @NotBlank
    @NotEmpty
    private String mesaj;

    // get-set

}

Kullanılabilecek diğer ifadeler;

  • @AssertFalse
  • @AssertTrue
  • @DecimalMax
  • @DecimalMin
  • @Digits
  • @Email
  • @Future
  • @FutureOrPresent
  • @Max
  • @Min
  • @Negative
  • @NegativeOrZero
  • @NotBlank
  • @NotEmpty
  • @NotNull
  • @Null
  • @Past
  • @PastOrPresent
  • @Pattern
  • @Positive
  • @PositiveOrZero
  • @Size

İfade özelliklerine istenilen değerler vererek ifadelerin yapacağı işlem özelleştirilebilir.

Model dosyasına eklenen ifadelerin kontrolü için controller metotlarına @Valid ifadesini ekleyelim.

@Controller
public class HomeController {

    @GetMapping
    public String index(@ModelAttribute("mesaj") Mesaj mesaj) {
        return "form";
    }

    @PostMapping
    public String sayfa(@ModelAttribute("mesaj") @Valid Mesaj mesaj, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "form";
        }
        return "index";
    }

}

Verilerin gönderimi sırasında oluşan hata ve bilgilere erişim BindingResult ve Errors arayüzünde yer alan metotlar ile yapılır.

NOT: BindingResult arayüzü Errors arayüzünü kalıtarak daha fazla özellik kullanımı sağlar.

Doğrulama sırasında oluşan hatalara ait mesajlar view katmanında Spring MVC JSP etiketi(form:errors) ile erişilir.

<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!doctype html>
<html>
<body>
    <form:form modelAttribute="mesaj">
        <form:errors path="*" />
        <form:label path="mesaj">Mesaj: </form:label>
        <form:input path="mesaj" /><form:errors path="mesaj"/>
        <form:button>Submit</form:button>
    </form:form>    
</body>
</html>

Belirli bir değere ait mesaj için path kullanılırken tüm hata mesajlarına erişim için yıldız/wildcard/joker karakteri(*) kullanır.

Kullanılan ifade içerisindeki message özelliği ile istenilen uyarı mesajı verilebilir.

@Size(min = 10, max = 20, message = "Mesaj 10 ila 20 arasında olmalıdır.")

Bean validation varsayılan olarak i18n desteği sağlayan javax.validation.constraints.ifade.message mesajlarını kullanır.

Mesajı message özelliğini kullanarak değiştirmek yerine bean validation tarafından varsayılan olarak kullanılan mesaj değerini değiştirmek faydalı olacaktır.

Örneğin; @Size ifadesine ait mesajı değiştirmek için i18 desteği başlındaki mesaj dosyasına aşağıdaki ifadenin eklenmesi yeterli olacaktır.

Size=Girilen değer uzunluğu {2} ile {1} arasında olmalıdır

Bean validation sağladığı i18n desteği sayesinde belirlenen veya belirtilen dile göre uyarı mesajlarını verecektir.

Spring MVC LocaleResolver ayarı yapılıp ?locale=br ile dil değiştirildiğinde uyarı mesajı belirtilen dile göre olacaktır.

Bean validation mevcut ifadeleri kullanarak

@Pattern(regexp = "\\+90-\\d{3}-\\d{3}-\\d{2}-\\d{2}")
@Constraint(validatedBy = {})
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(List.class)
public @interface Telefon { /*diğer tanımlar*/ }

veya özel sınıf belirterek yeni doğrulama-kontro annotation ifadesi tanımlamayı destekler.

@Constraint(validatedBy = {com.yusufsezer.validator.TelefonValidator.class})

Kontrol için kullanılacak olan sınıf ConstraintValidator arayüzünde yer alan initialize ile kontrol için gerekli sınıfların başlatılması, isvalid ise kontrol işlemini yapılmasını sağlar.

public class TelefonValidator
        implements ConstraintValidator<Telefon, String> {

    @Override
    public void initialize(Telefon constraintAnnotation) {
        // kontrol için gerekli olan sınıf yüklemeleri
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // String türü için doğrulama-kontrol yapılıyor
        return true;
    }

}

Sınıf String türündeki değerleri kontrol edecektir.

NOT: Tüm türleri kontrol etmek için String yerine Object kullanılabilir.

İstisna yönetimi

Spring MVC ortaya çıkan beklenmedik durumları(istisna) yönetmek için DispatcherServlet yaşam döngüsü sırasında varsayılan olarak HandlerExceptionResolver arayüzünü kullanan ExceptionHandlerExceptionResolver, ResponseStatusExceptionResolver ve DefaultHandlerExceptionResolver sınıflarını kullanır.

NOT: HandlerExceptionResolver arayüzünü kullanan başka SimpleMappingExceptionResolver sınıfı da yer almaktadır.

HandlerExceptionResolver arayüzünü kullanarak özel istisna yönetimini sağlayan sınıf oluşturulabilir.

DefaultHandlerExceptionResolver sınıfı uygulama içerisinde yer alan içsel hataları yönetmek için kullanılır.

@RequestMapping("/sayfa/{id}")
public ResponseEntity<String> sayfa(
        @PathVariable(value = "sayfaId") Long id
) {
    return ResponseEntity.ok(String.valueOf(id));
}

@RequestMapping("/sayfa2")
public void hata() throws Exception {
    throw new HttpRequestMethodNotSupportedException(HttpMethod.TRACE.name());
}

sayfa metodundaki id ile sayfaId eşleşmediği için oluşan hata DefaultHandlerExceptionResolver tarafından kontrol edilecektir.

Benzer şekilde hata metodunda olduğu gibi spring içerisinde yer alan çeşitli istisna sınıfları DefaultHandlerExceptionResolver tarafından kontrol edilecektir.

ExceptionHandlerExceptionResolver sınıfı oluşan hataları @ExceptionHandler ifadesi ile belirtilen metoda yönlendirir.

@Controller
public class HomeController {

    // diğer kodlar

    @PostMapping
    public String sayfa(@ModelAttribute("mesaj") @Valid Mesaj mesaj, BindingResult bindingResult)
            throws Exception {
        if (bindingResult.hasErrors()) {
            throw new Exception("Form hatası");
        }
        return "index";
    }

    @ExceptionHandler
    public ResponseEntity<?> hata(Exception ex) {
        return ResponseEntity.ok(ex.toString());
    }

}

@ExceptionHandler ifadesinin kullanıldığı metot parametre olarak @RequestMapping ifadesi ile kullanılan değerleri kullanabilir.

İfadeye parametre olarak sadece belirli istisna sınıfları(Exception) yazılarak sadece belirtilen hatalar için çalışması sağlanabilir.

@ExceptionHandler({ArithmeticException.class, NullPointerException.class})

ResponseStatusExceptionResolver sınıfı oluşturulan özel istisna sınıfları için @ResponseStatus ifadesini kullanarak özel hata mesajı vermeyi sağlar.

@PostMapping
public String sayfa(@ModelAttribute("mesaj") @Valid Mesaj mesaj, BindingResult bindingResult)
        throws Exception {
    if (bindingResult.hasErrors()) {
        throw new FormException("Form hatası");
    }
    return "index";
}

Yukarıdaki örnekte FormException ile oluşan hata Servlet Container tarafından 500 içsel hata olarak görüntülenecektir.

@ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED, reason = "Form hatası")
public class FormException extends Exception {

    public FormException(String message) {
        super(message);
    }

}

FormException sınıfına eklenen @ResponseStatus ifadesi ile HTTP durum kodu ve mesajı(reason) değiştirilmiştir.

NOT: Spring MVC tarafından yönetilemeyen istisna sınıfları için Servlet istisna yönetimi kullanılır.

@ExceptionHandler ifadesi sadece kullanıldığı sınıf içerisindeki hataları yönetmek için kullanılır.

Tüm uygulama için geçerli bir istisna yönetimi için @ControllerAdvice kullanılır.

@ControllerAdvice

Spring MVC içerisindeki @ExceptionHandler, @InitBinder@ModelAttribute ifadeleri sadece kullanıldığı sınıfta geçerli olacaktır.

İfadelerin tüm uygulama için geçerli olması için sınıfın @Controller yerine @ControllerAdvice ifadesi ile belirtilmesi gerekmektedir.

@ControllerAdvice
public class SupportController {

    @ModelAttribute("mesaj")
    public Mesaj mesaj() {
        Mesaj mesaj = new Mesaj();
        mesaj.setMesaj("Merhaba Dünya!");
        return mesaj;
    }

    @ExceptionHandler
    public ResponseEntity<?> hata(Exception ex) {
        return ResponseEntity.ok(ex.toString());
    }

}

@ControllerAdvice ile belirtilen sınıf içerisindeki hata metodu uygulama içerisinde hata olduğunda çalışacaktır.

Uygulama içerisindeki herhangi bir metot çalıştırıldığında @ModelAttribute ile belirtilen metot çalıştırılarak Model alanına kayıt edilecektir.

NOT: Her controller sınıfında özel hata yönetimi yapmak yerine @ControllerAdvice kullanmak faydalı olacaktır.

Diğer

Spring MVC, MVC tasarım mimarisini kullanarak Spring framework içerisinde yer alan IoC özelliği ile geliştirme yapmayı kolay ve hızlı hale getirir.

Sağladığı kolay ve geniş kullanım sayesinde geliştirme yapmayı hızlandırsa da aynı işi yapmanın birden fazla yöntemi olduğundan karmaşıklığa neden olabilmektedir.

Spring bu karmaşıklığı gidermek ve en iyi(best pratices) olarak adlandırılan yöntemleri varsayılan olarak kullanan Spring Boot projesini geliştirmiştir.

Spring Boot Web projesinin temelinde de Spring Core ve Spring Web MVC kullanıldığından nasıl çalıştığını bilmek faydalı olacaktır.

Uygulama geliştirirken JCP ve JEE tarafından belirlenen standartları kullanmak platform değişikliği, beklenmedik değişikliklere karşı faydalı olacaktır.

Java Derslerine buradan ulaşabilirsiniz.

Hayırlı günler dilerim.


Bunlara'da bakmalısın!