SeamFramework.orgCommunity Documentation

章 3. Getting started with Web Beans, the Reference Implementation of JSR-299

3.1. Using JBoss AS 5
3.2. Using Apache Tomcat 6.0
3.3. Using GlassFish
3.4. numberguess 範例
3.4.1. The numberguess example in Tomcat
3.4.2. The numberguess example for Apache Wicket
3.4.3. The numberguess example for Java SE with Swing
3.5. 轉譯器範例

The Web Beans is being developed at the Seam project. You can download the latest developer release of Web Beans from the the downloads page.

Web Beans comes with a two deployable example applications: webbeans-numberguess, a war example, containing only simple beans, and webbeans-translator an ear example, containing enterprise beans. There are also two variations on the numberguess example, the tomcat example (suitable for deployment to Tomcat) and the jsf2 example, which you can use if you are running JSF2. To run the examples you'll need the following:

You'll need to download JBoss AS 5.0.1.GA from jboss.org, and unzip it. For example:

$ cd /Applications
$ unzip ~/jboss-5.0.1.GA.zip

Next, download Web Beans from seamframework.org, and unzip it. For example

$ cd ~/
$ unzip ~/webbeans-$VERSION.zip

接下來,我們需要讓 Web Bean 知道 JBoss 的位置在哪裡。請編輯 jboss-as/build.properties 然後設置 jboss.home 內容。例如:

jboss.home=/Applications/jboss-5.0.1.GA

To install Web Beans, you'll need Ant 1.7.0 installed, and the ANT_HOME environment variable set. For example:

JBoss 5.1.0 comes with Web Beans built in, so there is no need to update the server.

$ unzip apache-ant-1.7.0.zip
$ export ANT_HOME=~/apache-ant-1.7.0

Then, you can install the update. The update script will use Maven to download Web Beans automatically.

$ cd webbeans-$VERSION/jboss-as
$ ant update

現在,您已準備好建置您的第一個範例!

提示

The build scripts for the examples offer a number of targets for JBoss AS, these are:

  • ant restart - 以分解的格式來建置範例

  • ant explode - 在不重新建置的情況下更新一個已分解的範例

  • ant deploy - 以 jar 格式來建置範例

  • ant undeploy - 將範例由伺服器中移除

  • ant clean - 清除範例

若要建置 numberguess 範例:

$ cd examples/numberguess
ant deploy

Start JBoss AS:

$ /Application/jboss-5.0.0.GA/bin/run.sh

提示

If you use Windows, use the run.batscript.

請等待應用程式的建置,然後在 http://localhost:8080/webbeans-numberguess 花上幾個小時!

Web Beans includes a second simple example that will translate your text into Latin. The numberguess example is a war example, and uses only simple beans; the translator example is an ear example, and includes enterprise beans, packaged in an EJB module. To try it out:

$ cd examples/translator
ant deploy

請等待應用程式的建置並進入 http://localhost:8080/webbeans-translator

You'll need to download Tomcat 6.0.18 or later from tomcat.apache.org, and unzip it. For example:

$ cd /Applications
$ unzip ~/apache-tomcat-6.0.18.zip

Next, download Web Beans from seamframework.org, and unzip it. For example

$ cd ~/
$ unzip ~/webbeans-$VERSION.zip

Next, we need to tell Web Beans where Tomcat is located. Edit jboss-as/build.properties and set the tomcat.home property. For example:

tomcat.home=/Applications/apache-tomcat-6.0.18

提示

The build scripts for the examples offer a number of targets for Tomcat, these are:

  • ant tomcat.restart - deploy the example in exploded format

  • ant tomcat.explode - update an exploded example, without restarting the deployment

  • ant tomcat.deploy - deploy the example in compressed jar format

  • ant tomcat.undeploy - remove the example from the server

  • ant tomcat.clean - clean the example

To deploy the numberguess example for tomcat:

$ cd examples/tomcat
ant tomcat.deploy

Start Tomcat:

$ /Applications/apache-tomcat-6.0.18/bin/startup.sh

提示

If you use Windows, use the startup.batscript.

請等待應用程式的建置,然後在 http://localhost:8080/webbeans-numberguess 花上幾個小時!

TODO

在 numberguess 應用程式中,您將會有 10 次機會來猜一個介於 1 至 100 之間的號碼。每當猜過一遍,系統便會告知您您所輸入的數字是否太大或太小。

numberguess 範例包含了 Web Bean 的一個數字、配置檔案,以及 Facelet JSF 頁面,並且封裝為 war。讓我們先從配置檔案開始。

