Stripes (English)

Z FI WIKI
Verze z 8. 11. 2013, 14:21; 232464@muni.cz (diskuse | příspěvky)

(rozdíl) ← Starší verze | zobrazit aktuální verzi (rozdíl) | Novější verze → (rozdíl)
Přejít na: navigace, hledání


Úvod

Stripes jsou nový populární rámec pro vývoj webových aplikací. Poučil se z chyb rámce Apache Struts, kde je nutné udržovat v konzistentním stavu několik konfiguračních souborů a každá akce zpracovávající data od prohlížeče musí být v samostatné třídě.

Stripes nepotřebuje žádné konfigurační soubory, protože využívá

  • anotace zavedené v Java 5.0
  • princip convention-over-configuration zavedený v Ruby-On-Rails.

Instalace a závislosti

Stripes jsou v Maven repository, takže stačí do projektu přidat závislost

 
        <dependency>
            <groupId>net.sourceforge.stripes</groupId>
            <artifactId>stripes</artifactId>
            <version>1.5.6</version>
        </dependency>

Pokud chceme používat upload souborů, je třeba přidat ještě implementaci parsování multipart HTTP requestu:

 
        <dependency>
            <groupId>servlets.com</groupId>
            <artifactId>cos</artifactId>
            <version>05Nov2002</version>
        </dependency>
 

Konfigurace

Stripes jsou realizovány pomocí servlet filteru a servletu. Do deployment descriptoru v souboru WEB-INF/web.xml je tedy potřeba dopsat jejich definice.

Pomocí inicializačních parametrů filtru lze volit další upřesňující nastavení.

  • parametr ActionResolver.Packages určuje, ve kterých balících budou hledány ActionBeans
  • parametr Extension.Packages určuje, ve kterých balících budou hledány implementace rozšíření, např. LocalePicker a podobně
  • parametr LocalePicker.Locales obsahuje seznam Locale a kódování znaků, které aplikace podporuje
  • parametr Interceptor.Classes určuje třídy s tzv. interceptory, které mohou jsou aktivovány v určitých fázích zpracování HTTP requestu
 
    <!-- Stripes -->
    <filter>
        <display-name>Stripes Filter</display-name>
        <filter-name>StripesFilter</filter-name>
        <filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
        <!-- see http://www.stripesframework.org/display/stripes/Configuration+Reference -->
        <init-param>
            <param-name>ActionResolver.Packages</param-name>
            <param-value>cz.muni.fi.pa165.stripes</param-value>
        </init-param>
        <init-param>
            <param-name>Extension.Packages</param-name>
            <param-value>cz.muni.fi.pa165.stripes</param-value>
        </init-param>
        <init-param>
            <param-name>LocalePicker.Locales</param-name>
            <param-value>cs:utf-8,en:utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>Interceptor.Classes</param-name>
            <param-value>net.sourceforge.stripes.integration.spring.SpringInterceptor</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <url-pattern>*.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <filter-mapping>
        <filter-name>StripesFilter</filter-name>
        <servlet-name>StripesDispatcher</servlet-name>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <servlet>
        <servlet-name>StripesDispatcher</servlet-name>
        <servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>StripesDispatcher</servlet-name>
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>

