<< 前のページ  |最新号|  次のページ >>

 
 isbn番号の入力チェックにStrutsのvalidateを使います。
 
 input.doとinsertedit.doではActionFormを使う必要がない
ので、以下のように修正しました。これでbookCatalogForm
のvalidateメソッドは、list.doアクションとinsert.doアクション
の場合のみ実行されます。

struts-config.xml
------------------------------------------------------- 

 <?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>

 <!-- ActionForm-->
 <form-beans>
  <form-bean name="bookCatalogForm" type="action.form.BookCatalogForm"></form-bean>
 </form-beans>
 
 <!-- Action--> 
 <action-mappings>
  <action path="/list"
    type="action.BookCatalogAction"
    name="bookCatalogForm"
    validate="true"
    input="/WEB-INF/jsp/input.jsp">
   <forward name="success" path="/WEB-INF/jsp/itemMenu.jsp" />
  </action>

  <action path="/input"
    type="action.InputAction">
   <forward name="success" path="/WEB-INF/jsp/input.jsp" />
  </action>

  <action path="/insertedit"
    type="action.InsertEditAction">
   <forward name="success" path="/WEB-INF/jsp/insert.jsp" />
  </action>

  <action path="/insert"
    type="action.InsertAction"
    name="bookCatalogForm"
    validate="true"
    input="/WEB-INF/jsp/insert.jsp">
   <forward name="success" path="/WEB-INF/jsp/result.jsp" />
  </action>

 </action-mappings>

 <message-resources parameter="application"/>
-------------------------------------------------------

BookCatalogFormでは正規表現を使ってisbn番号のチェックを行っています。

BookCatalogForm
------------------------------------------------------- 

package action.form;

import java.util.List;
import java.util.regex.*;

import javax.servlet.http.HttpServletRequest;

import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;


public class BookCatalogForm extends ActionForm{
 private List bookList;
 private String isbn;
 private String title;
 private String content;
 public List getBookList() {
  return bookList;
 }
   
 public ActionErrors validate(
            ActionMapping mapping,
            HttpServletRequest request) {

  ActionErrors errors = new ActionErrors();
  
  if(isbn!="" && isbn!= null){
         Pattern ptn;
         Matcher mch;
         
         ptn = Pattern.compile("[0-9A-Z]{10}");
         mch = ptn.matcher(isbn);
         if(mch.find()!= true){
          errors.add("isbn", new ActionMessage(
          "isbn.invalid"));
         }
        }else {
      errors.add("isbn", new ActionMessage(
      "isbn.noinput"));
        }
  
  return errors;
    }
 
 
 public void setBookList(List bookList) {
  this.bookList = bookList;
 }

 public String getIsbn() {
  return isbn;
 }

 public void setIsbn(String isbn) {
  this.isbn = isbn;
 }

 public String getTitle() {
  return title;
 }

 public void setTitle(String title) {
  this.title = title;
 }

 public String getContent() {
  return content;
 }

 public void setContent(String content) {
  this.content = content;
 }
}
-------------------------------------------------------


struts-config.xmlで指定したメッセージリソースファイルです。

application.properties
------------------------------------------------------- 
errors.header=<ul>
errors.prefix=<li>
errors.suffix=</li>
errors.footer=</ul>

isbn.noinput = isbn番号を入力してください
isbn.invalid = isbn番号が不正です
------------------------------------------------------- 

input.jspとinsert.jspでは、<html:errors/>で入力チェックエラーをすべて出力しています。


input.jsp
------------------------------------------------------- 
<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@taglib prefix="html" uri="http://jakarta.apache.org/struts/tags-html" %>
<%@taglib prefix="bean" uri="http://jakarta.apache.org/struts/tags-bean" %>
<%@taglib prefix="logic" uri="http://jakarta.apache.org/struts/tags-logic" %>

<html:html>
<head>
<title>書籍検索条件入力画面</title>
</head>
<div align="center">
<body>
<h2>書籍検索条件入力</h2>
<html:form action="/list">

<!-- <form method="POST" action="list.do"> -->
<!--isbn番号<input type="text" name="isbn">  -->
<html:text name="bookCatalogForm" property="isbn" maxlength="10"/>
<br>
<input type="submit" name="serch" value="検索">
</html:form>

<html:errors/>

<html:link page="/main.html">メインへ</html:link>
</body>
</div>
</html:html>
------------------------------------------------------- 

insert.jsp
-------------------------------------------------------
<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@taglib prefix="html" uri="http://jakarta.apache.org/struts/tags-html" %>
<%@taglib prefix="bean" uri="http://jakarta.apache.org/struts/tags-bean" %>
<%@taglib prefix="logic" uri="http://jakarta.apache.org/struts/tags-logic" %>
<html:html>
<head>
 <title>書籍登録画面</title>
</head>
<body>
<html:form action="/insert">
 <div align="center" class="body">
  <h2><font>書籍登録</font></h2>
 <table>
  <tr height="40px">
   <td>isbn番号</td>
   <td>
    <html:text name="bookCatalogForm" property="isbn" maxlength="10"/>
   </td>
  </tr>
  <tr height="40px">
   <td>タイトル</td>
   <td>
    <html:text name="bookCatalogForm" property="title" maxlength="100"/>
   </td>
  </tr>
  <tr height="40px">
   <td>概要</td>
   <td>
    <html:textarea name="bookCatalogForm" property="content" />
   </td>
  </tr>
 </table>
 <br>
 <html:submit value="登録" />
 <br><br>
 
 <html:errors/>
 
 <html:link page="/main.html">メインへ</html:link>
 </div>