此範例的所有配置檔案都位於 WEB-INF/ 中,並且它又儲存在來源樹中的 WebContent 裡。首先,我們有個可使用來指定 JSF 來使用 Facelet 的 faces-config.xml


<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
    
    <application>
        <view-handler
>com.sun.facelets.FaceletViewHandler</view-handler>
    </application>

</faces-config
>

有個空的 web-beans.xml 檔案,它會將此應用程式標記為一個 Web Bean 應用程式。

最後為 web.xml

Let's take a look at the Facelet view:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:s="http://jboss.com/products/seam/taglib">

  <ui:composit(1)ion template="template.xhtml">
    <ui:define name="content">
       <h1>Guess a number...</h1>
       <h:form id="NumberGuessMain">
          <div(2) style="color: red">
             <h:messages id="messages" globalOnly="false"/>
             <h:outputText id="Higher" value="Higher!" rendered="#{game.number gt game.guess and game.guess ne 0}"/>
             <h:outputText id="Lower" value="Lower!" rendered="#{game.number lt game.guess and game.guess ne 0}"/>
          </div>
   
          <div>
             I(3)'m thinking of a number between #{game.smallest} and #{game.biggest}.
             You have #{game.remainingGuesses} guesses.
          </div>
     
          <div>
             Your guess: 
             <(4)h:inputText id="inputGuess" 
                          value="#{game.guess}" 
                          required="true" 
                          size="3" 
                          disabled="#{game.number eq game.guess}">
              (5)  <f:validateLongRange maximum="#{game.biggest}" 
                                     minimum="#{game.smallest}"/>
             </h:inputText>
            <h(6):commandButton id="GuessButton"  
                             value="Guess" 
                             action="#{game.check}" 
                             disabled="#{game.number eq game.guess}"/>
          </div>
          <div>
            <h:commandButton id="RestartButton" value="Reset" action="#{game.reset}" immediate="true" />
          </div>
       </h:form>
    </ui:define>
  </ui:composition>
</html>
1

Facelets is a templating language for JSF, here we are wrapping our page in a template which defines the header.

2

There are a number of messages which can be sent to the user, "Higher!", "Lower!" and "Correct!"

3

As the user guesses, the range of numbers they can guess gets smaller - this sentance changes to make sure they know what range to guess in.

4

This input field is bound to a Web Bean, using the value expression.

5

A range validator is used to make sure the user doesn't accidentally input a number outside of the range in which they can guess - if the validator wasn't here, the user might use up a guess on an out of range number.

6

And, of course, there must be a way for the user to send their guess to the server. Here we bind to an action method on the Web Bean.

範例存有 4 個類別,前兩個為綁定類型。首先,有個使用來注入亂數號碼的 @Random 綁定類型:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface Random {}

還有個用來注入可注入之最大號碼的 @MaxNumber 綁定類型:

@Target( { TYPE, METHOD, PARAMETER, FIELD })

@Retention(RUNTIME)
@Documented
@BindingType
public @interface MaxNumber {}

Generator 類別負責透過產生器的方式來建立亂數號碼。它也會透過一個產生器的方式來顯示最大的可能號碼:

@ApplicationScoped

public class Generator {
   
   private java.util.Random random = new java.util.Random( System.currentTimeMillis() );
   
   private int maxNumber = 100;
   
   java.util.Random getRandom()
   {
      return random;
   }
   
   @Produces @Random int next() { 
      return getRandom().nextInt(maxNumber); 
   }
   
   @Produces @MaxNumber int getMaxNumber()
   {
      return maxNumber;
   }
}

您將會注意到 Generator 是屬於應用程式導向的;因此,我們不會每次都一定能得到不同的亂數號碼。

應用程式中最後的 Web Bean 為 session 導向的 Game

您將會注意到我們使用了 @Named 標記,如此一來我們便可在 JSF 頁面中的 EL 上使用這個 bean。最後,我們使用了 constructor injection 來利用亂數號碼來初始化了這個遊戲。當然,當玩家贏時我們將需要告知玩家,並藉由一個 FacesMessage 來給予回應。

package org.jboss.webbeans.examples.numberguess;



import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.webbeans.AnnotationLiteral;
import javax.webbeans.Current;
import javax.webbeans.Initializer;
import javax.webbeans.Named;
import javax.webbeans.SessionScoped;
import javax.webbeans.manager.Manager;
@Named
@SessionScoped
public class Game
{
   private int number;
   
