SCJPTester wyświetla newsy – podstawy Springa w praktyce

Dziś przyszedł czas na coś większego. Mam nadzieję, że ilość kodu nie będzie odstraszająca. Ponieważ dziś zaimplementujemy coś z pomocą tandemu Spring + Hibernate. Konkretnie przebudujemy troszeczkę widok strony głównej po to, aby móc wyświetlić ostatnie informacje o aplikacji (podobną rzecz robiliśmy z ProgramBash). Jest to o tyle ciekawe, iż użyjemy Springa w całej jego krasie, stworzymy pierwszą tabelę w naszej bazie, nauczymy się czegoś więcej o kontrolerach, używaniu Hibernate poprzez klasy Springa, a ostatecznie troszeczkę podlejemy to sosem od FreeMarkera. Do dzieła.

Zaczniemy od bazy. Zainstalowałem ją jakiś czas temu, a do naszej aplikacji podpiąłem chwilkę później. W ten sposób wyposażeni możemy stworzyć pierwszą tabelę w bazie danych – konkretnie news:

CREATE TABLE news (
id serial PRIMARY KEY,
title varchar(127) NOT NULL,
content text NOT NULL,
data date NOT NULL
) WITHOUT OIDS ;

Co zaowocuje takim komunikatem:

NOTICE: CREATE TABLE will create implicit sequence "news_id_seq" for serial column "news.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "news_pkey" for table "news"
Zapytanie zostało wykonane w 454 ms i nie zwróciło żadnych wyników.

Mówiąc krótko – została utworzona sekwencja, która została przypisana do kolumny z naszym kluczem głównym (ten też został zresztą utworzony jak widać). Mamy zatem tabelę, na której będziemy pracować – dodałem do niej też 2 rekordy, aby mieć jakieś dane do pracy. Zajmiemy się teraz samym Springiem. Na czym bowiem polega jego zajefajność? Otóż podstawą jest właśnie wstrzykiwanie zależności – mamy obiekt, którego składową jest inny obiekt. Konfigurujemy te 2 obiekty w pliku XML, a Spring zajmie się utworzeniem obiektu, jego przekazaniem i wszystkim po drodze. Jest to o tyle praktyczne, iż redukuje ilość kodu, który trzeba za każdym razem napisać. Deklarujemy po prostu, że potrzebujemy w danym obiekcie innego obiektu i viola – działa. Suchy opis jednakże niewiele znaczy, zatem lepiej przerobić rzecz w praktyce.

Zaczniemy od klasy domenowej dla naszego newsa. Dla tej i pozostałych klas domenowych utworzymy oddzielny pakiet com.wordpress.chlebik.domain. Kod wygląda tak:

package com.wordpress.chlebik.domain;

import java.io.Serializable;
import java.sql.Date;

/**
 * Klasa domenowa dla newsa w serwisie
 *
 * @author chlebik
 */
public class News implements Serializable {

 private    long      id;
 private    String   title;
 private    String   content;
 private    Date     data;

 /**
 * Konstruktor
 *
 * @author chlebik
 */
 public News() {}

 // Gettery
 public long getId() {
 return id;
 }

 public String getTitle() {
 return title;
 }

 public String getContent() {
 return content;
 }

 public Date getData() {
 return data;
 }

 // Settery
 public void setId( int id ) {
 this.id = id;
 }

 public void setTitle( String title ) {
 this.title = title;
 }

 public void setContent( String content ) {
 this.content = content;
 }

 public void setData( Date data ) {
 this.data = data;
 }

}

Nihil novi sub sole. Klasa jest prosta i sztampowa do bólu. Mapowanie:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
 <class name="com.wordpress.chlebik.domain.News" table="news">
 <id column="id" name="id" type="int">
 <generator/>
 </id>
 <!--  zwykle kolumny -->
 <property column="content" name="content" type="string"/>
 <property column="title" name="title" type="string"/>
 <property column="data" name="data" type="date"/>
 </class>
</hibernate-mapping>