</html:form>
</body>
</html:html>
-------------------------------------------------------

今回は、書籍の登録処理を追加します。まず
struts-config.xmlから見ていきましょう。






 struts-config.xml
-------------------------------------------------------------------

<?xml version="1.0" encoding="Shift_JIS"?>
    <!DOCTYPE struts-config PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
    "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>

<!-- ActionForm-->

<form-beans>
    <form-bean name="bookCatalogForm" type="action.form.BookCatalogForm"></form-bean>
</form-beans>

<!-- Action-->
<action-mappings>
    <action path="/list" type="action.BookCatalogAction" name="bookCatalogForm">
<forward name="success" path="/WEB-INF/jsp/itemMenu.jsp" />

</action>
  
<action path="/input" type="action.InputAction" name="bookCatalogForm">
    <forward name="success" path="/WEB-INF/jsp/input.jsp" />
</action>

<action path="/insertedit" type="action.InsertEditAction" name="bookCatalogForm">
    <forward name="success" path="/WEB-INF/jsp/insert.jsp" />
</action>

<action path="/insert" type="action.InsertAction" name="bookCatalogForm">
    <forward name="success" path="/WEB-INF/jsp/result.jsp" />
</action>

</action-mappings>

</struts-config>

-------------------------------------------------------------------

追加したアクションは太字部分です。入力用画面のinsert.jspと結果画面のresult.jsp
です。

呼び出しはそれぞれ、/insertedit.do、/insert.doですが、/insert.doを直接呼び出すこと
はありません。(入力情報がないと登録処理ができない)

※現在はこの制御はしないため、/insert.doを直接呼び出すと、SQLExceptionが
発生します。スコープ内のActionForm(BookCatalogFormクラス)からデータを取得
できないためです。

「登録画面から正しく登録ボタン押下によって/insert.doが呼ばれること」が条件と
なります。アプリケーション側で上記のような制御ロジックを入れる必要があります。


insert.jspは、isbn番号とタイトルの入力テキストとsubmitボタンのみの画面で、
result.jspは登録が成功したらメッセージを表示するだけのどちらも簡単な画面です。



insert.jsp
-------------------------------------------------------------------

<%@
page contentType="text/html;charset=Shift_JIS" %>
<%@
taglib prefix="html" uri="http://jakarta.apache.org/struts/tags-html" %>
<%@
taglib prefix="bean" uri="http://jakarta.apache.org/struts/tags-bean" %>
<%@
taglib prefix="logic" uri="http://jakarta.apache.org/struts/tags-logic" %>

<html:html>
<
head>
<
title>書籍登録画面</title>

</head>
<
body>
<
html:form action="/insert">
<
div align="center" class="body">

<h2>書籍登録</font></h2>
<
table>
    <
tr height="40px">
        <
td>isbn番号</td>
        <
td>
            <
html:text name="bookCatalogForm" property="isbn" maxlength="10"/>
        </
td>
    </
tr>

    <tr height="40px">
        <
td>タイトル</td>
        <
td>
            <
html:text name="bookCatalogForm" property="title" maxlength="100"/>
        </
td>
    </
tr

    <
tr height="40px">
        <
td>概要</td>
        <
td>
            <
html:textarea name="bookCatalogForm" property="content" />
        </
td>
    </
tr>
</
table>
<
br>
<
html:submit value="登録" />
</
div>
</
html:form>
</
body>
</
html:html>
-------------------------------------------------------------------

<html:form action="/insert">のStrutsタグ部分は、struts-config.xmlの

<action>要素を参照して


<form name="bookCatalogForm" method="POST" action="/insert.do">


と解釈されます。


result.jsp
-------------------------------------------------------------------
<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@
taglib prefix="html" uri="http://jakarta.apache.org/struts/tags-html" %>

<html>
<
head>
<
title>登録完了</title>
</
head>
<
body>
<
div align="center" class="body">

登録しました。

<BR>

<html:link action="/insertedit" >戻る</html:link>

</body>
</
html>
-------------------------------------------------------------------



実装の方を見てみましょう。まず、登録処理に必要となる登録用RDBMS操作クラス
InsertBookクラスを新規追加します。

これは以前、コマンドラインベースのアプリケーションで使用したものです。それを
そのまま使っています。RDBMS操作クラスを使うことでこのように簡単にクラスの
使い回しが出来ます。

package dao;

import java.sql.Types ;
import javax.sql.DataSource ;
import org.springframework.jdbc.core.SqlParameter ;
import org.springframework.jdbc.object.SqlUpdate ;

public
class InsertBook extends SqlUpdate{

    private static final String sqlStr = "INSERT INTO book(isbn , title)"
  
                                     +" VALUES(? , ?)" ;

    public InsertBook(DataSource ds) {

        super(ds , sqlStr) ;

        super.declareParameter(new SqlParameter("isbn", Types.VARCHAR)) ;

        super.declareParameter(new SqlParameter("title", Types.VARCHAR)) ;

          compile() ;

    }

}

 

InsertBookクラスを使うBookDaoImplクラスを追加修正します。
BookDaoImplクラスに追加したinsertBookメソッドがそのままサービス
メソッドになります。

※BookCatalogインターフェース、BookCatalogImplクラスのinsertBook
メソッドに対応しています。




package
dao ;

import java.util.List ;
import org.springframework.dao.DataAccessException ;
import org.springframework.jdbc.core.support.JdbcDaoSupport ;

