[Java] Spring to Java EE Migration, Part 4

原文はこちら。
http://www.oracle.com/technetwork/articles/java/springtojavaee4-1592643.html

はじめに
これはSpring FrameworkからJava EEへの移行方法を説明している投稿の第4部、最終回です。
第1部第2部では、Spring FrameworkにバンドルされているPet Clinicというサンプルアプリケーションを、SpringではなくJava EE(JavaServer Faces [JSF]、JSF Facelets、Enterprise JavaBeans [EJB] 3.1、Java Persistence API [JPA]))で書きました。途中、NetBeansに付属の強力なツールを使うと、記録的な速さで完全なJava EEアプリケーションを開発することができることを示しました。その後、生成されたコードを分析し、ウィザードが利用するいくつかのすばらしいJava EE機能(Faceletsテンプレート機能、JSFデータモデル、カスタムJSFコンバータ、最大文字列長やNULL/非NULL値などの情報を保持しつつデータベーススキーマを完全に再作成できるJPAアノテーション属性、その他)に注目しました。
第3部では、生成されたJava EEアプリケーションを調整してよりユーザーフレンドリーにしました。いくつかのラベルを理解しやすい名前に変更し、ユーザーインターフェースに値を表示している代替キーをユーザにとって意味のある値に変更しました。さらに、Spring版とJava EE版のアプリケーション間で依存性の個数、設定ファイルの行数、コードやマークアップの行数を比較しました。この一連の記事で開発したJava EE版のアプリケーションは、Spring版を直接移植したものではなく、実はSpring Frameworkにバンドルされているものよりも多くの機能を持っていることは覚えておきましょう。
この第4部では、Java EEとSpringで同等の機能を比較します。ここではMVCデザインパターン実装やデータアクセス、トランザクション管理、依存性の注入のトピックを取り上げます。

Model-View-Controllerの実装
Model-View-Controller (MVC) デザインパターンはデファクトスタンダードのエンタープライズアプリケーションの設計方法であり、Java EEやSpringを問いません。どちらのフレームワークもアプリケーション開発者がこのデザインパターンを使ってアプリケーションを開発する際に役立つ方法を提供しています。
MVCパターンのモデルは、通常アプリケーション層の間で受け渡されるデータオブジェクトです。

SpringにおけるMVC
SpringでのModelは、Spring固有のmodelインターフェースの実装にaddAttribute()メソッドで追加されるPlain Old Java Object (POJO)です。これは、Spring版アプリケーションのAddOwnerForm.javaのsetupForm()メソッドで確認できます。
@RequestMapping(method = RequestMethod.GET)
  public String setupForm(Model model) {
      Owner owner = new Owner();
      model.addAttribute(owner);
      return "ownerForm";
  }
この設定により、Modelクラスと属性にJSPのEL(Expression Language)を通じてViewからアクセスできます。
<form:form modelAttribute="owner">
  <table>
    <tr>
      <th>
        First Name: <form:errors path="firstName" cssClass="errors"/>
        <br/>
        <form:input path="firstName" size="30" maxlength="80"/>
      </th>
    </tr>
.
.
.
</form>
Springを利用する場合、Springの<form:form>タグのmodelAttribute属性の値として、Model属性名を指定し、Springのフォームタグライブラリが生成する種々の入力フィールドのpath属性の値として対応するプロパティ名を使う必要があります。
Java EEにおけるMVC
Java EEとJSFを使用する場合、やるべきことはクラスに@ManagedBeanアノテーションを付けることで、その後、当該クラスとプロパティをUnified ELを通じてViewから利用できるようになります。Pet ClinicアプリケーションのJSF版では、JPAエンティティをモデルとして使用します。直接アクセスするのではなく、各Controllerには、対応するJPAエンティティを返すgetSekected()メソッドがあります。例えば、OwnerController.javaにはOwnerインスタンスを返すgetSelected()メソッドがあります。
public Owner getSelected() {
  if (current == null) {
    current = new Owner();
    selectedItemIndex = -1;
  }
  return current;
}
Unified ELでgetSelected()メソッドを呼び出して、ViewからJPAエンティティOwnerにアクセスすることができます。
<h:inputText id="firstName" value="#{ownerController.selected.firstName}"
           title="#{bundle.CreateOwnerTitle_firstName}" />
