EJB2+Servlet+JSPなEARをIDEなしで作る

諸事情によりIDEなしでEJB2+Servlet+JSPなEnterprise Applicationを構築したりする必要があったのでメモ。

想定するアプリケーション構造

Web Browser <---> JSP <---> Servlet <---> EJB(Stateless Session Bean) <---> DAO(POJO) <---> DB
Web Browser
クライアントアプリ。
JSP
入力と出力用画面。Sendボタン押下でto入力欄の内容をServletに送信、結果をResult欄に表示する。
Servlet
EJBを生成し、JSPからの入力を渡してリモートメソッドの実行、結果を受け取りJSPへ返す。
EJB
入力文字列を加工して結果を返却する。

DB用意するのが面倒なのでDAO以降は省略。EJB使っているわけではなく単なるPOJOなので調べる必要なし。

アプリケーションサーバにはWebLogic 9.2MP3を利用。ちょい古めのバージョンだが諸事情のため。

EARの構造

EAR(Enterprise Archive)ファイルは下記のようなディレクトリ構造を持つZIPファイル*1。jarコマンドで作成する。application.xmlファイルが含まれるMETA-INFディレクトリと複数のWARファイルおよび複数のEJB-JAR(EJBを使っている場合)ファイルで構成される。

/
  META-INF/
    application.xml
    weblogic-application.xml
  hello.war
  hello.jar

application.xmlファイルにはEARファイルに含まれるWARファイルとEJB-JAR ファイルの名前を定義する感じ。ここでWARファイルに対してコンテキストルートを定義すると、WARファイル内のweb.xmlで定義されたコンテキストルートは上書きされる*2

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.2//EN' 'http://java.sun.com/j2ee/dtds/application_1_2.dtd'>
<application>
  <description>Hello Enterprise Application</description>
  <module>
    <ejb>hello.jar</ejb>
  </module>
  <module>
    <web>
      <web-uri>hello.war</web-uri>
      <context-root>hello</context-root>
    </web>
  </module>
</application>

weblogic-application.xmlファイルはWebLogic用のapplication.xmlファイル。最小構成においては特に書くことない。一応用意する。

<?xml version="1.0" encoding="ISO-8859-1"?>
<wls:weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wls="http://www.bea.com/ns/weblogic/90" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-application.xsd">
</wls:weblogic-application>

EJB-JARの構造

EJB-JAR(EJB Java Archive)は下記のようなディレクトリ構造を持つZIPファイル。jarコマンドで作成する。ejb-jar.xmlファイルが含まれるMETA-INFディレクトリとEJBのclassファイル(Homeインタフェース、Remoteインタフェース、Beanクラス)で構成される。

/
  META-INF/
    ejb-jar.xml
    weblogic-ejb-jar.xml
  net/
    jibunstyle/
      hello/
        ejb/
          Hello.class
          HelloBean.class
          HelloHome.class

ejb-jar.xmlファイルには作成したEJB関連クラスのFQCNとBeanの種類などを定義する。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar>
  <enterprise-beans>
    <session>
      <ejb-name>Hello</ejb-name>
      <home>net.jibunstyle.hello.ejb.HelloHome</home>
      <remote>net.jibunstyle.hello.ejb.Hello</remote>
      <ejb-class>net.jibunstyle.hello.ejb.HelloBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Bean</transaction-type>
    </session>
  </enterprise-beans>
</ejb-jar>

weblogic-ejb-jar.xmlWebLogic用のejb-jar.xmlファイル。EJB名とそのJNDI名を定義しておく。weblogic-application.xmlweblogic.xmlでは何も記載いらなかったのでこれも同様である気がするが調べるのが面倒なのでとりあえず。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN' 'http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd'>
<weblogic-ejb-jar>
  <weblogic-enterprise-bean>
    <ejb-name>Hello</ejb-name>
    <jndi-name>Hello</jndi-name>
  </weblogic-enterprise-bean>
</weblogic-ejb-jar>

以下、EJB関連のクラス。

package net.jibunstyle.hello.ejb;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

/**
 * Remoteインタフェース
 */
public interface Hello extends EJBObject { 
  public String say(String to) throws RemoteException; 
}
package net.jibunstyle.hello.ejb;

import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

/**
 * Beanクラス
 */
public class HelloBean implements SessionBean { 
  public String say(String to) { 
    return "Hello, " + to;
  }

  public void setSessionContext(SessionContext sc) {}
  public void ejbCreate() {}
  public void ejbRemove() {} 
  public void ejbActivate() {} 
  public void ejbPassivate() {} 
}
package net.jibunstyle.hello.ejb;

import java.rmi.RemoteException; 
import javax.ejb.CreateException; 
import javax.ejb.EJBHome;

/**
  * Homeインタフェース
  */
public interface HelloHome extends EJBHome { 
  public Hello create() throws RemoteException, CreateException;
}

WARの構造

WAR(Web Application Archive)は下記のようなディレクトリ構造を持つZIPファイル。web.xmlファイルやlibディレクトリ、classesディレクトリが含まれるWEB-INFディレクトリ、JSPやHTMLなどのコンテンツファイルで構成される。

/
  WEB-INF/
    web.xml
    weblogic.xml
    lib/
    classes/
      net/
        jibunstyle/
          hello/
            servlet/
              HelloServlet.class
  hello.jsp

web.xmlファイルにはWARに含まれるServletの定義と、そのServletにアクセスするためのURLマッピング情報などを記載する。

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>net.jibunstyle.hello.servlet.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>

