SeamFramework.orgCommunity Documentation

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

3.1. 使用JBoss AS 5
3.2. 使用Apache Tomcat 6.0
3.3. 使用Glassfish
3.4. 猜数字例子
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. 翻译器例子

Web Bean参考实现由Seam项目开发。你可以从the downloads page下载最新的开发者版本。

Web Beans RI自带了两个例子:webbeans-numberguess (一个仅包含一个简单Bean的WAR应用例子)和webbeans-translator (一个包含企业Bean的EAR应用例子)。为了运行例子,你需要:

当前,Web Beans参考实现只能运行在JBoss AS 5之上。你需要从jboss.org下载JBoss AS 5.0.0.GA, 然后解压。例如:

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

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

$ cd ~/
$ unzip ~/webbeans-1.0.0.ALPHA1.zip

然后,我们需要告诉Web Beans JBoss的位置。编辑jboss-as/build.properties,设置jboss.home属性。例如:

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

为了安装更新,你需要安装Ant 1.7.0,设置ANT_HOME环境变量。例如:

注意

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

然后,你需要安装更新,更新脚本使用Maven来自动下载Web Beans和EJB3。

$ cd webbeans-1.0.0.ALPHA1/jboss-as
$ ant update

现在,你可以部署你的第一个例子了!

提示

例子的构建脚本包含多个目标:

  • ant restart - 以exploded形式部署例子

  • ant explode - 无需重新部署,更新一个exploded形式部署的例子

  • ant deploy - 以压缩jar包形式部署例子

  • ant undeploy - 将例子从服务器中移除

  • ant clean - 清除例子

部署猜数字(numberguess)例子:

$ cd examples/numberguess
ant deploy

启动 JBoss AS:

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

提示

如果你使用Windows操作系统,则使用run.bat 脚本。

等待应用部署完毕,好好体验一下http://localhost:8080/webbeans-numberguess

Web Bean参考实现的第二个简单例子能够将你的文本翻译为拉丁文。猜数字例子是一个WAR应用,仅仅使用了一个简单Beans;翻译器例子是一个EAR应用,包含了打包在EJB模块中的企业Beans。试一下:

$ cd examples/traslator
ant deploy

等待应用部署,试一下http://localhost:8080/webbeans-translator

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

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

然后从seamframework.org下载Web Beans的参考实现,然后解压。例如:

$ cd ~/
$ unzip ~/webbeans-1.0.0.ALPHA1.zip

然后,我们需要告诉Web Beans JBoss的位置。编辑jboss-as/build.properties,设置jboss.home属性。例如:

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

提示

例子的构建脚本包含多个目标:

  • ant restart - 以exploded形式部署例子

  • ant explode - 无需重新部署,更新一个exploded形式部署的例子

  • ant deploy - 以压缩jar包形式部署例子

  • ant undeploy - 将例子从服务器中移除

  • ant clean - 清除例子

部署猜数字(numberguess)例子:

$ cd examples/traslator
ant deploy

启动Tomcat:

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

提示

如果你使用Windows操作系统,则使用run.bat 脚本。

等待应用部署完毕,好好体验一下http://localhost:8080/webbeans-numberguess

待办

在猜数字应用中,你有十次机会来猜一个1到100之间的数字。每次猜测之后,应用都会告诉你你猜的数字是高了还是低了。

猜数字应用由Web Beans,配置文件,Facelete JSF页面组成,打包为一个WAR。我们先看一下配置文件。

猜数字应用的所有的配置文件位于WEB-INF/,这个目录位于源码树的WebContent中。首先,我们在faces-config.xml文件中告诉JSF使用Faceletes:


<?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 Beans应用。

最后,这有一个 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(2) id="NumberGuessMain">
          <div 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(3)>
             I'm thinking of a number between #{game.smallest} and #{game.biggest}.
             You have #{game.remainingGuesses} guesses.
          </div>
     
          <div>
             Y(4)our guess: 
             <h:inputText id="inputGuess" 
                          value="#{game.guess}" 
                          required="true" 
                          size="3" 
              (5)            disabled="#{game.number eq game.guess}">
                <f:validateLongRange maximum="#{game.biggest}" 
                                     minimum="#{game.smallest}"/>
             <(6)/h:inputText>
            <h: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类通过一个生产者(producer)方法创建一个随机数。它也通过一个生产者方法暴露可能的最大值:

@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是会话范围的 Game

你也许注意到我们使用了 @Named注释,以便我们能够通过EL(表达式语言)在JSF页面中使用Bean。最后,我们通过构造器注入来初始化猜数字游戏并给它设一个随机数。当然,在玩家猜对数字后,我们需要告诉玩家他赢了,所以我们通过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 ) );
    } (5)
    @PostConstruct
    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应用,包含EJBs和企业Beans。因此,它的结构比猜数字例子复杂。

首先,让我们看一下EAR聚合器,它位于webbeans-translator-ear模块下。Maven将为我们自动生成application.xmljboss-app.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>
      <jboss>
         <loader-repository
>webbeans.jboss.org:loader=webbeans-translator</loader-repository>
      </jboss>
   </configuration>
</plugin
>

我们需要在这里做些事情-首先我们需要设置上下文路径为一个不错的URL(http://localhost:8080/webbeans-translator),我们还需要将JBoss AS的类加载器隔离配置激活。

提示

如果你不使用Maven来生成这些文件,你将需要META-INF/jboss-app.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
>

然后,我们看一下WAR包。在猜数字例子中,我们需要faces-config.xml(配置Facelets)和一个位于WebContent/WEB-INF下的web.xml(配置JSF并将Web Beans服务加入Servlet容器中)。

更有意思的是用来翻译文本的facelet。在猜数字应用中我们有一个模板,这个模板套着表单(省略了表单):


<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下有两个配置文件,一个是空的web-beans.xml,用来标识这个档案包包含Web Beans,一个是ejb-jar.xml。Web Beans为所有的EJB提供注入和初始化服务,使用ejb-jar.xml文件来配置。你将在使用Web Beans的EJB项目中需要这些配置:

我们将最有意思的部分放在最后,那就是代码!这个例子有两个简单Beans, SentanceParserTextTranslator,还有两个企业Beans,TanslatorControllerBeanSentenceTranslator。现在你应该对Web Beans有点熟悉了,我们在这里着重最有意思的部分。

SentanceParserTextTranslator是相互依赖的Beans,TextTranslator使用构造器初始化:

public class TextTranslator { 

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

TextTranslator是一个无状态Bean(拥有一个本地业务接口),这里是魔术展现的地方-当然,我们不会开发一个完整的翻译器,但我们可以开发一个不错的小玩意!

最后,这里又要一个面向UI的控制器,从用户输入处搜集文本,转发给翻译器。这个控制器是请求范围的,具名的,有状态的会话Bean,它可以将翻译器注入进来。

@Stateful

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

这个Bean也拥有页面上所有域的getter和setter方法。

这个Bean是有状态会话Bean,我们需要有一个remove方法:

   @Remove

   public void remove()
   {
      
   }

Web Beans管理器在这个bean销毁的时候调用remove方法;在这个例子中是请求结束的时候。

Web Beans参考实现的例子到此结束。想要获得关于参考实现更多的知识或者帮助,请访问http://www.seamframework.org/WebBeans/Development

我们在各个方面都需要帮助-bug修复,新特性开发,例子开发和参考指南的翻译等等。