View
MVCデザインパターンにおけるViewはユーザーインターフェースを利用者に表示する役目があります。
SpringにおけるView
Spring MVCで使われるViewテクノロジーは通常、JavaServer Pages Standard Tag Library(JSTL)を使ったJSPです。JSFの場合、Faceletsが推奨されるViewテクノロジーです。JSPは枯れたテクノロジーですが、FaceletsはDeveloper Experienceを提供します。初心者にとって、Faceletsページはカスタム名前空間を持つ標準のXHTMLページで、さらにJSFはよく使われる機能のコンポーネントを提供しています。
Spring版アプリケーションの飼い主のリスト表示のコードを見ていきましょう。

<table>
  <tr>
  <thead>
    <th>Name</th>
    <th>Address</th>
    <th>City</th>
    <th>Telephone</th>
    <th>Pets</th>
  </thead>
  </tr>
  <c:forEach var="owner" items="${selections}">
    <tr>
      <td>
          <a href="owner.do?ownerId=${owner.id}">
              ${owner.firstName} ${owner.lastName}</a>
      </td>
      <td>${owner.address}</td>
      <td>${owner.city}</td>
      <td>${owner.telephone}</td>
      <td>
        <c:forEach var="pet" items="${owner.pets}">
          ${pet.name}
        </c:forEach>
      </td>
    </tr>
  </c:forEach>
</table>
おわかりのように、SpringではHTMLの表にネストしたJSTLタグを組み合わせてリストを生成する必要があります。では、Java EE版で同等のページを見てみましょう。
Java EEにおけるView
Java EEはFaceletsを使ったJSFをデフォルトのViewテクノロジーとして利用します。以下のコードはこのテクノロジーを使って動的なデータを表示する表の実装方法を示したものです。

<h:dataTable value="#{ownerController.items}" var="item"
             border="0" cellpadding="2" cellspacing="0"
             rowClasses="jsfcrud_odd_row,jsfcrud_even_row"
             rules="all" style="border:solid 1px">
    <h:column>
        <f:facet name="header">
            <h:outputText
                value="#{bundle.ListOwnerTitle_firstName}"/>
        </f:facet>
        <h:outputText value="#{item.firstName}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText
                value="#{bundle.ListOwnerTitle_lastName}"/>
        </f:facet>
        <h:outputText value="#{item.lastName}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText
                value="#{bundle.ListOwnerTitle_address}"/>
        </f:facet>
        <h:outputText value="#{item.address}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText
                value="#{bundle.ListOwnerTitle_city}"/>
        </f:facet>
        <h:outputText value="#{item.city}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText
                value="#{bundle.ListOwnerTitle_telephone}"/>
        </f:facet>
        <h:outputText value="#{item.telephone}"/>
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText value=" "/>
        </f:facet>
        <h:commandLink
            action="#{ownerController.prepareView}"
            value="#{bundle.ListOwnerViewLink}"/>
        <h:outputText value=" "/>
        <h:commandLink
            action="#{ownerController.prepareEdit}"
            value="#{bundle.ListOwnerEditLink}"/>
        <h:outputText value=" "/>
        <h:commandLink
            action="#{ownerController.destroy}"
            value="#{bundle.ListOwnerDestroyLink}"/>
    </h:column>
</h:dataTable>
Faceletsを使っているJava EE版では、JSFのdataTableコンポーネントを利用し、表形式のデータを動的に組み立てています。JSFとFaceletsを使ったWebアプリケーションの開発ではHTMLタグとカスタムのテクノロジータグを混在させたり調和させないことが一般的で、上述のような動的データから表を生成するような機能が多く既に用意され提供されています。

Controller
MVCにおけるControllerはユーザの入力を処理しView間を遷移することを役目としています。

SpringにおけるController
Spring(2.5以後)では、Controllerには@Controllerアノテーションがついています。このアノテーションが機能するよう、以下の行をSpringのMVC設定ファイルに追加する必要があります。
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>
Springコンテナがbase-package属性で指定されたパッケージ内のSpringコンポーネントを調べるためにこの行が必要です。このやり方は以前のバージョンのSpringよりも確かに簡単ではありますし、ControllerをSpringのXML設定ファイルで設定する必要はないのですが、まだ上記の一文を設定ファイルに追加しなければ、コンポーネントのスキャンがうまくいかないことは覚えておくべきでしょう。
Spring 2.5以後では、通常Contorollerのメソッドに@RequestMappingアノテーションを付けます。メソッドの一つがHTTP GETリクエストを扱い、別のメソッドがHTTP POSTリクエストを処理します。@RequestMappingアノテーションのmethod属性の値でどのタイプのHTTPリクエストをアノテーションを付けたメソッドで処理するかを指定します。通常、HTTP GET リクエストを取り扱うメソッドはユーザが入力するフォームを表示する役目があり、HTTP POSTリクエストを扱うメソッドはユーザが入力したデータを処理する役目があります。どちらのメソッドも処理終了後リクエストをディスパッチする方法を指定する文字列を返さなければなりません。
ランディングページを表示する必要がある場合、適切なJSPを解決するためには、SpringのXML設定ファイルに以下のように設定をする必要があります。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
    p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