   private int guess;
   private int smallest;
   private int biggest;
   private int remainingGuesses;
   
   @Current Manager manager;
   
   public Game()
   {
   }
   
   @Initializer
   Game(@MaxNumber int maxNumber)
   {      
      this.biggest = maxNumber;
   }
   public int getNumber()
   {
      return number;
   }
   
   public int getGuess()
   {
      return guess;
   }
   
   public void setGuess(int guess)
   {
      this.guess = guess;
   }
   
   public int getSmallest()
   {
      return smallest;
   }
   
   public int getBiggest()
   {
      return biggest;
   }
   
   public int getRemainingGuesses()
   {
      return remainingGuesses;
   }
   
   public String check()
   {
      if (guess
>number)
      {
         biggest = guess - 1;
      }
      if (guess<number)
      {
         smallest = guess + 1;
      }
      if (guess == number)
      {
         FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
      }
      remainingGuesses--;
      return null;
   }
   
   @PostConstruct
   public void reset()
   {
      this.smallest = 0;
      this.guess = 0;
      this.remainingGuesses = 10;
      this.number = manager.getInstanceByType(Integer.class, new AnnotationLiteral<Random
>(){});
   }
   
}

Whilst JSR-299 specifies integration with Java ServerFaces, Web Beans allows you to inject into Wicket components, and also allows you to use a conversation context with Wicket. In this section, we'll walk you through the Wicket version of the numberguess example.

Like the previous example, the Wicket WebBeans examples make use of the webbeans-servlet module. The use of the Jetty servlet container is common in the Wicket community, and is chosen here as the runtime container in order to facilitate comparison between the standard Wicket examples and these examples, and also to show how the webbeans-servlet integration is not dependent upon Tomcat as the servlet container.

These examples make use of the Eclipse IDE; instructions are also given to deploy the application from the command line.

JSF uses Unified EL expressions to bind view layer components in JSP or Facelet views to beans, Wicket defines it's components in Java. The markup is plain html with a one-to-one mapping between html elements and the view components. All view logic, including binding of components to models and controlling the response of view actions, is handled in Java. The integration of Web Beans with Wicket takes advantage of the same binding annotations used in your business layer to provide injection into your WebPage subclass (or into other custom wicket component subclasses).

The code in the wicket numberguess example is very similar to the JSF-based numberguess example. The business layer is identical!

Differences are:

This example can be found in the examples/se/numberguess folder of the Web Beans distribution.

To run this example:

There is an empty beans.xml file in the root package (src/main/resources/beans.xml), which marks this application as a Web Beans application.

The game's main logic is located in Game.java. Here is the code for that class, highlighting the changes made from the web application version:

(1)(2)@ApplicationScoped

public class Game implements Serializable
{
    private int number;
    private int guess;
    private int smallest;
    @MaxNumber
    private int maxNumber;
    private int biggest;
    private int remainingGuesses;
    private boolean validNumberRange = true;
    @Current Generator rndGenerator;
    ...
    public boolean isValidNumberRange()
    {
        return validNumberRange;
    }
    public boolean isGameWon()
(3)    {
        return guess == number;
    }
    public boolean isGameLost()
    {
        return guess != number && remainingGuesses <= 0;
    }
    public boolean check()
    {
        boolean result = false;
(4)        if ( checkNewNumberRangeIsValid() )
        {
            if ( guess > number )
            {
                biggest = guess - 1;
            }
            if ( guess < number )
            {
                smallest = guess + 1;
            }
            if ( guess == number )
            {
                result = true;
            }
            remainingGuesses--;
        }
        return result;
    }
    private boolean checkNewNumberRangeIsValid()
    {
        return validNumberRange = ( ( guess >= smallest ) && ( guess <= biggest ) );
    }
    @PostConstruct
(5)    public void reset()
    {
        this.smallest = 0;
        ...
        this.number = rndGenerator.next();
    }
}
1

The bean is application scoped instead of session scoped, since an instance of the application represents a single 'session'.

2

The bean is not named, since it doesn't need to be accessed via EL

3

There is no JSF FacesContext to add messages to. Instead the Game class provides additional information about the state of the current game including:

  • If the game has been won or lost

  • If the most recent guess was invalid

This allows the Swing UI to query the state of the game, which it does indirectly via a class called MessageGenerator, in order to determine the appropriate messages to display to the user during the game.

4

Validation of user input is performed during the check() method, since there is no dedicated validation phase

5