import
model.Book;

public
class BookDaoImpl extends JdbcDaoSupport implements BookDao{

    protected void initDao() throws Exception {

        this.bookListQuery = new BookListQuery(getDataSource()) ;
        this.insertBook = new InsertBook(getDataSource()) ;

}
   //書籍をisbnで検索する(RDBMS操作クラスを使う)
   
private BookListQuery bookListQuery ;

   public List getBookList(String isbn) throws DataAccessException {

        return this.bookListQuery.execute(isbn) ;

    }
   //書籍を追加する(RDBMS操作クラスを使う)
   
private InsertBook insertBook ;

    public void insertBook(Book book) throws DataAccessException {

       this.insertBook.update(new Object[]
            {book.getIsbn() ,
  
      book.getTitle()}) ;

        }
}



そして、実際にapplicationContextからインスタンスを取得する
BookCatalogImplクラスにサービスメソッド(insertBookメソッド)を
追加します。

※BookCatalogにインターフェース(API)を定義し、BookCatalogImplクラス
で実装します。DIコンテナから実装のインスタンスを受けとりインターフェース
に代入します。これは基本ですね。

package model ;

import
java.util.List;
import model.Book;

public interface BookCatalog {
  
List getBookList(String isbn);
  
void insertBook(Book book);
}

 

package model ;

import
dao.BookDao;
import model.Book;
import java.util.List;

public
class BookCatalogImpl implements BookCatalog{
  
private BookDao bookDao;
   
    public
void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

   public List getBookList(String isbn) {
        return this.bookDao.getBookList(isbn);
    }

    public void insertBook(Book book){
        this.bookDao.insertBook(book);

    }

}

最後に登録処理を行うActionクラス(InsertActionクラス)を新規追加します。
ソースを見て分かると思いますが、登録に関する処理は
bookCatalog.insertBook(book) ;だげです。


DIによってdatasource、DAO(BookDaoImpl クラス)、サービスオブジェクト
(BookCatalogImpl クラス)が連鎖的にインジェクションされて、サービスメソッド
(insertBookメソッド)から一気にDB登録処理が実行されます。

package action;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import
org.springframework.web.struts.ActionSupport;
import org.springframework.context.ApplicationContext;

import action.form.BookCatalogForm;
import model.BookCatalog;
import model.Book;

public class InsertAction extends ActionSupport{

    public ActionForward execute(ActionMapping mapping,
        ActionForm form, HttpServletRequest request,
        HttpServletResponse response)
throws Exception {

        BookCatalogForm bookCatalogForm = (BookCatalogForm)form;

       // AppricationContex取得
        ApplicationContext applicationContext = getWebApplicationContext();

        BookCatalog bookCatalog = (BookCatalog)applicationContext.getBean("bookCatalog");

        // 書籍情報(入力)の取得
        Book book = new Book() ;
        book.setIsbn(bookCatalogForm.getIsbn()) ;
        book.setTitle(bookCatalogForm.getTitle()) ;

       //DBへ登録(ビジネスロジックはこれだけ)
       bookCatalog.insertBook(book) ;

        return mapping.findForward("success");

    }

}

以上で簡易的な書籍の登録機能の追加が出来ました。今回の機能追加では
DIの設定(applicaitonContext.xml)の変更はありません。



文字化け対策


JSPでエンコーディングを指定していましたが、文字化けが発生するので、
以下のようにSpringのCharacterEncodingFilterを使用することにしました。


web.xmlに追加

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>Shift_JIS</param-value>
    </init-param>
   
    <init-param>
  
     <param-name>forceEncoding</param-name>
  
     <param-value>true</param-value>
  
</init-param>
</filter>

<filter-mapping>
  
<filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

前回は、Spring+Strutsを使って書籍の
一覧画面を出力しました。
検索条件(isbn番号)の入力画面から
入力されたisbn番号で検索するように
しましょう。

 

まず検索条件入力画面はSpringMVCバージョンの時に使ったinput.jspをほとんど
そのまま使えます。

input.jsp
-------------------------------------------------------------------
<%@
page language="java" contentType="text/html; charset=windows-31j"
pageEncoding="windows-31j"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<
head>
<
meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
<
title>書籍検索条件入力画面</title>
</
head>
<
body>
<
h2>書籍検索条件入力</h2>
<
form method="POST" action="list.do">
isbn番号
<input type="text" name="isbn">
<
br>
<
input type="submit" name="serch" value="検索">
</
form>
</
body>
</
html>
-------------------------------------------------------------------

このJSPを直接呼び出すのではなくStrutsを使って/input.doで呼び出すようにします。

具体的には新しくActionクラス(InputActionクラス)を作成します。このActionクラスは
ただinput.jspを表示するだけのものです。

public class InputAction extends ActionSupport{
  
public ActionForward execute(ActionMapping mapping,
  
   ActionForm form, HttpServletRequest request,
         HttpServletResponse response)
throws Exception {

  
     return mapping.findForward("success");

    }
}



struts-config.xml
-------------------------------------------------------------------
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>

<!-- ActionForm-->

<form-beans>
  
<form-bean name="bookCatalogForm" type="action.form.BookCatalogForm"></form-bean>
</form-beans>

<!-- Action-->
<action-mappings>
  
<action path="/list" type="action.BookCatalogAction" name="bookCatalogForm">
        <forward name="success" path="/WEB-INF/jsp/itemMenu.jsp" />
  