異なるControllerにリクエストを送信する必要がある場合、受信したControllerは@RequestMappingアノテーションがついているメソッドでなければならず、value属性に処理したいURLを設定する必要があります。
Spring版のPet Clinicでは、ClinicController.javaに以下のようにアノテーションがついたownerHandler()メソッドがあります。
@RequestMapping("/owner.do")
public ModelMap ownerHandler(@RequestParam("ownerId") int ownerId) {
  return new ModelMap(this.clinic.loadOwner(ownerId));
}
@RequestMappingアノテーションの値が "/owner.do" なので、処理が終了するとprocessSubmit()にcontrolを送信します。

Java EE/JSFにおけるController
JSFを使ってMVC Controllerを実装するためには、ControllerクラスにJSFの@ManagedBeanアノテーションを付ける必要があります。Controllerとしてクラスにマークを付ける必要はありませんし、XML設定ファイルも不要です。
JSFでHTTP POSTリクエストを処理するメソッドは引数をもたず、返値として文字列をとらなければなりません。例えば、OwnerController.javaの以下のメソッドは既存の飼い主のレコードを更新します。
public String update() {
    try {
        getFacade().edit(current);
        JsfUtil.addSuccessMessage(ResourceBundle.getBundle("/Bundle").
                getString("OwnerUpdated"));
        return "View";
    } catch (Exception e) {
        JsfUtil.addErrorMessage(e, ResourceBundle.getBundle("/Bundle").
                getString("PersistenceErrorOccured"));
        return null;
    }
}
メソッドにアノテーションを付ける必要がないことに注目して下さい。
JSFコマンドコンポーネントからControllerメソッドを呼び出すことができます。通常コマンドボタンからUnified ELをコンポーネントのaction属性の値として追加することで呼び出します。
<h:commandLink action="#{ownerController.update}"
     value="#{bundle.EditOwnerSaveLink}"/>
デフォルトでは、JSF Managed Beanの名前は小文字で始まるクラス名であることを忘れないで下さい。ドット(.) の後の文字は呼び出すメソッド名で、慣例でpublic、引数は無しで文字列型を返します。
上記メソッドに戻って、例外をキャッチしない場合、慣例に従い"View"という文字列を返し、JSFはView.htmlというファイルを探し、メソッドの処理が終了すると前記のページに移動します。再度申し上げますが、どこにもこの設定をする必要はありません。JSFがやってくれます。
これがJSFが慣例に依存しほとんど構成が不要であることを示す一例です。

データアクセスとトランザクション管理
データベースのデータにアクセスする必要があるアプリケーションを開発するためには、データアクセスオブジェクト(DAO)デザインパターンを使うことが通例です。このパターンでは、データアクセスオブジェクト(DAO)があらゆるデータベースに対する操作を隠蔽します。

SpringにおけるDAO
Springでは、DAOには通常@Repositoryアノテーションを付け、当該クラスがDAOであるとマークします。O-Rマッピングツール(Hibernateなど)が変換してSpring固有のDataAccessExceptionに変換されます。
Spring版のPetClinicアプリケーションでは、HibernateClinic.javaがDAOであり、@Repositoryアノテーションがついています。
package org.springframework.samples.petclinic.hibernate;

/* imports omitted */

@Repository
@Transactional
public class HibernateClinic implements Clinic {

     @Autowired
     private SessionFactory sessionFactory;

     @Transactional(readOnly = true)
     @SuppressWarnings("unchecked")
     public Collection<Vet> getVets() {
          return sessionFactory.getCurrentSession().createQuery(
                "from Vet vet order by vet.lastName, vet.firstName").list();
     }

     @Transactional(readOnly = true)
     @SuppressWarnings("unchecked")
     public Collection<PetType> getPetTypes() {
          return sessionFactory.getCurrentSession().createQuery(
                "from PetType type order by type.name").list();
     }