The reset() method makes a call to the injected rndGenerator in order to get the random number at the start of each game. It cannot use manager.getInstanceByType(Integer.class, new AnnotationLiteral<Random>(){}) as the JSF example does because there will not be any active contexts like there is during a JSF request.

The MessageGenerator class depends on the current instance of Game, and queries its state in order to determine the appropriate messages to provide as the prompt for the user's next guess and the response to the previous guess. The code for MessageGenerator is as follows:

public class MessageGenerator

{
(1)    @Current Game game;
(2)    public String getChallengeMessage()
    {
        StringBuilder challengeMsg = new StringBuilder( "I'm thinking of a number between " );
        challengeMsg.append( game.getSmallest() );
        challengeMsg.append( " and " );
        challengeMsg.append( game.getBiggest() );
        challengeMsg.append( ". Can you guess what it is?" );
        return challengeMsg.toString();
    }
(3)    public String getResultMessage()
    {
        if ( game.isGameWon() )
        {
            return "You guess it! The number was " + game.getNumber();
        } else if ( game.isGameLost() )
        {
            return "You are fail! The number was " + game.getNumber();
        } else if ( ! game.isValidNumberRange() )
        {
            return "Invalid number range!";
        } else if ( game.getRemainingGuesses() == Game.MAX_NUM_GUESSES )
        {
            return "What is your first guess?";
        } else
        {
            String direction = null;
            if ( game.getGuess() < game.getNumber() )
            {
                direction = "Higher";
            } else
            {
                direction = "Lower";
            }
            return direction + "! You have " + game.getRemainingGuesses() + " guesses left.";
        }
    }
}
1

The instance of Game for the application is injected here.

2

The Game's state is interrogated to determine the appropriate challenge message.

3

And again to determine whether to congratulate, console or encourage the user to continue.

Finally we come to the NumberGuessFrame class which provides the Swing front end to our guessing game.

public class NumberGuessFrame  extends javax.swing.JFrame

{
(1)    private @Current Game game;
(2)    private @Current MessageGenerator msgGenerator;
(3)    public void start( @Observes @Deployed Manager manager )
    {
        java.awt.EventQueue.invokeLater( new Runnable()
            {
                public void run()
                {
                    initComponents();
                    setVisible( true );
                }
            } );
    }
(4)    private void initComponents() {
        buttonPanel = new javax.swing.JPanel();
        mainMsgPanel = new javax.swing.JPanel();
        mainLabel = new javax.swing.JLabel();
        messageLabel = new javax.swing.JLabel();
        guessText = new javax.swing.JTextField();
        ...
        mainLabel.setText(msgGenerator.getChallengeMessage());
        mainMsgPanel.add(mainLabel);
        messageLabel.setText(msgGenerator.getResultMessage());
        mainMsgPanel.add(messageLabel);
        ...
    }
(5)    private void guessButtonActionPerformed( java.awt.event.ActionEvent evt )
    {
        int guess =  Integer.parseInt(guessText.getText());
        game.setGuess( guess );
        game.check();
        refreshUI();
    }
(6)    private void replayBtnActionPerformed( java.awt.event.ActionEvent evt )
    {
       game.reset();
       refreshUI();
    }
(7)    private void refreshUI()
    {
        mainLabel.setText( msgGenerator.getChallengeMessage() );
        messageLabel.setText( msgGenerator.getResultMessage() );
        guessText.setText( "" );
        guessesLeftBar.setValue( game.getRemainingGuesses() );
        guessText.requestFocus();
    }
    // swing components
    private javax.swing.JPanel borderPanel;
    ...
    private javax.swing.JButton replayBtn;
}
1

The injected instance of the game (logic and state).

2

The injected message generator for UI messages.

3

This application is started in the usual Web Beans SE way, by observing the @Deployed Manager event.

4

This method initialises all of the Swing components. Note the use of the msgGenerator.

5

guessButtonActionPerformed is called when the 'Guess' button is clicked, and it does the following:

  • Gets the guess entered by the user and sets it as the current guess in the Game

  • Calls game.check() to validate and perform one 'turn' of the game

  • Calls refreshUI. If there were validation errors with the input, this will have been captured during game.check() and as such will be reflected in the messeges returned by MessageGenerator and subsequently presented to the user. If there are no validation errors then the user will be told to guess again (higher or lower) or that the game has ended either in a win (correct guess) or a loss (ran out of guesses).

6

replayBtnActionPerformed simply calls game.reset() to start a new game and refreshes the messages in the UI.

7

refreshUI uses the MessageGenerator to update the messages to the user based on the current state of the Game.