Należy pamiętać o dopisaniu mapowania do pliku konfiguracyjnego Hibernate. Teraz wypadałoby zaimplementować klasę dostępu do naszej klasy domenowej, czyli mówiąc krótko piszemy DAO. Rzecz jasna wychodzimy od interfejsu, który zdefiniuje kontrakt jaki chcemy by spełniały ew. klasy implementujące. Uwaga! Dla tych co nie wiedzą dlaczego używamy interfejsów ważne wskazanie – posiadając interfejs korzystamy przy ewentualnej pracy z polimorfizmu. Zatem nagłe przepisanie aplikacji z MySQLa na PostgreSQL i na odwrót nie stanowi problemu, gdyż ew. obiekty przekazujemy rzutując w górę na interfejs.

Rzecz jasna w przypadku newsów na stronie głównej problemu nie ma – będzie tam póki co jedna metoda, która będzie nam wyciągała nasze newsy w określonej ilości. Może i dla takiego zastosowania możnaby od razu sklepać klasę, ale ani to profesjonalne, ani nie wykształca dobrych zachowań. Zatem tworzymy prosty interfejs w pakiecie com.wordpress.chlebik.dao.interfaces.

package com.wordpress.chlebik.dao.interfaces;

import com.wordpress.chlebik.domain.News;
import java.util.List;

/**
 * Interfejs specyfikujacy kontrakt dla klas DAO newsow w serwisie
 *
 * @author chlebik
 */
public interface NewsDaoInterface {
 public List<News> getNews( int counter );
}

Interfejs mamy – teraz przyszedł czas na klasę go implementującą. I tutaj pierwszy styk Springa z Hibernate. Ten pierwszy posiada gotowe klasy, które służą obsłudze zapytań SQLa poprzez Hibernate. Oczywiście skorzystamy z tej możliwości, tym bardziej, że ułatwia to pisanie kodu. Jak bowiem wyglądałaby nasza klasa DAO? Zaimplementowalibyśmy metodę, w której wyciągalibyśmy sesję, potem ją zamykali, obsługa wyjątków, blech, coś brzydkiego, prawie jak JDBC. Zaczniemy od konfiguracji – musimy poinformować Springa, że używać będziemy Hibernate. Jednakże wprowadzimy pewien porządek, coby nie mieszać niepotrzebnie w dotychczasowym pliku konfiguracyjnym. Stworzymy oddzielny plik XMLa z konfiguracją bazy danych, a następnie zaimportujemy go do obecnie już istniejącego frontcontroller-servlet.xml. Nasz plik nazwiemy database-config.xml i wrzucamy do katalogu WEB-INF:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

 <bean id="dataSource">

 <property name="driverClassName">
 <value>org.postgresql.Driver</value>
 </property>
 <property name="url">
 <value>jdbc:postgresql://88.198.31.169/chlebik</value>
 </property>

 <property name="username"><value>MOJ_USER</value></property>
 <property name="password"><value>MOJE_HASLO</value></property>
 </bean>

 <bean id="mySessionFactory">

 <property name="mappingResources">
 <list>
 <value>News.hbm.xml</value>
 </list>
 </property>

 <property name="hibernateProperties">
 <props>
 <prop key="hibernate.dialect">
 org.hibernate.dialect.PostgreSQLDialect
 </prop>
 </props>

 </property>
 <property name="dataSource">
 <ref bean="dataSource"/>
 </property>

 </bean>
</beans>

Zaś do pliku frontcontroller-servlet.xml dopisujemy taki kod (dotyczy bazy danych jak i naszego beana z newsami):

<import resource="database-config.xml"/>

  <bean id="newsDao" class="com.wordpress.chlebik.dao.implementation.NewsDao">
        <property name="sessionFactory" ref="mySessionFactory" />
     </bean>

  <bean name="/index.html" class="com.wordpress.chlebik.controllers.IndexController">
       <property name="newsDao" ref="newsDao" />
  </bean>

