[Java] 52. SpringフレームワークでDAOをFactory method Patternを利用して依存性注入する方法


Study / Java    作成日付 : 2019/10/17 07:15:48   修正日付 : 2021/06/24 17:32:00

こんにちは。明月です。


この投稿はSpringフレームワークでDAOをFactory method Patternを利用して依存性注入する方法に関する説明です。


以前の投稿でSpringフレームワークでJPA ORMのDAOを@Autowiredのアトリビュートを使って依存性注入する方法に関する説明しました。

link - [Java] 51. SpringフレームワークでJPAを使い方(依存性注入@Autowired)


Spring ControllerにはDAOを取得してJPA ORMを利用してデータを取得する部分に関しては問題ありません。

でも、問題はSpring ControllerではないクラスでDAOを取得する方法が問題です。もちろん、一般クラスでただDAOクラスのインスタンスを生成(new)して使っても問題ありません。


しかし、Springで依存性注入でSingleton形式で使うのに、他の一般クラスで一般インスタンス生成してデータベースに接続してデータを取得することではなく、同じSingletonパターンで取得して使いたいです。

そのため、DAOクラスを制御するFactory method patternを先に作成することが必要です。

package dao;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class FactoryDao {
  // FactoryDaoクラスのsingletonパターンのインスタンス変数
  private static FactoryDao instance = null;
  // Daoクラスのインスタンスを格納するflyweightパターンのマップ
  private final Map<Class<?>, AbstractDao<?>> flyweight;
  // Singletonパターンを守るためにコンストラクタをprivateタイプに設定
  private FactoryDao() {
    // flyweightパターンのマップのインスタンス生成
    flyweight = new HashMap<Class<?>, AbstractDao<?>>();
  }

  @SuppressWarnings("unchecked")
  // DAOインスタンスを取得するためのSingletonパターンの関数
  public static <T> T getDao(Class<T> clz) {
    try {
      // FactoryDaoのインスタンスがなければ生成する。
      if (instance == null) {
        // インスタンス生成
        instance = new FactoryDao();
      }
      // FactoryDaoのflyweightマップでパラメータのクラスタイプのDAOが存在しない場合
      if (!instance.flyweight.containsKey(clz)) {
        // Reflection機能でコンストラクタを探す
        Constructor<T> constructor = clz.getDeclaredConstructor();
        // アクセス修飾子に関係せず、アクセス可能にする設定
        constructor.setAccessible(true);
        // flyweightのマップにクラスタイプをキーに設定してインスタンスを格納する。
        instance.flyweight.put(clz, (AbstractDao<?>) constructor.newInstance());
      }
      // flyweightのマップに格納されたDAOインスタンスをリターンする。
      return (T) instance.flyweight.get(clz);
    } catch (Throwable e) {
      // エラーが発生
      throw new RuntimeException(e);
    }
  }
}

上のソースはFactoryDaoクラスをSingletonパターンタイプで作成しました。つまり、プログラムが開始してFactoryDaoのインスタンスはただ一つだけ生成されます。

そしてgetDaoはFactory method patternです。つまり、パラメータのクラスタイプによりインスタンスを取得します。

しかし我々がDAOのインスタンスを生成するたびにFactoryDaoでifやswitchの分岐文を作成することが大変なので、パラメータのクラスタイプでReflectionを利用してインスタンスを生成するflyweight patternを適用しました。

つまり、改めてまとめたらSingleton + Factory method + flyweightパターンの結果です。


じゃ、SpringのController部分ではなく、一般クラスの関数部分でDAOを取得して使いましょう。

package dao;
 
import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import model.User;
 
// UserデータのDaoクラス、AbstractDaoを継承してジェネリックタイプはUserクラスを設定する。
public class UserDao extends AbstractDao<User> {
  // コンストラクタの再定義、protectedからpublicに変更してパラメータを再設定する。
  private UserDao() {
    // protectedコンストラクタを呼び出す。
    super(User.class);
  }
 
  // Idによるデータを取得
  public User selectById(String id) {
    // AbstractDao抽象クラスのtransaction関数を使う。
    return super.transaction((em) -> {
      // クエリを作成する。(実務ではcreateQueryではなく、createNamedQueryを使ってEntityでクエリを管理する。)
      Query query = em.createQuery("select u from User u where u.id = :id");
      // パラメータ設定
      query.setParameter("id", id);
      try {
        // 結果リターン
        return (User) query.getSingleResult();
      } catch (NoResultException e) {
        // データがなしでエラーが発生するとnullでリターン
        return null;
      }
    });
  }
}
package common;

