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.

Advertisements

6 thoughts on “SCJPTester w wersji HelloWorld!

  1. darek

    W sumie nie pomyślałem, żeby dać index.html, albo zapomniałem, dzięki za zwrócenie uwagi.

    A co ze stronami błędu, ja jak robiłem pracę dyplomową to miałem ten problem, że strona błędu nie jest składana tak samo jak zwykły Request, toteż gdy występował błąd, miał zupełnie inny layout ;/

  2. chlebik Post author

    W to jeszcze nie uderzałem, ale w Grailsach mozna bylo obsluzyc zadanie z mapowaniem ‘/404’ czy ‘500’ i wskazac strone bledu. Jak to jest w Springu jeszcze nie wiem, ale sie na pewno rozejrze przy okazji nadchodzacego wpisu.

  3. Michał Sz.

    No stary, za wskazówkę z kodowaniem we freemarker masz duży browar. Ja też pewnie spędził bym ze 2 godziny na szukaniu. Wielkie dzięki.

  4. Mikel

    Hej, mam problem – stronka nie widzi css:(. Robię wszystko dokładnie tak jak opisałeś i ciągle brak stylów. Folder /css/main.css mam bezpośrednio w folderze webapp. Poza tym FreeMaker z layoutem działa bez zarzutu. Proszę o pomoc.
    Pozdrawiam

  5. Przemo

    Witam, Podpinam się pod kolegę powyżej. FreeMarker + Spring działa znakomicie. Stronka jednak nie zasysa css. Próbowałem różnych kombinacji ze zmianą położenia pliku main,css, nie bardzo wiem co jeszcze mógłbym zrobić. Fajnie jakby ktoś pomógł, zależy mi na podmianie tego stylu. Z góry dziękuję i pozdrawiam. BTW dobra robota z tym artykułem ;] Pozdrawiam

  6. chlebik Post author

    Kwestia tego, gdzie po zbudowaniu Mavenem laduja te foldery (ze zdjeciami czy CSSem). Najlepiej jest podejrzeć zawartość WARa i jeśli foldery te będą na tym samym poziomie co WEB-INF i META-INF to powinny być widoczne z pliku layoutu za pomocą składni ‘css/style.css’ i tak dalej.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s