     @Transactional(readOnly = true)
     @SuppressWarnings("unchecked")
     public Collection<Owner> findOwners(String lastName) {
          return sessionFactory.getCurrentSession().createQuery(
                "from Owner owner where owner.lastName like :lastName")
                     .setString("lastName", lastName + "%").list();
     }

     @Transactional(readOnly = true)
     public Owner loadOwner(int id) {
          return (Owner) sessionFactory.getCurrentSession().load(
            Owner.class, id);
     }

     @Transactional(readOnly = true)
     public Pet loadPet(int id) {
          return (Pet) sessionFactory.getCurrentSession().load(Pet.class, id);
     }

     public void storeOwner(Owner owner) {
          // Note: Hibernate3's merge operation does not reassociate the object
          // with the current Hibernate Session. Instead, it will always copy the
          // state over to a registered representation of the entity. In case of a
          // new entity, it will register a copy as well but will not update the
          // ID of the passed-in object. To still update the IDs of the original
          // objects too, we need to register Spring's
          // IdTransferringMergeEventListener on our SessionFactory.

          sessionFactory.getCurrentSession().merge(owner);
     }

     public void storePet(Pet pet) {
          sessionFactory.getCurrentSession().merge(pet);
     }

     public void storeVisit(Visit visit) {
          sessionFactory.getCurrentSession().merge(visit);
     }
}
DAOに@Repositoryアノテーションを付けた後、Springコンテナで<bean>タグを使って登録する必要があります。Pet Clinicの場合は以下のような感じです。
<bean id="clinic" class="org.springframework.samples.petclinic.hibernate.HibernateClinic"/>
もしくは先ほど説明したように、Springコンテナを設定し、自動的にコンポーネントをスキャンする必要があります。
データベースを扱う場合、通常メソッドがトランザクショナルである必要がありますので、データベース中で一貫性のないデータが残る心配はありません。Springではクラスもしくは個々のメソッドに@Transactionalアノテーションを付けることでこれを実現できます。
Java EE版に移動する前に、Springでは、HibernateTemplateのようなSpring固有のO-Rマッピングテンプレートを使ってデータアクセス処理をすることが非常に一般的なやり方であることに注目しましょう。これらのテンプレートはデータアクセスのコードをいくらか簡単にしてくれますが、アプリケーションがSpring APIに縛り付けられてしまいます。

Java EEにおけるDAO
Java EEの場合、通常はステートレスSesslon BeanをDAOとして用います。JavaのクラスをステートレスSession Beanにするためには、@Statelessアノテーションを付けるだけです。Java EE版のPet ClinicのDAOは、Facadeデザインパターンを実装しています、つまりJPAのEntityManagerインターフェースよりも簡単なインターフェースを提供します。
package com.ensode.petclinicjavaee.session;
//imports omitted

@Stateless
public class OwnerFacade extends AbstractFacade<Owner> {
    @PersistenceContext(unitName = "PetClinicJavaEEPU")
    private EntityManager em;

    protected EntityManager getEntityManager() {
        return em;
    }
    public OwnerFacade() {
        super(Owner.class);
    }
}
上記のDAO(Java EE版アプリケーションのその他のDAOも全て)はAbstractFacadeを拡張しており、これにはアプリケーションのJPAエンティティを取り扱うためのgenericメソッドが含まれています(第2部にAbstractFacadeのコードがあります)。着目すべきは、AbstractFacadeはSession Beanではなく、Session BeanがPOJOを拡張できることを説明するために提供している、ということです。Java EEにはSpringの@Transactionalアノテーションに相当するものはありません。それはEJBのメソッドは全てデフォルトでトランザクショナルだからです。
[注意]
訳あって非トランザクショナルなメソッドをEJBに実装する必要がある場合、@TransactionAttributeアノテーションを付け、属性値にNOT_SUPPORTEDを設定することができます。詳細はJava EE 6チュートリアルの43章をご覧下さい。
Java EE 6 Tutorial
http://download.oracle.com/javaee/6/tutorial/doc/
手短に言うと、ほとんどの場合、DAOを実装する際には@Statelessアノテーションをクラスに付加するだけでよく、これによりEJBコンテナが提供するトランザクション管理を「無料で」利用することができます。ほとんどの場合、追加のアノテーションやXML設定ファイルは不要です。