Mamy pokonfigurowane stosowne beany by ogarnąć połączenie z bazą danych. W ten sposób wstępnie poinformowaliśmy Springa (zdefiniowaliśmy bean) o istnieniu fabryki sesji Hibernate. Ślicznie. Dzięki temu możemy teraz zastanowić się nad implementacją klasy DAO dla newsów. Mamy tutaj kilka możliwości:

  • HibernateDaoSupport – jest to klasa, którą powinny rozszerzać nasze klasy implementujące interfejs DAO konkretnej encji. Dzięki jej użyciu uzyskujemy o wiele łatwiejszy dostęp do sesji (po to właśnie skonfigurowaliśmy session factory), co prawda może to momentami być problematyczne ( o tym pewnie kiedy indziej napiszę ), dla obecnego przykładu powinno być OK.
  • HibernateDaoSupport wraz z obiektem pomocniczym HibernateTemplate – jest pokłosiem powyższego – również rozszerzamy klasę HibernateDaoSupport, ale za to odowłujemy się również do obiektu pomocniczego HibernateTemplate, co znacznie skraca kod.

Jako, że jedno wynika z drugiego pokażę obie implementacje. W wersji pierwszej po prostu rozszerzymy klasę HibernateDaoSupport:


package com.wordpress.chlebik.dao.implementation;

import com.wordpress.chlebik.dao.interfaces.NewsDaoInterface;
import com.wordpress.chlebik.domain.News;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * Klasa DAO dla newsów
 *
 * @author chlebik
 */
public class NewsDao extends HibernateDaoSupport implements NewsDaoInterface {

 List<News> newsy;
 SessionFactory sessionFactory;

 /**
 * Bezargumentowy konstruktor
 */
 public NewsDao() {}

 /**
 * Wyciaga newsy w zadanej ilosci
 *
 * @param   int    counter
 * @return  List<News>
 */
 public List<News> getNews(int counter) {

 try {

 Session session = getSession();
 newsy = (List<News>)session.createQuery( "from News order by data desc limit " + counter ).list();
 releaseSession( session );
 return newsy;

 } catch ( Exception e ) {
 System.out.println( e.toString() );
 }

 return newsy;

 }

 public List<News> getNewsy()  {
 return newsy;
 }

 public void  setNewsy( List<News> lista )  {
 newsy = lista;
 }
}

Jak widać łatwiej dostać się do sesji, łatwiej ją uwolnić i w ogóle jest zawsze czysto, zawsze sucho i powiedzmy, że zazwyczaj pewnie. Jednakże kod ten można jeszcze bardziej uprościć i doprowadzić do takiej oto postaci (sama metoda getNews()):

public List<News> getNews(int counter) {
         return (List<News>) getHibernateTemplate().find( "from News order by data desc limit " + counter );
}

Obiekt pomocniczny HibernateTemplate udostępnia kilka najbardziej popularnych metod używanych przy pracach z bazami danych, dlatego też w tak prostych przypadkach jego użycie jest jak najbardziej wskazane. Po więcej informacji na temat tej klasy odsyłam do dokumentacji. Ja użyłem metody find, która jak widać wyciąga rekordy pasujące do zapytania. Uwaga! Używanie tego podejścia jest bardzo szybkie i przyjemne, ale automatycznie mocno skazuje nas na integrację ze Springiem. W przypadku gdybyśmy chcieli zmienić coś w samym kodzie aplikacji i ewentualnie przemigrować jego kawałek na inny framework mielibyśmy trochę roboty. Polecam odnośnik z jednego z komentarzy do tutoriala Darka Zonia ( mały link ).

Cóż nam teraz zostało? Sprawdzić czy w ogóle coś działa! Odwołamy się do istniejącego już kontrolera – IndexController. Tam w metodzie handleRequest do tej pory zwracaliśmy prostą instancję ModelandView. Na razie zostawimy ten temat sam sobie, natomiast stworzymy obiekt DAO i zobaczymy czy na pewno zwrócił to, co powinien zwrócić. Modyfikujemy zatem ww. metodę i dodajemy taki oto kod:


newsy = newsDao.getNews(MAIN_PAGE_NEWS_COUNTER);
System.out.println( "Zwroconych newsow " + newsy.size() );

Jak widać do kontrolera dodałem też stałą, która będzie nam wskazywała ile newsów pobrać na główną stronę. W codziennej praktyce rzeczy związane z konkretnym kontrolerem/akcją umieszczam najbliżej jak się da – do tego jako stałą. Jakoś tak bardziej to elegancko wygląda. Choć pewnie w komercyjnym projekcie dla zewnętrznego klienta takie rzeczy powinno się trzymać w zewnętrznym pliku, a konto łatwiejszego utrzymania. Uruchamiamy nasz projekt. I w konsoli wyskakuje krótkie:


Zwroconych newsow 2

Czyli coś poszło tak jak powinno 😉  Oczywiście kwestia jest teraz taka, że wypadałoby owe newsy przedstawić w odpowiedni sposób. Rzecz jasna wpierw trzeba listę z newsami przekazać do widoku. W kontrolerze zatem zamieniamy linijkę z instrukcją zwrotu obiektu ModelandView na następującą:


return new ModelAndView( "index", "newsList", newsy );

I po kilku zabawach z CSSem oraz z modyfikacją widoku:

  <#list newsList as News>
                  <div class="col">
                      <h4 style="color: #FFF2B3;">${News.title}</h4>
                      <h5>${News.data}</h5>
                      <p style="font-size: 10px; line-height: 1.2em;">${News.content}</p>

                  </div>
    </#list>

Znaczniki są dość samo-opisujące. Mamy zwykłą iterację po liście i ostateczny efekt wygląda w taki oto sposób:

SCJPTester w wersji HelloWorld!

Dzisiaj zrobimy coś poważniejszego. Konkretnie postaramy się sprawić, że nasz SCJPTester zaistnieje w sieci, a także sprawimy by owo zaistnienie było czymś więcej niż wyświetleniem zwykłej strony JSP z napisem “Hello World!”. Do roboty.

W tym momencie jednakże nie wiem od czego tak naprawdę zacząć. Może od samego Spring MVC, gdyż to w jego strukturze będziemy się poruszać. Już po nazwie widać, iż realizujemy wzorzec MVC, co jest bardzo dobre, ale co więcej – Spring MVC implenentuje również wzorzec Front Controllera, to znaczy, iż wszystkie żądania skierowane do aplikacji przechodzą przez jedno miejsce (niezależnie od requestu). Poniżej ładny schemat zaczerpnięty ze stron Spring MVC:

Jest mi to wszystko o tyle bliskie, że w codziennej pracy z PHP korzystam z tych samych wzorców. Podoba mi się to również z tego powodu, iż wiadomo gdzie przychodzi nasze żądanie, można z nim po drodze zrobić jeszcze wiele ciekawych rzeczy. O wiele bardziej to przejrzyste niż JSF. Lecimy dalej. Nasz front controller jest najzwyklejszym serwletem, który dziedziczy po klasie HttpServlet i jako taki powinien być zdefiniowany w pliku web.xml. Podstawowa konfiguracja wyglądałaby na przykład tak:


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

    <servlet-mapping>
        <servlet-name>frontcontroller</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

Domyślnie Spring MVC załaduje wszystkie ustawienia z pliku web.xml, jednakże przy każdym requeście (w powyższym przykładzie do zasobów kończących się na .html) zostanie uruchomiony serwlet o nazwie frontController. W tym momencie mechanizm ładujący spróbuje znaleźć w katalogu WEB-INF dodatkowy plik z konfiguracją, według wzoru:

[nazwa-serwletu]-servlet.xml

Dzięki czemu możemy mieć wyspecjalizowany plik kontekstu dla konkretnego typu żądań (np. do takich, które wymagają zwrotu nie HTMLa, ale np. pliku PDF). Generalnie w myśl mojego zamysłu aplikacja będzie posługiwała się rozszerzeniami *.html. Samych stron jakoś specjalnie wiele to nie będzie, dlatego też poprzestaniemy na opisowych nazwach “niby-routingu”, czyli np. login.html czy testuj.html. Request do konkretnego zasobu jest podpinany pod klasę kontrolera, która go obsłuży. Spring dostarcza szereg bazowych klas, które powinny być użyte do obsługi żądań (w sensie – należy po nich dziedziczyć). Dlatego też w pliku frontcontroller-servlet.xml pojawi się taki kod:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

  <!-- the application context definition for the springapp DispatcherServlet -->

  <bean name="/index.html" class="com.wordpress.chlebik.controllers.IndexController" />

</beans>

I teraz trzeba napisać nasz kontroler. Generalnie jego zadaniem będzie serwowanie zwykłej statycznej strony, zatem nie ma potrzeby korzystać z jakiś bardziej zaawansowanych klas Springa – zaimplementujemy po prostu interfejs Controller, który jest bazowym dla wszystkich klas kontrolerów. O innych klasach implementujących ten interfejs napiszę później przy tworzeniu bardziej zaawansowanej użyteczności:

package com.wordpress.chlebik.controllers;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

/**
 *  Klasa kontrolera dla strony glownej
 *
 * @author chlebik
 */
public class IndexController implements Controller {

 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    return new ModelAndView("index.jsp");
  }

}

Na razie odnosimy się do prostej strony index.jsp, która została utworzona wraz z całym projektem. Jest ona też wpisana w pliku web.xml na liście plików powitalnych dla aplikacji. Po małych przeróbkach z mojej strony plik ten wygląda tak:


<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>SCJPTester</title>
    </head>
    <body>
        <h1>Bardziej rozbudowane Hello World!</h1>
    </body>
</html>

No i po uruchomieniu aplikacji widzimy taki ekran:

Rzecz jasna jest to domyślny start poprzez odwołanie się do domeny localhost:8084, sprawdźmy zaś, czy nasz kontroler daje sobie radę. Odwiedzamy adres localhost:8084/index.html i widok nam się nie zmienił – czyli jesteśmy w domu, kontroler działa. Jednakże jak na razie jest to dość słabe HelloWorld. Dorzucimy do tego przykładu ładny widok – czyli podepniemy FreeMakera wraz z całym layoutem. Na pewno będzie się przyjemniej z tym wszystkim pracowało.

Wpierw zatem trzeba się zatroszczyć o same źródła biblioteki FreeMarker. Odwiedzamy starego znajomego POM.xml i w zależnościach projektu dopisujemy:

 <dependency>
   <groupId>org.freemarker</groupId>
   <artifactId>freemarker</artifactId>
   <version>2.3.16</version>
 </dependency>

I dociągamy tę zależność. Kiedy FreeMarker pojawi się nam na liście bibliotek jesteśmy w domu. Tak w ogóle to zdecydowałem się na tę bibliotekę, gdyż jest ponoć bardzo podobna do Velocity (czyli poznaję prawie 2 rzeczy za cenę 1:) ), a do tego Spring posiada do tego wyprowadzenie (w sensie integracji widoku), ale do tego jeszcze dojdziemy. Co konkretnie robi FreeMarker? Organizuje nam widok – jest to w zasadzie system szablonów – czyli dzięki niemu stworzymy sobie cały layout strony, a także będziemy dzięki niemu operować w widoku na danych, które dostaniemy z modelu. Problem z FreeMarkerem jest taki, że w sieci jest masa tutoriali na temat jego połączenia ze Springiem, jednakże najczęściej na przykładzie 1 statycznej strony się kończy. Takie zaś rozwiązania były dobre przy HTMLu 3 jakieś 10 lat temu, jak nie lepiej. Dlatego też ja pokażę jak przygotować projekt do życia – czyli zbudujemy sobie cały layout, który będzie dynamicznie wypełniany w zależności od wywołanego zasobu. Do dzieła!

FreeMarker jest już w naszej bibliotece. Teraz należy poinformować Springa o jego istnieniu, a także ustawić, że wyświetlamy widok inaczej niż za pomocą czystych stron HTMLa. Zatem odwiedzamy web.xml i klepiemy tam takie coś:

<servlet>
    <servlet-name>freemarker</servlet-name>
    <servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>

    <!-- FreemarkerServlet settings: -->
    <init-param>
      <param-name>TemplatePath</param-name>
      <param-value>/WEB-INF</param-value>
    </init-param>
    <init-param>
      <param-name>NoCache</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>ContentType</param-name>
      <param-value>text/html</param-value>
    </init-param>

    <!-- FreeMarker settings: -->
    <init-param>
      <param-name>template_update_delay</param-name>
      <param-value>0</param-value>
    </init-param>
    <init-param>
      <param-name>default_encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>number_format</param-name>
      <param-value>0.##########</param-value>
    </init-param>

    <load-on-startup>1</load-on-startup>
  </servlet>