</action>

    <action path="/input" type="action.InputAction" name="bookCatalogForm">
  
   <forward name="success" path="/WEB-INF/jsp/input.jsp" />
  
</action>
</action-mappings>

</struts-config>

-------------------------------------------------------------------

さらに入力されたisbn番号を受け取るActionFormクラスであるBookCatalogForm
クラスにisbn番号のセッタ/ゲッタを定義します。

そして、BookCatalogActionクラス(一覧表示アクションクラス)を呼び出します。
呼び出しはlist.doです。

BookCatalogActionクラスでは入力されたisbn番号をActionFormクラス
(BookCatalogFormクラス)から取得してgetBookList()メソッドのパラメタ
に指定します。

public class BookCatalogAction extends ActionSupport{
   
public ActionForward execute(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
throws Exception {

        BookCatalogForm bookCatalogForm = (BookCatalogForm)form;

       // AppricationContex取得
       ApplicationContext applicationContext = getWebApplicationContext();

        BookCatalog bookCatalog = (BookCatalog)applicationContext.getBean("bookCatalog");

        // 書籍一覧情報の取得
        List bookList = bookCatalog.getBookList(bookCatalogForm.getIsbn()) ;

        bookCatalogForm.setBookList(bookList);

       return mapping.findForward("success");

    }

}


以上で、検索条件からisbn番号を入力して結果を一覧表示するアプリケーションを
Spring + Strusに移植完了しました。

[ 2007/10/01 17:05更新 ]  

SpringフレームワークではWebフレームワークとして独自のSpringMVCが
ありますが、WebフレームワークのデファクトスタンダードのStrutsもサポート
しています。

Strutsの次のスタンダードとしてJSFやStruts2があげられるなど、Web
フレームワークでもJ2EEは今、変動期にあるといってもいいのではない
でしょうか?

ですが既存ソフト資産や安定性、技術情報量の多さもあってStrutsは
まだまだ使い続けられるでしょう。

書籍の一覧検索をSpring + Strutsに移植してみましょう。

ポイントはActionSupportクラスです。


ではコードいってみよう!



package
action;
import java.util.List;
import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import org.springframework.context.ApplicationContext;
import org.springframework.web.struts.ActionSupport;

import model.BookCatalog;
import action.form.BookCatalogForm;

public
class BookCatalogAction extends ActionSupport{

public ActionForward execute(ActionMapping mapping, ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
throws Exception {

    BookCatalogForm bookCatalogForm = (BookCatalogForm)form;

    // AppricationContex取得
    ApplicationContext applicationContext = getWebApplicationContext();

    BookCatalog bookCatalog = (BookCatalog)applicationContext.getBean("bookCatalog");

   // 書籍一覧情報の取得
    List bookList = bookCatalog.getBookList("1234567890") ;

   // ActionFormクラスにデータを設定する。 
  bookCatalogForm.setBookList(bookList);

    return mapping.findForward("success");

    }

}

このクラスがStrutsの場合のActionクラスに相当します。executeメソッドのシグニチャ
も全く同じです。

BookCatalogFormkクラスはStrutsのActionFormです。


前回記事でapplicationContextを切り分けたので、getWebApplicationContext()関数
でApplicationContext を生成し、DIコンテナからbookCatalogクラスを取得しています。

bookCatalog型(インターフェース)変数にはbookCatalogImplクラスのインスタンスが
入ります。この辺はSpringMVCでの場合と同じです。

※実装型に直接代入するのではなく、インターフェースに代入します。


SpringMVCの場合と異なるのはBookCatalogFormkクラス(ActionFormクラス)を
利用してJSPにデータを渡す部分です。

※BookCatalogFormkクラスはgetBookList()関数の結果のList<Book>を格納するために
使用するList<Book>型変数のセッタ/ゲッタのみを定義したActionFormクラスです。


こうすることでJSPにてBookCatalogFormkクラスのインスタンスからisbn番号とタイトルを
取得することが出来ます。


一覧結果を出力するJSP
------------------------------------------------------------------------
<%@ page language="java" contentType="text/html; charset=windows-31j"
pageEncoding="windows-31j"%>
<%@
taglib prefix="html" uri="http://jakarta.apache.org/struts/tags-html" %>
<%@
taglib prefix="bean" uri="http://jakarta.apache.org/struts/tags-bean" %>
<%@
taglib prefix="logic" uri="http://jakarta.apache.org/struts/tags-logic" %>

<html:html>
<
head>
<
title>書籍一覧(strutsバージョン)</title>
</
head>
<
body>
<
div align="center" class="body">
<
h2>書籍一覧</h2>
<
table border="1">
<
tr class="header">
<
th align="center" width="80">iabn番号</th>
<
th align="center" width="320">タイトル</th>
</
tr>
<
logic:iterate id="item" name="bookCatalogForm" property="bookList">
<
tr class="record">
<
td align="center">
<
bean:write name="item" property="isbn" />
</
td>
<
td align="left">
<
bean:write name="item" property="title" />
</
td>
</
tr>
</
logic:iterate>
</
table>
</
div>
</
body>
</
html:html>
------------------------------------------------------------------------

Strutsカスタムタグを使っていますが、JSTLの場合とほぼ同じです。

※Eclipseプロジェクトのファイル構成は専用HPに掲載してあります。

SpringMVCを用いたWebアプリケーションでは、DIの設定をサーブレット
コンテキストとアプリケーションコンテキストに分けるとデータソース/
ビジネスロジックに関するDIの設定と、MVCに関するDIの設定を別ファイル
に記述できて便利です。

MVCに関するDIの設定とは、

HandlerMapping、Controller、ViewResolverの設定(実装クラスの指定)のことを
言っています。


具体的にはbookmng-2-servlet.xmlに全てのDIを記述していたのをapplication
Context.xmlファイル
(新規作成)にデータソース/ビジネスロジックのDI設定を
移動します。

ここでどちらが先に読み込まれるかが重要になってきます。というのも
データソース/ビジネスロジックの方が先に読み込まれてDIが完了してから
IndexControllerクラスにサービスbean(catalogImplクラス)が読み込まれる
必要があるからです。


public class IndexController implements Controller{