轉譯器範例能接受您所輸入的任何句子,然後將它們翻譯成拉丁文。

轉換器範例被建置為一個 ear 並包含著 EJB。正因如此,它的結構比 numberguess 範例要複雜得多。

首先,讓我們先來看一下 ear 聚合器,它位於 webbeans-translator-ear 模組中。Maven 會自動地為我們產生 application.xml


<plugin>
   <groupId
>org.apache.maven.plugins</groupId>
   <artifactId
>maven-ear-plugin</artifactId>
   <configuration>
      <modules>
         <webModule>
            <groupId
>org.jboss.webbeans.examples.translator</groupId>
            <artifactId
>webbeans-translator-war</artifactId>
            <contextRoot
>/webbeans-translator</contextRoot>
         </webModule>
      </modules>
   </configuration>
</plugin
>

在此我們將設置 context 路徑,它能提供給我們一個網址(http://localhost:8080/webbeans-translator)。

提示

若您不使用 Maven 來產生這些檔案,那麼您將需要 META-INF/application.xml


<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd"
             version="5">
  <display-name
>webbeans-translator-ear</display-name>
  <description
>Ear Example for the reference implementation of JSR 299: Web Beans</description>
  
  <module>
    <web>
      <web-uri
>webbeans-translator.war</web-uri>
      <context-root
>/webbeans-translator</context-root>
    </web>
  </module>
  <module>
    <ejb
>webbeans-translator.jar</ejb>
  </module>
</application
>

Next, lets look at the war. Just as in the numberguess example, we have a faces-config.xml (to enable Facelets) and a web.xml (to enable JSF) in WebContent/WEB-INF.

還有更有趣的就是使用來轉換文字的 facelet。就和 numberguess 範例中一樣,我們有個圍繞著 form 的頁面格式(在此將省略不提):


<h:form id="NumberGuessMain">
            
   <table>
      <tr align="center" style="font-weight: bold" >
         <td>
            Your text
         </td>
         <td>
            Translation
         </td>
      </tr>
      <tr>
         <td>
            <h:inputTextarea id="text" value="#{translator.text}" required="true" rows="5" cols="80" />
         </td>
         <td>
            <h:outputText value="#{translator.translatedText}" />
         </td>
      </tr>
   </table>
   <div>
      <h:commandButton id="button" value="Translate" action="#{translator.translate}"/>
   </div>
   
</h:form
>

用戶可在左手邊的文字區域中輸入一些文字,然後點選轉譯按鈕並於右手邊的區域中查看結果。

最後,讓我們來看一下 ejb 模組 webbeans-translator-ejb。在 src/main/resources/META-INF 中只有一個用來將 archive 標記為包含著 Web Bean 的空 web-beans.xml

我們將最有趣的部份保留到了最後,那就是程式碼!該專案含有兩個基本的 bean,SentenceParserTextTranslator,以及兩個企業級的 bean,TranslatorControllerBeanSentenceTranslator。到了現在您應該已經很熟悉 Web Bean 長得如何了,因此我們在此將只著重於其它較有趣的部份。

SentenceParserTextTranslator 兩者皆為相依性的 bean,並且 TextTranslator 使用了 constructor 初始化:

public class TextTranslator { 

   private SentenceParser sentenceParser; 
   private Translator sentenceTranslator; 
   
   @Initializer
   TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) 
   { 
      this.sentenceParser = sentenceParser; 
      this.sentenceTranslator = sentenceTranslator;

TextTranslator 是個無狀態的 bean(以及一個本地的商業介面)- 當然,我們無法開發一個完整的轉譯器。

最後,有個 UI 導向的控制器,它會藉由用戶收集文字然後將它發送給轉譯器。這是個請求導向、被命名,而有狀態的 session bean,並且會注入轉譯器。

@Stateful

@RequestScoped
@Named("translator")
public class TranslatorControllerBean implements TranslatorController
{
   
   @Current TextTranslator translator;

這個 bean 針對於頁面上所有欄位都有 getter 與 setter。

因為這是個 stateful(有狀態)的 session bean,因此我們必須要有個 remove method:

   @Remove

   public void remove()
   {
      
   }

當 bean 被毀掉後,Web Bean 管理員會為您調用 remove 這個 method;在此情況下為請求結束之後。

That concludes our short tour of the Web Beans examples. For more on Web Beans , or to help out, please visit http://www.seamframework.org/WebBeans/Development.

我們在所有層面都需要協助 - 錯誤修正、編寫新功能、編寫範例,以及翻譯此參照指南。