To na początek kilka podstawowych ustawień servletu FreeMarkera. Wartości są dość jasne – warto tylko zwrócić uwagę na parametr template_update_delay – ustawienie tam wartości 0 jest dobre dla środowiska developerskiego. Dla oddzielnych pików layoutu rzecz jasna utworzymy specjalny katalog w folderze WEB-INF, dzięki czemu nie da się otworzyć poszczególnych składowych w przeglądarce. Dla swojej aplikacji jak zawsze skorzystałem z darmowego szabloniku, który można podejrzeć w tym miejscu. Szablonik rzecz jasna posiada coś takiego jak nagłówek, menu, główną treść i content. Dlatego też naturalnie rozbijemy go na kilka oddzielnych części
i ładować potrzebne nam części. FreeMarker składuje szablony w plikach z rozszerzeniem *.ftl. Tworzymy katalog layout w folderze WEB-INF i wrzucamy tam plik layout.ftl:

<#macro scjptesterLayout>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>SCJPTester</title>
        <meta http-equiv="Content-Language" content="Polish" />
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <link href="/css/main.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div id="header">
          <h1><a href="/index.html">SCJPTester</a></h1>
          <ul id="menu">
            <li class="active"><a href="/index.html">główna</a></li>
            <li><a href="/start.html">zarejestruj/zaloguj</a></li>
            <li><a href="#">demo</a></li>
            <li><a href="#">kontakt</a></li> 
          </ul>
        </div>
    
        
      <div class="wrap">

       <#nested />
          

        </div>
        <div id="footer">
          <p class="right">Design: <a href="http://www.solucija.com/">Luka Cvrk</a></p>
          <p>&copy; Nazwa 'SCJPTester' &middot; jest własnością Michała 'Chlebika' Piotrowskiego</p>
        </div>
    </body>
</html>
</#macro>

Jest to zwykły layout (na razie większość linków i menu są dość umowne), w którym jednakże widać kilka nowości. Pierwsza linijka kodu mówi już wiele – tworzymy własne makro, któremu nadajemy określoną nazwę. Całość HTMLa musi być zawarta w treści tegoż makra. W środku do tego, konkretnie w miejscu gdzie będziemy chcieli, aby wyświetlana zawartość zmieniała się w zależności od wybranego zasobu – wrzuciliśmy znacznik: #nested. W tym miejscy FreeMarker wrzuci nam stosowny widok. Pliki z obrazkami oraz CSSem należy wrzucić bezpośrednio do katalogu webapp, dzięki czemu będzie on dostępny z poziomu przeglądarki – bez tego nic ładnego nam się nie pokaże. W katalogu WEB-INF tworzymy także podkatalog views, gdzie będziemy składować poszczególne widoki dla konkretnych akcji. Taki podział wprowadza trochę czystości do widoków, od razu wiadomo gdzie i czego szukać. Teraz do tego musimy poddać edycji plik frontcontroller-servlet.xml i wrzucić tam taki oto kod (zwróć uwagę na zapisy o kodowaniu – mi zeszło na rozkminienie tego 2h):

<bean id="freemarkerConfig"
  class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
  <property name="templateLoaderPath"
   value="/WEB-INF" />

  <!-- To jest niemozliwie wrecz wazne - bez tego bedziemy mieli krzaki w widoku zamiast UTF-8  -->
 <property name="freemarkerSettings"> 
    <props>
         <prop key="default_encoding">UTF-8</prop>
    </props>
 </property>

 </bean>


 <bean id="viewResolver"
  class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
      <property name="cache" value="true" />
      <property name="prefix" value="/views/" />
      <property name="suffix" value=".ftl" />

      <!-- if you want to use the Spring FreeMarker macros, set this property to true -->
      <property name="exposeSpringMacroHelpers" value="true" />

       <!-- To jest niemozliwie wrecz wazne - bez tego bedziemy mieli krzaki w widoku zamiast UTF-8  -->
      <property name="contentType" value="text/html;charset=UTF-8"></property>

 </bean>

Powiadamiamy w ten sposób FreeMarkera gdzie ma szukać plików z szablonami, a także w jakim formacie się to odbędzie (określenia prefix i suffix dla ViewResolvera). W związku z takim określeniem reguł, kod w kontrolerze musimy zmienić na:

 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {


    return new ModelAndView("index");
  }