Výše uvedená konfigurace váže akce jen na URL končící na příponu .action. Pokud chceme používat tzv. clean URLs, je třeba přidat ještě definici

 
 <filter>
      <description>Dynamically maps URLs to ActionBeans.</description>
      <display-name>Stripes Dynamic Mapping Filter</display-name>
      <filter-name>DynamicMappingFilter</filter-name>
      <filter-class>net.sourceforge.stripes.controller.DynamicMappingFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>DynamicMappingFilter</filter-name>
      <url-pattern>/*</url-pattern>
      <dispatcher>REQUEST</dispatcher>
      <dispatcher>FORWARD</dispatcher>
      <dispatcher>INCLUDE</dispatcher>
    </filter-mapping>

Je výhodné pro implementaci business logiky používat Spring beans, v tom případě můžeme přidat ještě aktivaci Spring, Spring beans jsou pak injektovány do proměnných označených anotací @SpringBean.

 
    <!-- Spring -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring-context.xml</param-value>
    </context-param>

Při použití JSTL tagů pro internacionalizaci je také výhodné nastavit jako zdroj lokalizovaných textů soubory s lokalizovanými chybovými hlášeními, které používají Stripes při validaci dat z formulářů:

 
    <!-- JSTL fmt: tags will use Stripes resources-->
    <context-param>
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>StripesResources</param-value>
    </context-param>

ActionBeans

Výkonný kód se umisťuje do tříd implementujících interface ActionBean, tedy může dědit z libovolné třídy. Interface ActionBean jednak označuje třídu, které se při startu aplikace navážou na nějaké URL, a druhak předepisuje dvě metody getContext() a setContext(), pomocí kterých je ActionBeanu nastaven objekt zpřístupňující všechny podstatné informace.


Není nutné mít konfigurační soubor pro vazbu mezi URL a třídou, protože rámec sám najde všechny třídy, které implementují ActionBean, a vazbu na URL vytvoří jedním ze dvou způsobů:

  • implicitně z názvu třídy (cz.neco.action.bla.MujActionBean -> /bla/Muj.action)
  • z anotace třídy @UrlBinding

Metoda pro zpracování HTTP požadavku se může jmenovat jakkoliv, jen musí být public a vracet typ Resolution.

Takových metod může být v jednom ActionBeanu několik. Pokud je jich víc než jedna, právě jedna z nich musí být označena anotací @DefaultHandler, a ta je vyvolána, pokud parametru URL nestanoví jinak.

Návratovou hodnotou metody je objekt typu Resolution, který přímo určuje, kam se předá řízení, a jeho konkrétní podtyp specifikuje, zda se řízení předá vnitřně (ForwardResolution) nebo přesměrováním prohlížeče (RedirectResolution).


Stripes nepotřebuje samostatnou třídu ani pro data z formuláře (form bean), používá přímo ActionBean.

Stejný příklad jako je použit u Struts by se tedy zapsal tímto ActionBeanem:

 
package cz.muni.fi.pa165.stripes;
 
import net.sourceforge.stripes.action.*;
import java.util.List;
 
@UrlBinding("/zbozi.action")
public class ZboziNaSkladeActionBean implements ActionBean {
 
    private ActionBeanContext ctx;
    public ActionBeanContext getContext() { return ctx; }
    public void setContext(ActionBeanContext ctx) { this.ctx = ctx; }
 
    public Resolution mameZbozi() {
        if (Sklad.mameNejakeZbozi()) {
            return new ForwardResolution("/seznamzbozi.jsp");
        } else {
            return new ForwardResolution("/zadneneni.jsp");
        }
    }
 
    public List getSeznamZbozi() {
        return Sklad.getZbozi();
    }
 
}

a stránka seznamzbozi.jsp by vypadala nějak takto:

 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
 ...
 <s:useActionBean var="actionBean" beanClass="cz.muni.fi.pa165.stripes.ZboziNaSkladeActionBean" />
 <table>
 <c:forEach var="z" items="${actionBean.seznamZbozi}">
   <tr>
     <td><c:out value="${z.nazev}"/></td>
     <td><c:out value="${z.mnozstvi}"/></td>
   </tr>
 </c:forEach>
 </table>

Stripes lze integrovat s různými rámci pro generování stránek, pro použití s JSP je předpřipravena knihovna značek.

(Značku <s:useActionBean> není nutné používat, instance příslušného ActionBean je automaticky dostupná jako atribut requestu jménem actionBean, ale vývojové prostředí IntelliJ IDEA podle ní dokáže kontrolovat správnost výrazů jazyka EL uvozených znaky ${}).

Formuláře a validace dat

Obsluha formulářů ve Stripes předpokládá, že není nutné vytvářet nové entitní objekty jen kvůli webové vrstvě, a lze používat přímo objekty z modelu. Např. mějme třídu pro editovaný objekt Zaznam:

 
public class Zaznam {
  private String jmeno;
  private int pocet;
  public void setJmeno(String jmeno) { this.jmeno = jmeno; }
  ...
}

Pak formulář pro editaci by mohl vypadat nějak takto:

 
 <!-- formulář navázaný na určitý ActionBean -->
 <s:form beanclass=cz.muni.fi.pa165.stripes.ZaznamActionBean" method="post">
 
   <!-- zobrazení chyb, pokud nastaly -->
   <s:errors/>
 
   <!-- lokalizovaný popis pole a editovatelné pole formuláře -->
   <s:label name="zaznam.jmeno"/>:  <s:text name="zaznam.jmeno" size="30"/> <br/>
   <s:label name="zaznam.pocet"/>:  <s:text name="zaznam.pocet" size="3"/> <br/>
   
   <!-- odesílací tlačítko s lokalizovaným nápisem-->
   <s:submit name="save"><f:message key="submit"/></s:submit>
 </s:form>
 

a příslušný ActionBean by validaci ošetřil anotacemi:

 
package cz.muni.fi.pa165.stripes;
 
public class ZaznamActionBean implements ActionBean {
 //...
@ValidateNestedProperties(
   value = {
     @Validate(on = {"pridej","uloz"}, field = "jmeno", required = true),
     @Validate(on = {"pridej","uloz"}, field = "pocet", required = true, minvalue = 1)
     }
 )
 private Zaznam zaznam;
 public Zaznam getZaznam() { return zaznam; }
 public void setZaznam(Zaznam zaznam) { this.zaznam = zaznam; }
 
 @SpringBean
 ZaznamManager zaznamManager;
 
 public Resolution uloz() {
     zaznamManager.uloz(zaznam):
     return new RedirectResolution(this.getClass());
 }

využívá pro převzetí dat z formuláře property typu Zaznam, která má vnořené property jmeno a pocet typů řetězec a číslo, přičemž při vyvolání metod pridej() a uloz() musí být obě zadány, druhá musí být číslo větší než 1.

Instance třídy Zaznam, pokud neexistuje, je automaticky vytvořena pomocí jejího bezparametrického konstruktoru.

Konverze mezi řetězci a příslušnými typy, v tomto příkladu na int u položky pocet, probíhají automaticky. Pokud se nepovedou, automaticky jsou vygenerována chybová hlášení a řízení je předáno zpět na stránku s formulářem, kde značka <s:errors>chybová hlášení zobrazí. Navíc pole formuláře obsahující chybné hodnoty mají nastavenu CSS třídu .error, takže je možné je graficky zvýraznit.

Chybová hlášení je možné předefinovat pro konkrétní formulář.

Složitější kontroly je možné dát do samostatných metod, způsob zobrazování lokalizovaných chybových hlášení je obdobný jako ve Struts:

 
@ValidationMethod(on = "pridej")
 public void kontrola(ValidationErrors errors) {
    if (zaznam.getJmeno().equals("diamanty")&&zaznam.getPocet()>10) {
        errors.add("diam", new LocalizableError("klic.chyby.moc"));
    }
 }

Pokud stránka s formulářem potřebuje nějaká předpřipravená data k úspěšnému zobrazení, je třeba tyto data předpřipravit i při neúspěšné validaci. TO lze jednoduše udělat tak, že třída bude implementovat rozhraní ValidationErrorHandler a implementovat jeho metodu handleValidationErrors(), ve které můžeme data předpřipravit. Například:

 
@Override
    public Resolution handleValidationErrors(ValidationErrors errors) throws Exception {
        username = userManager.getUserName(userId);
        return null;
    }

Layout Tags

Stripes obsahuje i vylepšený nástroj pro skládání stránek, ekvivalentní Struts Tiles.

Není třeba mít samostatný konfigurační soubor jako tiles-defs.xml. Stačí vytvořit JSP stránku se šablonou stránky, např:

 
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
 
<stripes:layout-definition>
 <html>
 <head>
   <title><f:message key="${klicTitulkuStranky}"/></title>
   <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/default.css"/>
 </head>
 <body>
  ... menu atd. ...
  <stripes:layout-component name="contents"/>
 </body>
</html>
</s:layout-definition>

a v JSP stránkách se lze na tuto šablonu odkazovat:

 
<%@ taglib prefix="s" uri="http://stripes.sourceforge.net/stripes.tld" %>
<s:layout-render name="/layout.jsp" klicTitulkuStranky="seznam">
  <s:layout-component name="contents">
      ... obsah stránky ...
  </s:layout-component>
</s:layout-render>

Všechny atributy tagu s:layout-render jsou předány šabloně jako atributy, která je může využít.

Doporučované postupy

Při tvorbě aplikace je dobré se držet doporučovaných postupů popsaných v Stripes Best Practices.

Typovaný přístup k atributům Session a ServletContext

ActionBeans jsou odstíněny od tříd Servlet API tím, že dostávají instanci třídy ActionBeanContext, přes kterou jsou přístupné objekty jako HttpServletRequest, HttpSession nebo ServletContext. To je výhodné pro testování.

Dá se to využít i k typovanému přístupu k atributům HttpSession a ServletContextu. Lze si vytvořit vlastního potomka třídy ActionBeanContext, který může mít metodu např:

 
public class MujBeanContext extends ActionBeanContext {
    @SuppressWarnings({"unchecked"})
    public MujTyp getMujTyp() {
        return (MujTyp) getServletContext().getAttribute("muj_typ");
    }
}

a v ActionBeanech odpadne neustálé přetypovávání atributů. Alternativně můžete všechny ActionBeans dědit ze společného předka, který typovaný přístup k atributům zajišťuje.

Anotace @Before pro natažení objektů

Při editování již existujících záznamů je potřeba je vytáhnout z databáze ještě před fází mapování údajů z formuláře na action bean. To lze jednoduše udělat tak, že ve formuláři je hidden field s id objektu, a metodě označené anotací @Before se objekt načte:

 
@Before(LifecycleStage.BindingAndValidation)
public void natahnout() {
    this.neco = getModel().findNecoById(context.getRequest().getParameter("id"));
}


Ukázka kompletní aplikace je v článku Cvičení Stripes