import dao.FactoryDao;
import dao.UserDao;
import model.User;
// 一般クラス
public class Common {
  // idのパラメータを受け取ってUserの名前値をリターンする関数
  public String getUserNameById(String id) {
    // FactoryDaoでUserDaoのインスタンスを取得する。
    UserDao userdao = FactoryDao.getDao(UserDao.class);
    // UserDaoクラスのselectById関数を利用してUser Entityを取得する。
    User user = userdao.selectById(id);
    // 名前の値をリターンする。
    return user.getName();
  }
}

じゃ、ControllerでCommonクラスのgetUserNameById関数を利用しましょう。

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import common.Common;

@Controller
public class Home {
  // 要請urlパターン
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // Commonクラスのインスタンスを生成
    Common common = new Common();
    // modelmapにCommonクラスのgetUserNameById関数を利用してNameを取得する。
    modelmap.addAttribute("Data", common.getUserNameById("nowonbun"));
    // viewのファイル名
    return "index";
  }
}



上の結果をみればCommonクラス中でUserDaoを利用してデータベースに接続してデータを取得して画面に表示することが確認できます。

ここまでSpringフレームワークの一般クラスでDAOインスタンスをFactory patternを利用して取得することが確認できました。


これからFactoryDaoにあるDAOインスタンスを@Autowiredを通ってControllerで依存性注入してインスタンスを取得しなければならないです。

以前には我々がbeanをmvc-config.xmlで登録しましたが、ここにはxmlで登録することではなく、クラスで設定します。


package controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import dao.FactoryDao;
import dao.UserDao;

// 設定アトリビュート
@Configuration
public class ApplicationConfig {
  // Bean設定、idはUserDao
  @Bean(name = "UserDao")
  public UserDao getUserDao() {
    // FactoryDaoのUserDaoのインスタンスを取得する。
    return FactoryDao.getDao(UserDao.class);
  }
}

上のApplicationConfigクラスはxmlで設定されたControllerパッケージに作成します。


そして@Configurationアトリビュートを設定してxmlで使ったbean-idをBeanアトリビュートで設定しましょう。


じゃ、またControllerで依存性注入でDAOを取得しましょう。

package controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import dao.FactoryDao;
import dao.UserDao;

@Controller
public class Home {
  // 依存性注入
  @Autowired
  // ApplicationConfigクラスで設定したbean-id
  @Qualifier("UserDao")
  private UserDao userdao;
  
  // 要請urlパターン
  @RequestMapping(value = "/index.html")
  public String index(ModelMap modelmap, HttpSession session, HttpServletRequest req, HttpServletResponse res) {
    // 依存性注入で受け取ったUserDaoインスタンスのメモリアドレス
    System.out.println(userdao);
    // FactoryDaoで受け取ったUserDaoインスタンスのメモリアドレス
    System.out.println(FactoryDao.getDao(UserDao.class));
    
    // 依存性注入で受け取ったUserDaoのインスタンスでデータ取得
    modelmap.addAttribute("Data", userdao.selectById("nowonbun").getName());
    // viewのファイル名
    return "index";
  }
}


依存性注入でもUserDaoを利用してもデータベースの値を正しく取得します。


コンソール出力でFactoryDaoから取得したUserDaoインスタンスと依存性注入から取得したUserDaoインスタンスのメモリアドレスが同じです。

つまり、同じインスタンスだという意味です。


このように作成すればFactoryDaoを通ってSpringの依存性注入したDAOと一般クラスで使ったDAOは一つで統一が可能です。

そして、実はここで一つの作業をもっと進めなければならないです。それはDAOのコンストラクタのアクセス修飾子のタイプをすべてprivateに修正しなければならないです。


FactoryDaoクラスをみればアクセス修飾子と関係せずにインスタンスを生成するライン(constructor.setAccessible(true))があります。

つまり、DAOクラスのコンストラクタをprivateに設定してもFactoryDaoには問題なく、インスタンス生成が可能という意味です。

そしてDAOのコンストラクタのアクセス修飾子をprivateに設定すれば他のクラスでDAOのインスタンスを生成(new)することができなくなります。

ここまでするとプロジェクト開始準備が完了します。


ここまでSpringフレームワークでDAOをFactory method Patternを利用して依存性注入する方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