依存性の注入
依存性の注入(Dependency Injection)とは、クラスの依存性をコンテナが自動的に注入するというデザインパターンです。Spring、Java EEとも依存性の注入をサポートしています。

Springにおける依存性の注入
Springでは、依存性の注入は通常@Autowiredアノテーションを使って実施します。これはSpring版のアプリケーションにあるClinic DAOインターフェースのHibernate実装で使われていることが確認できます。
package org.springframework.samples.petclinic.hibernate;

//imports omitted

@Repository
@Transactional
public class HibernateClinic implements Clinic {
     @Autowired
     private SessionFactory sessionFactory;
      .
      .
      .

}
このアノテーションが機能するために、以下のタグをSpring XML設定ファイルに追加する必要があります。
<context:annotation-config />
Springコンテナが@Autowiredアノテーションを見つけると、適切な型のBeanを注釈を付けたプロパティに注入します。

Java EEの依存性の注入
JSFでは、Managed Beanを@ManagedPropertyで各々に注入することができます。例えば、2個のJSF Managed Bean(FooとBar)があり、それらがFoo型のプロパティを持っている場合、自動邸に依存性を以下のように注入することができます。
package com.ensode.foo;

//imports omitted

@ManagedBean
@RequestScoped
public class Bar {

    @ManagedProperty("#{foo}")
    private Foo foo;

    /** Creates a new instance of Bar */
    public Bar() {
    }

    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }
}
そのvalue属性の@ ManagedPropertyのデフォルト値は、注入するBeanを解決するUnified ELの式でなければなりません。
Java EEアプリケーションがコンテキストと依存性注入(CDI)を使用する場合は、依存性の注入はこれよりも簡単です。注入されたプロパティは、@Injectアノテーションをつかって注釈を付ける必要があり、Bean名を指定する必要はありません。

まとめと結論
ここまで、SpringのPetClinicアプリケーションのJava EE版を作成してきました。NetBeansの提供する優れたツールのおかげで、迅速にJava EEアプリケーションを開発できることがわかりました。その後、生成されたコードを見ていき、コードの理解と確認をしました。アプリケーションに少々修正を加えて使いものになるアプリケーションができたわけですが、手でコードを書く必要はほとんどありませんでした。
Java EE版のアプリケーションの開発が終了すると、Spring版のアプリケーションと比較しましたが、Spring版には種々の依存があったのに対し、Java EE版ではなかったことに着目しました。注入されるプロパティには@Injectアノテーションで注釈を付ける必要がありますが、bean名を指定する必要はありません。
最後に、MVCやDAO実装、トランザクション管理、Dependency Injectionのような機能の実装方法をSpringとJava EEで比較しました。どの場合でもSpringではコードへのアノテーションを付加するだけでなくXMLの設定ファイルが必要です。Java EEは規則に従っており、こうしたサービスを実装する上でほとんどの場合XMLの設定ファイルは不要です。
Springの新しいバージョンでは明示的なXML設定ファイルに従う必要がかなり減りましたが、Springのアノテーションが有効に効果を出すためにはやはり数行はXML設定ファイルに追加する必要があります。これはDRYの原則(Don't repeat yourself / 同じことを繰り返さない)に反します。ほとんどの場合Java EEでは設定ファイルは不要で、アノテーションだけで十分に機能を果たしてくれます。
さらに、Springアプリケーションはどうしても依存性をもつ傾向にありますが、それは”軽量な”Servletコンテナ(TomcatやJetty)で動かすことを意図しているからです。これらのコンテナは必要な全ての機能を提供していません。対して、Java EEアプリケーションは本格的なJava EEアプリケーションサーバ(例えばOracle GlassFish Server)にデプロイされることを意図しています。Java EE 6アプリケーションサーバはエンタープライズで必要な機能をほとんど提供しており、外部依存性を管理する必要はありません。
こうした理由で、エンタープライズアプリケーションの開発にはSpringよりもJava EEを常にお勧めしています。
参考
著者について
David Heffelfinger
ソフトウェアコンサルタンティングファームであるEnsode Technology, LLCのCTO。
1995年よりアーキテクチャ、デザイン、ソフトウェア開発に従事。1996年からJavaを主要なプログラミング言語として活用し、大規模プロジェクト(アメリカ国土安全保障省、Freddie Mac、Fannie Mae、アメリカ国防総省など)に参画。Southern Methodist Universityソフトウェア工学修士。

0 件のコメント:

コメントを投稿