weblogic.xmlファイルはWebLogic用のweb.xmlファイル。特に何も記載しなくてもよい。

<?xml version="1.0" encoding="ISO-8859-1"?>
<weblogic-web-app>
</weblogic-web-app>

libディレクトリには参照する共通ライブラリを、classesディレクトリにはServletのclassファイルを格納する。以下、ServletJSPのソース。

package net.jibunstyle.hello.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import net.jibunstyle.hello.ejb.*;

public class HelloServlet extends HttpServlet {
  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Properties prop = new Properties();
    String msg = "";
    prop.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
    prop.put(Context.PROVIDER_URL, "t3://localhost:8001");
    Context ctx = null;
    try {
      ctx = new InitialContext(prop);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    HelloHome home = null;
    Hello hello = null;
    try {
      home = (HelloHome)ctx.lookup("Hello");
    } catch (Exception ex) {
      ex.printStackTrace();
    }

    try {
      hello = home.create();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    
    String to = request.getParameter("to");
    
    try {
      msg = hello.say(to);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    
    request.setAttribute("to", to);
    request.setAttribute("result", msg);
    
    request.getRequestDispatcher("hello.jsp").forward(request, response);
  } 
}
<html>
  <head>
    <title>Hello Enterprise Application</title>
  </head>
  <body>
    <h1>Hello Enterprise Application</h1>
    <form action="/hello/hello" method="POST">
      <div>Say hello to ... <input type="text" name="to" value="<%= request.getAttribute("to") %>"></div>
      <div><input type="submit" value="Say">
      <div>Result : <%= request.getAttribute("result") %></div>
    </form>
  </body>
</html>

EARのデプロイ(WebLogic)

できあがったEARファイルをWebLogicのautodeployフォルダにドロップすれば簡単にデプロイされる。あらかじめConfiguration Wizardで適当に作成した新しいドメインmydomainにデプロイする。autodeployディレクトリの場所は以下。

C:\bea\user_projects\domains\mydomain\autodeploy

動作確認

Webブラウザを起動して、 http://localhost:8001/hello/hello.jsp にアクセスする。なお、ポートはmydomain作成時に8001を指定した。

WebLogicに関する注意

  • application.xmlなどのXMLファイルにUTF-8を使うと、「1文字目が思ってたのと違う」的なエラーがでて面倒。BOM付きUTF-8にすれば解決しそうなエラーではあるが面倒なのですべてISO-8859-1で統一した。
  • 管理コンソールからデプロイしようとすると「EARにアクセスできん」的なエラーが出てうまくいかない。ファック。autodeployフォルダを使った。

まとめ

今までIDEにやらせていて詳しく理解できていなかった部分がクリアになった気がした。

おまけ

作業ディレクトリ構成とバッチファイル。なお、JRockitのbinディレクトリにパスが通っている前提で書かれている。

myear
│  hello.ear
│  
├─hello.ear.folder
│  │  hello.jar
│  │  hello.war
│  │  mkear.bat
│  │  
│  ├─hello.jar.folder
│  │  │  mkjar.bat
│  │  │  
│  │  ├─META-INF
│  │  │      ejb-jar.xml
│  │  │      weblogic-ejb-jar.xml
│  │  │      
│  │  └─net
│  │      └─jibunstyle
│  │          └─hello
│  │              └─ejb
│  │                      Hello.class
│  │                      HelloBean.class
│  │                      HelloHome.class
│  │                      
│  ├─hello.war.folder
│  │  │  hello.jsp
│  │  │  mkwar.bat
│  │  │  
│  │  └─WEB-INF
│  │      │  web.xml
│  │      │  weblogic.xml
│  │      │  
│  │      ├─classes
│  │      │  └─net
│  │      │      └─jibunstyle
│  │      │          └─hello
│  │      │              └─servlet
│  │      │                      HelloServlet.class
│  │      │                      
│  │      └─lib
│  └─META-INF
│          application.xml
│          weblogic-application.xml
│          
└─src
    │  buildejb.bat
    │  buildservlet.bat
    │  
    └─net
        └─jibunstyle
            └─hello
                ├─ejb
                │      Hello.java
                │      HelloBean.java
                │      HelloHome.java
                │      
                └─servlet
                        HelloServlet.java
EJBビルド用(buildejb.bat)
@ECHO OFF
SET CLASSPATH=C:\bea\weblogic92\server\lib\weblogic.jar
SET DEST=..\hello.ear.folder\hello.jar.folder
javac -d %DEST% net\jibunstyle\hello\ejb\*.java
サーブレットビルド用(buildservlet.bat)
@ECHO OFF
SET CLASSPATH=C:\bea\weblogic92\server\lib\weblogic.jar;..\hello.ear.folder\hello.jar
SET DEST=..\hello.ear.folder\hello.war.folder\WEB-INF\classes\
javac -d %DEST% net\jibunstyle\hello\servlet\*.java
hello.jar作成用(mkjar.bat)
jar -cf ..\hello.jar *
hello.war作成用(mkwar.bat)
jar -cf ..\hello.war *
hello.ear作成用(mkear.bat)
jar -cf ..\hello.ear *.jar *.war META-INF

*1:厳密に言えば微妙に違うのだろうがまあこの程度の理解でよしとする

*2:複数のWARを一つのEARにまとめたときにコンテキストルートがかぶったりして困る事態を回避するために設定できるのだろう。たぶん。調べてないけど