Meddelande

Minska
No announcement yet.

[Java] Play! Framework - en introduktion

Minska
X
 
  • Filter
  • Klockan
  • Show
Clear All
new posts

  • [Java] Play! Framework - en introduktion

    Jag har nyligen upptäckt ett intressant ramverk för Java, som heter Play! Framework. Jag har egentligen bara hunnit känna på det men är redan imponerad över hur enkelt det är att använda och hur snabbt man utvecklar med det. Det är inte lika stort och flexibelt som t.ex. Spring, men om man vill ha något relativt enkelt gjort på en kort utvecklingstid kan Play! vara värt att titta närmare på.

    Play! är ett MVC-ramverk som använder JPA och Hibernate under huven – och som utvecklare behöver man inte bry sig alls om konfigurationen för JPA och Hibernate! Det är väldigt smidigt gjort, man får Model-superklasser att ärva från för att en smidig integration mot datakällan.

    Vidare har Play! ett utmärkt gränssnitt för unit testning, med antingen JUnit eller Selenium, där man kommer åt testen via webläsaren. Det är dock ingenting jag fokuserar på i den här texten.

    Jag kommer visa ett kortare exempel där jag bygger upp en filmsajt för listningar av filmer och skådespelare. De redskap som jag kommer använda är Play! Framework och eclipse för att hantera min kod.

    Upplägget ser ut som följer:
    1. Installation och hur man skapar ett projekt
    2. Sätta upp modeller och datakälla
    3. Första titt på vyer
    4. Sätta upp kontrollers
    5. Vyer för våra kontrollers
    6. URL-mappning och rutter
    7. Knyt ihop säcken!
    8. Avslutning


    1. Installation och hur man skapar ett projekt

    Ladda ned ramverket och packa upp det på valfri plats, ingen automatisk installation sker. Allt vi behöver finns i zip-arkivet. Efter att du packat upp Play! behöver vi peka miljövariabeln PATH till platsen där Play! hittas. I mitt fall, jag sitter i GNU/Linux, sparade jag Play! i min hemmapp home/bjorn/play så för att mitt system ska kunna hitta Play! lägger jag till följande rader i min ~/.bash_profile:
    Kod:
    PATH=$HOME/play:$PATH
    export PATH
    Start om terminalen du sitter i och du ska kunna komma åt Play! genom att helt enkelt skriva play.
    Då var installationen klar! Dags att sätta upp ett första projekt.
    Placera dig i den mapp där du vill att ditt nya projekt ska husera, i mitt fall blev det /home/bjorn/projects. Jag väljer att döpa det här projektet till moviedb och ska också finnas i en mapp som heter moviedb, så för att skapa projektet och mappen kör jag helt enkelt kommandot:
    Kod:
    play new moviedb
    Kommandot skapar en projektmapp med namnet moviedb. Vi får nu en fråga om vad vi vill döpa själva projektet till, och här anger jag moviedb igen.
    Nu har vi skapat projektet moviedb i mappen med samma namn, och Play! har nu skapat ett skelett med kataloger och filer i projektmappen. Redan nu kan vi köra projektet genom:
    Kod:
    play run moviedb
    Vi kan titta på vårat verk genom en webläsare, pekad mot addressen http://localhost:9000/ - 9000 är Play!s standardport vilken går att ändra. Det som visas är Play!s välkommen-skärm, som bara visar att allt har gått som det ska.
    För att vi ska kunna använda eclipse som IDE till vårat nya Play! projekt på ett smidigt sätt, kan vi låta Play! göra om vårat projekt till ett eclipse-projekt genom kommandot:
    Kod:
    play eclipsify moviedb
    Öppna nu upp eclipse, gå till File > Import... och välj General > Existing Projects into Workspace, och lägg till moviedb-mappen. Vips, så fungerar allt out-of-the-box med eclipse!

    I mappen app finns de filer vi kommer att arbeta med, nämligen våra kontroller, modeller och vyer. Vi kommer också att titta lite i conf för den lilla konfiguration vi behöver göra. Tillsvidare använder vi standardkonfigurationen av Play!.

    2. Modeller och datakälla

    Som jag tidigare sa sköter Play! all mappning med JPA och Hibernate åt oss, utan att vi behöver bry oss om konfigurationsinställningar och liknande. Däremot måste vi tala om för Play! vilken typ av datakälla vi vill använda. Under utvecklingstiden kan vi använda HSQLDB, en "in-memory"-databas från Hibernate. Så fort vi stänger vårat projekt töms alltså databasen på information. I eclipse öppnar vi upp filen conf/application.conf. För att få Play! att använda HSQLDB som datakälla är det enda vi behöver göra att avmarkera rad 75, under Database configuration, så det står db=mem.

    Min första modell blir klassen som representerar en skådespelare. De egenskaper vi lagrar för våra skådespelare är enbart namn. Vanligtvis sätter man upp sina modeller med inkapsling och privata instansvariabler för att lagra värden, men Play! arbetar lite annorlunda. Här har vi istället publika instansvariabler som Play! senare kapslar in åt oss. Varje modell måste också ha annoteringen @Entity för att JPA ska hantera det som en entitet/modell, liksom en konstruktor med de nödvändiga parametrarna för instansen. Skapa filen Actor.java under models-paketet i eclipse. I sin helhet ser klassen ut så här:
    java:

    package models;

    import play.db.jpa.*;

    import javax.persistence.*;

    /* Tala om för JPA att det här är en entitet */
    @Entity
    public class Actor extends Model { /* Få funktionalitet från Play!s modeller */
    public String name;

    public Actor(String name) {
    this.name = name;
    }
    }

    Den andra modellen blir klassen som representerar en film. De egenskaper våra filmer behöver är en titel för varje film, en handling och en samling med skådespelare. Eftersom att en film kan ha flera skådespelare och en skådespelare kan medverka i flera filmer, blir relationen mellan dom många-till-många. Det måste märkas ut med annoteringar för att JPA ska förstå sambandet mellan våra entiteter. Skapa filen Movie.java under models-paketet i eclipse. I sin helhet ser klassen ut så här:

    java:

    package models;

    import play.db.jpa.*;

    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.*;

    /* Tala om för JPA att det är en entitet */
    @Entity
    public class Movie extends Model { /* Få funktionalitet från Play!s modeller */
    public String title;
    /* Tala om för JPA att det ska kunna rymmas
    mycket text i variabeln plot */
    @Lob
    public String plot;

    /* Vår lista med skådespelare återkommer vi strax till */
    @ManyToMany(cascade=CascadeType.PERSIST)
    public List<Actor> cast;

    /* Konstruktorn tar emot de nödvändiga parametrarna
    som behövs för att skapa en film */
    public Movie(List<Actor> cast, String title, String plot) {
    this.cast = cast;
    this.title = title;
    this.plot = plot;
    }
    }

    Vi ska ha ytterligare en modell i vårt projekt, för att visa ett en-till-många-förhållande, nämligen s.k. arbetstitlar för en film. En film kan alltså ha flera arbetstitlar, innan den slutgiltiga titeln sätts. Modellen ser ut som följer:
    java:

    package models;

    import play.db.jpa.*;

    import javax.persistence.*;

    @Entity
    public class WorkingTitle extends Model {
    public String title;
    /* Den här entiteten hör till “många”-gruppen
    i ett en-till-många-förhållande */
    @ManyToOne
    public Movie movie;

    /* På “många”-sidan injicerar vi vilken film
    arbetstiteln hör till */
    public WorkingTitle(Movie movie, String title) {
    this.movie = movie;
    this.title = title;
    }
    }


    Nu måste vi ju dock uppdatera vår Movie-modell så att den innehåller alla sina arbetstitlar. Lägg till följande efter listan med skådespelare:
    java:

    @OneToMany(mappedBy=”movie”, cascade=CascadeType.ALL)
    public List<WorkingTitle> workingTitles;

    Vi behöver också initiera listan i konstruktorn, på samma sätt som vi gör med listan över skådespelare - men vi behöver inte ta emot en lista med arbetstitlar som parameter:
    java:

    public Movie(List<Actor> cast, String title, String plot) {
    this.workingTitles = new ArrayList<WorkingTitle>();


    Då ska våra modeller vara klara! Redan nu kan man köra projektet, för att se så att man inte har gjort något galet – Play! spottar direkt ur sig felmeddelanden. Kör play run moviedb från terminalen och peka webläsaren till http://localhost:9000 – förhoppningsvis ska du få upp samma välkomstskärm som första gången du körde projektet. Om du får ett felmeddelande är det väldigt välbeskrivande, med radnummer på var felet inträffade och i vilken fil tillsammans med några rader kod från där problemet hittades.

    3. Första titt på vyer

    I mappen views hittar vi våra vyer, sorterade i mappar med samma namn som kontrollern som anropar dom. Än så länge har vi inte rört våra kontrollers, och det finns bara två mapper – en för standardkontrollern Application och en för felhanteringen med mappen errors. I dessa mappar finns sidmallar i form av HTML-filer, mest kända som "template"-mallar. Direkt under mappen views finns en fil som heter main.html, som innehåller dokumentens huvud och fot – och som man använder som standardmall. In i denna mall injiceras sedan varje anrops (även kallad action) template-mall.

    Om man har programmerat något i Groovy känner man igen en del från template-mallarnas syntax. Om man öppnar main.html (i eclipse högerklickar man och väljer "Open With > Text editor") så ser man var det injiceras data – nämligen vid alla #{...}/@{...}/${...}-taggar. Det magiska i filen händer vid #{doLayout /}, där action-templaten injiceras. Vi kommer snart att se mer på hur vi hanterar data i våra vyer.

    4. Kontrollers

    En kontroller (fr. eng. Controller) agerar mellanlager mellan entiteter/datakälla och vyer. De bestämmer vilken data som ska skickas till vyn, helt enkelt. Själva kontrollern är en klass, och dess metoder kallas för action-metoder – det är dessa som anropas när besökaren efterfrågar en sida. Varje action-metod svarar mot en vy med samma namn. T.ex. vyn views/listing/actors.html anropas när metoden actors() i kontrollern controllers/Listing.java anropas.

    Play! har som sagt en standardkontroller färdig åt oss, Application.java, men det är väl lika bra att göra en egen på en gång?! Det vi vill göra på vår webplats är att lista filmer och tillhandahålla information kring dem, samt att lista skådespelare och information om dessa. Det låter väl ganska vettigt att då skapa en Listing-kontroller? Vi börjar med ett skelett:
    java:

    package controllers;

    import play.*;
    import play.mvc.*;

    public class Listing extends Controller {

    }

    Om en besökare efterfrågar roten (/) på vår webplats, vill vi lista alla de filmer vi har i vår databas. Eftersom att ingen action har efterfrågats då, sätter vi upp en ingångs-action (eng. default action) kallad inde). I den här metoden vill vi då hämta alla våra filmer och skicka vidare till vyn, så att vyn kan presentera dom på lämpligt sätt. Alla action-metoder deklareras som statiska, för att Play! ska kunna anropa dom. Play! använder Javas Reflection API för att hitta i våra klasser, och anropar de nödvändiga metoderna. Play!s modeller tillhandahåller metoder enligt Active Record-pattern, vilket gör det enkelt för oss att skapa/läsa/uppdatera/ta bort objekt i/till datakällan – känt under akronymen CRUD (create/read/update/delete). Man använder Hibernates egna SQL-dialekt HSQL för att göra urval, sortering eller gruppering på resultatet. Action-metoden inde) i kontrollern Listing:
    java:

    package controllers;

    import play.*;
    import play.mvc.*;

    import models.*;

    import java.util.List;

    public class Listing extends Controller {
    public static void index() {
    /* Metoden find() i vår modell Movie är ärvd från
    Play!s Model-klass. Den kan ta en HSQL-fråga
    som parameter och returnerar ett fråge-objekt.
    Fråge-objektet (av typen JPAQuery) har en metod fetch()
    som kör frågan och returnerar alla träffar från
    vår datakälla */
    List<Movie> movies = Movie.find(“order by title asc”).fetch();

    /* För att vår vy ska kunna nå resultatet skickar vi med
    vår film-lista till metoden render(), som i sin tur
    parsar vyn och skickar den till besökaren */
    render(movies);
    }
    }

    Vi vill ju också kunna lista alla våra skådespelare, så vi skapar en till action-metod som vi kallar för actors():
    java:

    public static void actors() {
    List<Actor> actors = Actor.find(“order by name asc”).fetch();
    render(actors);
    }

    Hur visar vi dom nu då?

    5. Vyer för vår kontroller

    Då har vi två vyer att skapa, en som listar filmer och en som listar skådespelare. Först och främst editerar vi main.html så att den passar oss:
    HTML:

    <!DOCTYPE html>

    <html>
    <head>
    <title>MovieDB - min egen filmdatabas!</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link rel="stylesheet" type="text/css" media="screen"
    href="@{'/public/stylesheets/main.css'}">
    </head>
    <body>
    <div id=”page”>
    #{doLayout /}
    </div>
    </body>
    </html>

    Då börjar vi med vyn för action-metoden inde) - börja med att skapa katalogen Listing under views, och filen index.html i den nyligen skapade katalogen, med följande innehåll:
    HTML:

    <!-- Här säger vi till mallen att den ska injiceras i main.html //-->
    #{extends 'main.html' /}

    <ul>
    <!-- Genom olika funktioner i template-språket, kan vi åstadkomma
    diverse funktionalitet, kommandot 'list' itererar igenom
    listan 'movies' och skapar en lokal variabel 'movie' som svarar
    mot den nuvarande platsen i listan. //-->
    #{list items:movies, as:'movie'}
    <!-- Genom variabeln 'movie' kommer vi nu åt alla egenskaper
    hos vår film //-->
    <li>${movie.title}</li>
    #{/list}
    </ul>

    Vi skapar en liknande template för våra skådespelare, skapa filen actors.html i samma mapp:
    HTML:

    #{extends 'main.html' /}

    <ul>
    #{list items:actors, as:'actor'}
    <li>Skådespelare: ${actor.name}</li>
    #{/list}
    </ul>


    6. URL-mappning och rutter

    För att vi nu ska kunna besöka vår filmdatabas behöver vi ändra om lite bland URL-mappningen för Play!. Fortfarande anropas nämligen Application.java som standard-kontroller om vi kör vårat projekt, och så kan vi ju inte ha det! Öppna upp filen conf/routes och titta på hur sökvägar definieras.
    Kod:
    # Home page
    GET     /                                       Application.index
    
    # Map static resources from the /app/public folder to the /public path
    GET     /public/                                staticDir:public
    
    # Catch all
    *       /{controller}/{action}                  {controller}.{action}
    Vi kan alltså komma åt vår inde)-metod i Listing-kontrollern genom att ange URLn http://localhost:9000/listing/index – men vi vill ju nå den med ett rot-anrop! Det som behövs är alltså att ändra första rutten för GET / så att den pekar mot vår kontroller och metod:
    Kod:
    GET     /                                       Listing.index
    Lägg även in en rutt till listan med skådespelare:
    Kod:
    GET     /actors                                 Listing.actors
    GET     /actors/                                Listing.actors
    Nu är rutterna klara, men vi har ju ingen data! Vi behöver ett sätt att lägga till information till vår databas.

    7. Knyt ihop säcken

    Vi gör det enkelt för oss och skapar ett formulär under listningen av filmer, där man kan lägga till en film och dess skådespelare. Editera views/Listing/index.html:
    HTML:

    #{form @Listing.addData()}
    <p>
    <b>Filmens namn:</b><br />
    <input type="text" name="title" />
    </p>
    <p>
    <b>Handling:</b><br />
    <textarea name="plot"></textarea>
    </p>
    <p>
    <b>Skådespelare:</b> (separera med kommatecken)<br />
    <input type="text" name="actors" />
    </p>
    <p>
    <b>Arbetstitlar:</b> (separera med kommatecken)<br />
    <input type="text" name="workingTitles" />
    </p>
    <p>
    <input type="submit" value="Lägg till film" />
    </p>
    #{/form}

    Play! har i sitt template-språk färdig funktionalitet för att skapa fomulär. @-tecknet visar att det gäller en pekning till en kontroller och action-metod, annars byggs formuläret som vanligt. Som man säkert förstår behövs nu ytterligare en metod i Listing-kontrollern, nämligen addData(). Vi får in datat från formuläret till parameterlistan för metoden, i den ordning vi la till den i formuläret:

    java:

    public static void addData(String title, String plot, String actors, String workingTitles) {
    String[] actorList = actors.split(",");
    String[] wtList = (workingTitles.trim().length() > 0) ? workingTitles.split(",") : new String[0];

    List<Actor> cast = new ArrayList<Actor>();
    for (String s : actorList) {
    /* Vi ser först om det skådespelaren redan finns i
    databasen, annars skapar vi honom/henne */
    Actor actor = Actor.find("byName", s.trim()).first();
    if (actor == null) {
    actor = new Actor(s.trim());
    }

    cast.add(actor);
    }
    /* Skapa filmen och spara den tillsammans med
    skådespelarna */
    Movie movie = new Movie(cast, title, plot);
    movie.save();

    /* Om filmen hade några arbetstitlar skapar vi dom här,
    och sparar ner dom med relationen till filmen */
    for (String s : wtList) {
    new WorkingTitle(movie, s.trim()).save();
    }
    /* Rendera index()-metoden för besökaren */
    index();
    }

    Nu kan vi skapa filmer, skådespelare och arbetstitlar! Filmerna listas under http://localhost:9000/ och alla skådespelare hittar man under http://localhost:9000/actors.

    8. Avslutning
    Play! Framework är väldigt enkelt och snabbt att jobba med, och jag har såklart bara skrapat lite på ytan i det här exemplet. I exempelkoden som finns för nedladdning är koden något mer genomarbetad och lite mer funktionalitet finns inlagt. Det finns flera moduler som kan vara av intresse att titta på, som jag inte går igenom, som t.ex. admingränssnittet som Play! åstadkommer lekande lätt! Hoppas att läsaren har fått ut något vettigt ur det tunna materiel jag skrivit ned, och att törsten av att lära sig mer om ramverket har infunnit sig.

    Happy coding!
    Björn Wikström

    Play! Framework: http://playframework.org/
    En mer genomgående introduktion hittas på http://www.playframework.org/documentation/1.0.2.1/home
    Last edited by SPiN; 2011-04-20, 16:37.
    "Knock off the hippie shit, strap on a helmet and start shooting. This is Malibu, baby! I want you to storm that beach like it's fuckin' Normandy!"

  • #2
    Tack för en bra artikel. Play verkar vara ett intressant ramverk. Jag har precis börjat leka lite med det. Kanske kommer att använda det på nån liten sajt, men måste lära mig mer först.

    Jag har inte helt förstått arkitekturen. Play verkar ju inte använda sig av Java Servlets. Som jag har förstått det så skapar Java Servlets-containrar en ny tråd per besökare, vilket kan ta upp en del minne. Play verkar använda Netty istället. Betyder det att Play är mer event-driven och inte skapar en ny tråd per besökare?

    Det är nu nästan ett år sen artikeln skrevs, har du eller kanske nån annan använt Play Framework för nåt skarp projekt? Hur upplevde ni det?

    Kommentera


    • #3
      Tack.

      Helt riktigt, Play! använder inte J2EEs Servlet API. Då skulle det nämligen inte vara möjligt med "code hotswapping", och du skulle få publicera om dina projektfiler till en servlet container efter varje koduppdatering. I längden blir det ganska tradigt under utvecklingstiden, när man ibland vill göra snabba kodförändringar. Jag tror inte att minnesanvändningen var någon anledning till att strunta i Servlet APIet. Att förbigå Servlet API innebär också att Play! blir helt stateless, och lätt kan lastbalanseras.

      Däremot har en utvecklare skrivit en Servlet-wrapper, som i stort sett enbart binder in Play!-projektet i en/flera servlets.

      Jag har inte lyckats få in Play! på jobbet, så än så länge är det bara i små hobbyprojekt som inte gått live jag använt Play!. Tyvärr.
      "Knock off the hippie shit, strap on a helmet and start shooting. This is Malibu, baby! I want you to storm that beach like it's fuckin' Normandy!"

      Kommentera


      • #4
        Nu har ju även scala-versionen blivit riktigt intressant:
        http://scala.playframework.org/
        Min alldeles egna directory listing.

        Kommentera


        • #5
          Ja, den skulle jag gärna vilja känna lite mer på! När tid finnes...
          "Knock off the hippie shit, strap on a helmet and start shooting. This is Malibu, baby! I want you to storm that beach like it's fuckin' Normandy!"

          Kommentera


          • #6
            Tack för ett intressant uppslag. Jag försökte att ta hem dokumentationen igår. Nu ser jag att de andra länkarna fungerar men ditt eget är väl borttaget.
            Snygg beskrivning , och lättförståelig säger Lasp
            Livet är kort och Nu!
            Läs mera!
            !?

            Kommentera


            • #7
              Tack, och helt riktigt -- jag har lyckats plocka bort exempelfiler och PDF-versionen från min server. Jag hittar dom inte på någon disk heller, men någonstans ligger dom tror jag... Trodde att jag lyckats plocka bort alla länkar, men det var en kvar såg jag nyss
              "Knock off the hippie shit, strap on a helmet and start shooting. This is Malibu, baby! I want you to storm that beach like it's fuckin' Normandy!"

              Kommentera

              Working...
              X