Dzięki takiemu podejściu przy ewentualnej zmianie koncepcji widoku wystarczy, że zmienimy dane w pliku XML i możemy zmienić katalog, gdzie nasze pliki leżą, że o i ich rozszerzeniu nie wspomnę. Teraz na koniec wystarczy stworzyć plik index.ftl w katalogu views:

<#import "../layout/layout.ftl" as layout>
<@layout.scjptesterLayout>

  <div id="teaser">
      <div class="wrap">
       
        <div class="box">
          <h2>Witaj na stronach aplikacji <em title="SCJPTester">SCJPTester</em></h2>
          <p>Aplikacja ta powstała jako projekt edukacyjny Michała 'Chlebika' Piotrowskiego, w celu praktycznego poznania frameworka <strong>Spring</strong>, a także szeregu innych
          technologii. Projekt powstał niedawno zatem jego funkcjonalność jest na razie znikoma, jednakże stan ten zmienia się bardzo szybko. Więcej informacji na temat aplikacji
          moża znaleźć <a href="https://chlebik.wordpress.com">na blogu autora.</a></p>
        </div>
      </div>
    </div>


      <div class="col">
        <h3>Zarejestruj <span class="red"> się!</span></h3>
        <p>Bardzo szybki i prosty proces rejestracji pozwoli Ci od razu rozpocząć przygotowania do egzaminu.</p>
      </div>
      <div class="col">
        <h3>Kontroluj <span class="red">postępy</span></h3>
        <p>Dzięki panelowi statystyk będziesz mógł sprawdzić swoje postępy w nauce. Wyniki poprzednich testów zostaną zapisane i w każdej chwili można je przejrzeć.</p>
      </div>
      <div class="col last">
        <h3>Zgłaszaj <span class="red">własne pomysły</span></h3>
        <p>Jeżeli zaproponowane pytania nie są dla Ciebie wyzwaniem - stwórz swoje własne i podziel się nimi z innymi użytkownikami serwisu!</p>
      </div>



</@layout.scjptesterLayout>

I jesteśmy w domu. W pierwszej linijce importujemy nasz layout (wskazujemy konkretny plik) do konkretnej przestrzeni nazw, w tym przypadku layout. Niestety tak to wygląda, gdyż próby podpięcia automatycznego wgrywania pliku layoutu poprzez edycję web.xml nie powiodły się. Gdyby ktoś wiedział jak to osiągnąć byłbym wdzięczny. Uruchamiamy aplikację i przechodzimy pod adres http://localhost:8084/index.html i viola – mamy stronkę główną (poniosło mnie trochę przy marketingowych zwrotach, zatem wybaczcie :).

Teraz przyszedł czas na poprawienie naszej strony startowej. Należy przypomnieć, iż określiliśmy kontroler, który jest odpowiedzialny za obsługę żądań do zasobów kończących się na *.html. Jednakże wypadałoby zrobić coś z faktem, iż nasza główna strona będzie wyświetlana również w sytuacji, kiedy w pasku adresu wpiszemy tylko nazwę domeny. Tutaj w przypadku ProgramBash stosowałem zwykły plik HTML z redirectem. Jednakże nie jest to potrzebne. Do pliku web.xml dopisaliśmy regułę, iż wszystkie requesty kończące się na HTML są przechwytywane przez FreeMarkera. W takim razie w katalogu webapp tworzymy PUSTY plik index.html i dopisujemy go do listy plików powitalnych w web.xml

 <welcome-file-list>
        <welcome-file>index.html</welcome-file>
 </welcome-file-list> 

I teraz już bez konieczności zabaw w jakiekolwiek przenoszenie – wszystkie requesty, które pójdą na naszą domenę (jednakże te “całościowe” z prośbą o stronę główną) zostaną obsłużone w ten sam sposób co requesty przychodzące na adres http://domena.pl/index.html. Proste jak konstrukcja gwoździa, szkoda, że dopiero po tym jak się wyczyta o tym u wujka Google. To tyle na dziś – przyznam, że pomijając przygodę z kodowaniem stron w FreeMarkerze i nieudaną próbę podpięcia domyślnego layoutu poznawanie Springa jest całkiem przyjemne. Szybki rzut oka na klasy kontrolerów dostarczane wraz ze Springiem utwierdził mnie w przekonaniu, że ktoś wiedział co robi projektując te narzędzie.