    private BookCatalog bookCatalog;                //これにBookCatalogImplインスタンスが
                                                        //設定されていなければならない!

    public void setBookCatalog(BookCatalog bookCatalog) {

        this.bookCatalog = bookCatalog;

    }

   public ModelAndView handleRequest(HttpServletRequest request,

        HttpServletResponse response) throws Exception {

        String isbn = request.getParameter("isbn") ;

// 書籍一覧情報を取得(ここには入力されたisbn番号を設定)
List itemList = this.bookCatalog.getBookList(isbn);

 

上記コードは書籍の検索一覧表示を行うコントローラ(IndexControllerクラスの抜粋)です。
getBookList(isbn)メソッドで一覧を取得するには当然、HttpServletクラスを継承した
ControllerクラスにBookCatalogImplインスタンスがDIされていないといけません。

そこでContextLoaderListenerクラスを使います。

こうすることでapplicationContext.xmlが有効になります。


web.xml (DispatcherServletの設定) listener属性を追加 ContextLoaderListenerクラスを使う
-----------------------------------------------------------------------
<?xml version="1.0" encoding="Shift_JIS"?>

<!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>

<listener>

<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>

</listener>

<servlet>

<servlet-name>bookmng-2</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>bookmng-2</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

</web-app>
-----------------------------------------------------------------------

入力画面を作成するには当然ですが、入力画面用
のファイル(Html、jsp)とそのURLが必要になります。


【URLとファイル】
一覧表示用のURL(index.html)         ファイル:index.jsp
検索条件入力用のURL(input.html)  ファイル:input.jsp

そこで、いままでは指定をしていなかった
HandlerMappingの指定を行います。

※指定をしない場合はSpringMVCによって
自動的にBeanNameUrlHandlerMappingクラスが
使用されます。(今まではこれを使用していました)

BeanNameUrlHandlerMappingクラスはURLと1対1でマッピングするデフォルトの
マッピングクラスです。したがってURLと画面のマッピングの数だけ記述が必要に
なります。

SimpleUrlHandlerMappingクラスはコントローラのマッピングを一元管理するクラス
です。画面数が多くなる場合にまとめて管理できる便利なクラスです。さっそく使って
みましょう。


bookmng-1-servlet.xml
-------------------------------------------------------------------------------

<?xml version="1.0" encoding="Shift_JIS"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
                                "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!-- HandlerMapping -->

<bean id="handlerMapping"
            class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/index.html">indexController</prop>

<prop key="/input.html">inputController</prop>

</props>

</property>

</bean>

<!-- Controller -->

<bean id="indexController"  class="controller.IndexController">

<property name="bookCatalog"><ref bean="bookCatalog" /></property>

</bean>

<bean id="inputController" class="controller.InputController">

</bean>

<!-- ViewResolver -->

<!-- Data Source -->

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<!-- JDBCドライバクラス名の設定 -->

<property name="driverClassName">

<value>org.postgresql.Driver</value>

</property>

<!-- JDBCの接続文字列の設定 -->

<property name="url">

<value>jdbc:postgresql://192.168.1.11:5432/BookMng</value>

</property>

<!-- PostgreSQLのユーザIDの設定 -->

<property name="username">

<value>postgres</value>

</property>

<!-- PostgreSQLのパスワードの設定 -->

<property name="password">

<value>c079388a</value>

</property>

</bean>

<!-- BookCatalog -->

<bean id="bookCatalog" class="model.BookCatalogImpl">

<property name="bookDao">

<ref bean="bookDao" />

</property>

</bean>

 

<!-- BookDao -->

<bean id="bookDao" class="dao.BookDaoImpl">

<property name="dataSource">

<ref bean="dataSource" />

</property>

</bean>

</beans>
-------------------------------------------------------------------------------

input.htmlに対するコントローラはinputControllerで、以下のように単純にビューを
指定するだけのクラスです。

public
class InputController implements Controller{

   public ModelAndView handleRequest(HttpServletRequest request,
  
                 HttpServletResponse response) throws Exception {

    // 戻り値となるModelAndViewインスタンスを作成
   
ModelAndView modelAndView = new ModelAndView();

    modelAndView.setViewName("/WEB-INF/jsp/input.jsp");

    return modelAndView;

    }

}

input.jspは、isbn番号を入力して検索ボタンを押すだけの簡単な画面です。
(添付参照)

あとは、indexControllerクラスでinput.jspからリクエストからisbn番号を取得
して、今まで固定で指定していた部分へ代入するだけです。


以上で入力画面からisbn番号を入力して書籍を検索する簡単なアプリが出来
上がりました。 ただし、入力値チェックなどはまだ一切行っていません。

ソースファイル、プロジェクトの構成は専用HPで確認できます。

[ 2008/05/16 12:58更新 ]  

データベースによってDAOを切り替える必要が
ある場合があります。書籍管理システムでは
PostgreSQLを使っていますが、これをOracleに
変更するする必要がある場合を想定して、
Factory Methodパターン[GoF]を利用した
DaoFactoryを設計しました。

以下記事参照)

http://blog.mag2.com/m/log/0000235295/108820005.html

しかし、この設計には問題点があります。そう、IbookDAOインターフェースをOracleDao
クラスとPostgreSQLDaoクラスが直接実装しています。これでは書籍以外のDAO、
例えば注文DAOを導入する場合に不都合です。そこでAbstract Factoryパターン
[GoF]
を利用する手が考えられます。

以下GoF本から抜粋)

●目的
 互いに関連したり依存し合うオブジェクト群を、その具象クラスを明確にせず
生成するためのインターフェースを提供する。

Abstract Factoryとは「抽象的な工場」という意味です。具体的な工場の実装を直接
使わずに抽象的なAPIを提供し、それを使うということです。この考え方は多相(ポリモ
フィズム)のキホンです。

では、さっそくソース行ってみよう!

import java.util.List;
import java.util.ArrayList;

class Book {
private String isbn ;
public Book(String isbn) {
this.isbn = isbn ;
}
public String getIsbn(){
return isbn ;
}
}
class Order {
private String id ;
public Order(String id) {
this.id = id ;
}
public String getId(){
return id ;
}
}

interface BookDao {
List<Book> getBookList() ;
}
interface OrderDao {
List<Order> getOrderList() ;
}

class PostgreBookDaoImpl implements BookDao {
public PostgreBookDaoImpl(){}

/**
* postgreSQL DBから擬似的にデータを取得する
*/
public List<Book> getBookList() {
List<Book> bookList = new ArrayList<Book>() ;
bookList.add(new Book("1234567890")) ;
return bookList ;
}
}
class PostgreOrderDaoImpl implements OrderDao {
public PostgreOrderDaoImpl(){}

/**
* postgreSQL DBから擬似的にデータを取得する
*/
public List<Order> getOrderList() {
List<Order> orderList = new ArrayList<Order>() ;
orderList.add(new Order("001")) ;
return orderList ;
}
}
class OracleBookDaoImpl implements BookDao {
public OracleBookDaoImpl(){}

/**
* Oracle DBから擬似的にデータを取得する
*/
public List<Book> getBookList() {
List<Book> bookList = new ArrayList<Book>() ;
bookList.add(new Book("1234567890")) ;
return bookList ;
}
}
class OracleOrderDaoImpl implements OrderDao {
public OracleOrderDaoImpl(){}

/**
* Oracle DBから擬似的にデータを取得する
*/
public List<Order> getOrderList() {
List<Order> orderList = new ArrayList<Order>() ;
orderList.add(new Order("001")) ;
return orderList ;
}
}
interface DBFactory {
BookDao createBookDao() ;
OrderDao createOrderDao() ;
}


class OracleFactory implements DBFactory {
public BookDao createBookDao(){
return new OracleBookDaoImpl() ;
}
public OrderDao createOrderDao(){
return new OracleOrderDaoImpl() ;
}
}


class PostgreFactory implements DBFactory {
public BookDao createBookDao(){
return new PostgreBookDaoImpl() ;
}
public OrderDao createOrderDao(){
return new PostgreOrderDaoImpl() ;
}
}

public class AbstractFactoryPtnClient {
public static void main(String[] args){
DBFactory orafactory = new OracleFactory() ;
DBFactory pstfactory = new PostgreFactory() ;

//Oracle用の書籍DAOを作成
BookDao oradao = orafactory.createBookDao() ;

//postgreSQL用の注文DAOを作成
OrderDao pstdao = pstfactory.createOrderDao() ;



List list = oradao.getBookList() ;
Book book = (Book)list.get(0) ;
System.out.println("isbn番号:" + book.getIsbn()) ;

list = pstdao.getOrderList() ;
Order order = (Order)list.get(0) ;
System.out.println("注文番号:" + order.getId()) ;


}
}
 
■実行結果
isbn番号:1234567890
注文番号:001

このプログラムの場合、抽象的なインターフェースはBookDaoインターフェースと
OrderDao
インターフェースです。

DBFactoryインターフェースを実装したOracre用、PostgreSQL用のDAO生成クラス
では、実際にDAOのインスタンスを生成しています。

クライアントコードの太字(強調部分)に注目してください。

この例ではOracle用の書籍DAOpostgreSQL用の注文DAOしか作成していま
せんが、Oracle用の注文DAOpostgreSQL用の書籍DAOを作成するのも
容易です。

さて、これをDIを使えばどうなるでしょうか?SpringMVCではXML定義ファイルに
データベース依存部分がインジェクションされますので、そこを変更するだけでよい
ことになります。

つまりAbstract Factoryパターンを使う必要はないということになります。

ところでAbstract FactoryパターンはGoF本では最もよく使われるパターンの
一つとしてあげられています。23個のデザインパターンの重要性や使用頻度は
時代やシステム開発を取り巻く環境や使う立場の人たちによって認識が異なります。
これはむしろ当たり前のことのように感じます。

逆に時代や環境を超えて普遍的なものであるということもまた事実です。
読者の皆さんはどう考えますか?

Abstract Factoryパターンは相関が複雑なのでサンプルプログラムのUML
クラス図を添付します。時間があるときにじっくりと考察してみてください!

SpringMVCでは、Web.xmlの<servlet-name>ディレク
ティブで指定した文字列-servlet、以下の例では
bookmng-2-servlet.xml
がSpringMVCの読み込む
ファイルになります。マッピングする訳ですね。。


Web.xml
----------------------------------------------------------------------------
<?xml version="1.0" encoding="Shift_JIS"?>

<!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>bookmng-2</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>bookmng-2</servlet-name>

<url-pattern>*.html</url-pattern>

</servlet-mapping>

</web-app>

----------------------------------------------------------------------------


以下の設定ファイルですべてbeanインスタンスの設定は解決されます。

bookDaoはdataSourceを参照し、bookCatalogはbookDaoを参照します。そして
indexControllerはbookCatalogを参照します。このようにして参照の連鎖ができます。


bookmng-2-servlet.xml
----------------------------------------------------------------------------

<?xml version="1.0" encoding="Shift_JIS"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<!-- HandlerMapping -->

<!-- Controller -->

<bean id="indexController" name="/index.html" class="controller.IndexController">

<property name="bookCatalog"><ref bean="bookCatalog" /></property>

</bean>

<!-- ViewResolver -->

<!-- Data Source -->

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<!-- JDBCドライバクラス名の設定 -->

<property name="driverClassName">

<value>org.postgresql.Driver</value>

</property>

<!-- JDBCの接続文字列の設定 -->

<property name="url">

<value>jdbc:postgresql://192.168.1.11:5432/BookMng</value>

</property>

<!-- PostgreSQLのユーザIDの設定 -->

<property name="username">

<value>postgres</value>

</property>

<!-- PostgreSQLのパスワードの設定 -->

<property name="password">

<value>c079388a</value>

</property>

</bean>

<!-- BookCatalog -->

<bean id="bookCatalog" class="model.BookCatalogImpl">

<property name="bookDao">

<ref bean="bookDao" />

</property>

</bean>

<!-- BookDao -->

<bean id="bookDao" class="dao.BookDaoImpl">

<property name="dataSource">

<ref bean="dataSource" />

</property>

</bean>

</beans>
----------------------------------------------------------------------------


BookCatalog型(インターフェース)にDIコンテナからで実際にはdao.BookDaoImplクラス
のオブジェクトが代入されることに注目してください。

このようにインターフェースを介して実装(bean)をインジェクションすることでコードには
実装の型を記述しません。実装型ではなくインターフェースにインスタンスを代入する
メリットは読者の方はお分かりでしょう!

下記のIndexControllerの例では、BookCatalog型に対してBookCatalogImplインスタンス
を代入しています。(SpringMVCによって自動的にセットされます)

package controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import model.BookCatalog;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import model.Book;

public class IndexController implements Controller{

private BookCatalog bookCatalog;

//セッタインジェクション用
public void setBookCatalog(BookCatalog bookCatalog) {
this.bookCatalog = bookCatalog;
}

public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {

// 書籍一覧情報を取得(入力されたisbn番号を指定する)
//→現在Web入力画面がないのではとりあえず固定
List itemList = this.bookCatalog.getBookList("4797311126");


//List<Book>に追加してみる。
itemList.add(new Book("4756100503" , "オブジェクト指向入門")) ;


// モデルの作成
Map model = new HashMap();
model.put("itemList", itemList);

// 戻り値となるModelAndViewインスタンスを作成
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/WEB-INF/jsp/index.jsp");
modelAndView.addAllObjects(model);

return modelAndView;
}
}
 

package model ;

import java.util.List;

public interface BookCatalog {

List getBookList(String isbn);
}

 

package model ;

import dao.BookDao;
import java.util.List;

public class BookCatalogImpl implements BookCatalog{

private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public List getBookList(String isbn) {
return this.bookDao.getBookList(isbn);
}
}


※プロジェクト構成ファイルについては専用HPを参照ください。

さて、Webシステムになるとやはり便利なJAVA IDEを利用した方が効率的です。
等連載ではとりあえずEclipse3.2を使っていくことにします。

WebフレームワークはSpringMVC、Struts、Struts+Springサポート等を考えています。

次回は入力画面を作っていくことにします。

[ 2007/09/12 20:17更新 ]  

Stateパターン[GoF]は状態をオブジェクトとして扱います。

よくありがちな以下のような処理(擬似コード)を見てください。

天気によって処理が異なる場合、手続き型のプログラミングでは
以下のようになるでしょう。

function 傘をさす(){
    if (晴れ) { 何もしない。}
    elseif(曇り){傘をさすかどうか決める。}
    elseif(){傘をさす処理をおこなう。}
   
}
function 日傘をさす(){
    if (晴れ) { 日傘をさす処理をおこなう。}
    elseif(曇り){日傘をさすかどうか決める。}
    elseif(){何もしない。}

}
 
 このようにすると状態に対する分岐の数が多くなり、関数内のif分やswitch文が多くなり
それに伴って関数が巨大化していきます。そういうコードはとても読みにくいばかりでなく、
修正を加えるのが大変です。stateパターンはこのような状況の場合、リファクタリング
として非常に有効なテクニックになり得ます。

ではソース行ってみよう!

/**
@auhtor kenji nagano
*/
class Person {
Weather _weather ;
public Person(Weather weather){
_weather = weather ;
}
public void doneUmbrella(){
_weather.doneUmbrella() ;
}
public void doneParasol(){
_weather.doneParasol() ;
}

}
interface Weather {
void doneUmbrella() ;
void doneParasol() ;
}

class Rainny implements Weather{
private String _state ;
public Rainny(String state){
_state = state ;
}
public String toString(){
return "天気は雨です。" ;
}

public boolean equals(Object other){
if(other instanceof Rainny){
Rainny target = (Rainny)other ;
if(target.getState().equals(_state)){
return true ;
}else{
return false ;
}
}else{
return false ;
}
}
public String getState(){return _state ;}
public void doneUmbrella(){
System.out.println("傘をさしています。") ;
}
public void doneParasol() {
System.out.println("日傘なんていりません。") ;
}

}

class Clear implements Weather{
private String _state ;
public Clear(String state){
_state = state ;
}

public String toString(){
return "天気は晴れです。" ;
}

public boolean equals(Object other){
if(other instanceof Clear){
Clear target = (Clear)other ;
if(target.getState().equals(_state)){
return true ;
}else{
return false ;
}
}else{
return false ;
}
}
public String getState(){return _state ;}
public void doneUmbrella(){
System.out.println("傘なんていりません。") ;
}
public void doneParasol() {
System.out.println("日傘が必要です。") ;
}

}
class Cloudy implements Weather{
private String _state ;
Cloudy(String state){
_state = state ;
}
public String toString(){
return "天気は曇りです。";
}
public boolean equals(Object other){
if(other instanceof Cloudy){
Cloudy target = (Cloudy)other ;
if(target.getState().equals(_state)){
return true ;
}else{
return false ;
}
}else{
return false ;
}
}
public String getState(){return _state ;}
public void doneUmbrella(){
System.out.println("たぶん傘は必要ないでしょう。") ;
}
public void doneParasol() {
System.out.println("たぶん日傘は必要ないでしょう。") ;
}


}

public class StatePtnClient{
public static void main(String[] args){

if(args.length != 1){
System.out.println("usage : java 1 or 2 or 3") ;
System.exit(0) ;
}
Weather weather = null;

Person person = null ;

switch(Integer.valueOf(args[0])){
case 1 : {person = new Person(new Clear("clear")) ; break ;}
case 2 : {person = new Person(new Cloudy("cloudy")) ; break ;}
case 3 : {person = new Person(new Rainny("rainny")) ; break ;}
default : {
System.out.println("usage : java 1 or 2 or 3") ;
System.exit(0) ;}

}
person.doneUmbrella() ;
person.doneParasol() ;

}
}
 

■実行結果
C:\web\src\tips\GoF\State>java StatePtnClient 1
傘なんていりません。
日傘が必要です。


C:\web\src\tips\GoF\State>java StatePtnClient 2
たぶん傘は必要ないでしょう。
たぶん日傘は必要ないでしょう。

C:\web\src\tips\GoF\State>java StatePtnClient 3
傘をさしています。
日傘なんていりません。


■解説
 Personクラスはコンポジション用のクラスです。 したがってWeatherインター
フェースを実装した各Stateクラス(Rainny 、Clear 、Cloudy )はPersonクラス
を介して利用します。(コンポジション)

また各StateクラスのインスタンスをWeather型に代入していることに注目して
ください!こうすることで各Stateクラスの実装をクライアントは意識することが
ありません。(多相)
これもオブジェクト指向プログラミングの基本です。


■考察
 Stateパターンは、リファクタリングの際に最も用いられるパターンの一つです。
関連するパターンとしてStrategyパターン[GoF]があります。
Stateパターンが状態をオブジェクトとして扱うのに対し、Strategyパターンは
ロジック(処理)をオブジェクトとして扱います。

if分やswitch文が巨大になっているようであれば、Stateパターンを利用して
リファクタリングしましょう。また同じロジックが重複しているならばStrategy
パターンを利用しましょう。

両パターンの特徴としてどちらもコンポジションを使っていることがあげられます。

等連載で何度も問題として取り上げていますが、コンポジションはオブジェクト同士
の結合を緩やかにしてくれます。それによって例えばStrategyパターンであれば
新しいロジックに変更することが容易になります。
(クライアント側に変更が発生しない。)

 

[ 2007/09/12 00:22更新 ]  
SpringMVCと奮闘していますが、まだ十分な導入(書籍管理システムへの)のめどが
立っていない状況。私の知識不足や勉強不足も原因ではあるが、いくつか感じたこと
を書いてみることにしました。

1、SpringMVC自体の普及が現状あまりないように感じる。

2、Springの開発統合環境があまり整っていない。(情報も少ない)
 Web形式のアプリケーションの開発の場合、Eclipse等の統合開発環境は必須
である。beanやXMLなどのリソースファイルを決められた場所にデプロイしないと
いけないし、コードの補完機能や便利なエディタ、テスト環境、UMLツールなど、
やはりあると非常に便利なものである。

※EclipseにはSpringIDEプラグインがあります。


3、Spring2.0では仕様が大幅に追加拡張され、若干互換性に欠ける部分もある。
 したがって、Springフレームワーク自体がまだ成熟していない。(成長過程)

4、strutsと競合した場合、圧倒的にstrutsのほうが成熟していてSpringMVCは
まだいまから、という感じである。(両者のフレームワークは非常に似ている)

※どちらもフロントコントローラパターンのWebフレームワークであるという
ことです。
実際のフレームワークはかなり相違があります。

しかし、SpringMVCはDIの恩恵を最大に受けた非常にスマートなフレームワーク
です。strutsのサポート機能もあります。strutsの実装依存部分をDIによって補完
しているのです!