Spring FrameworkのDI(Dependency Injection)

Spring Frameworkの理解が進まない今日この頃ですが、今回はSpring FrameworkのなかのDI(Dependency Injection)についてです。

その前に、前回、『MySampleWebApp』というプロジェクトをSTSで作成しました。

f:id:ts0818:20160806174541p:plain

今回、javaファイルをまだ作成していないのでjavaのプログラムを実行する

public static void main(String[] args){ }

がないのに、『Pivotal tc Server Developer Edition v3.1』というサーバを起動し、『http://localhost:8080/MySampleWebApp』にアクセスしたところ、javaプログラムが実行され、index.jspが表示できていました。

これは、Pivotal tc Server Developer Edition v3.1』というサーバがアプリケーションサーバとWebサーバの両方の機能を兼ね備え、そのなかのServletコンテナクラスで『public static void main(String[] args){ }』が実行されているため、index.jspの中のjavaプログラムの部分が実行できているのではないかと思います。 

MVCアーキテクチャにおけるJSP, Java Servlet, JavaBeansの位置づけの図

f:id:ts0818:20160806184639p:plain

JSP(JavaServer Pages)

JavaServer Pages (JSP) は、HTML内にJavaのコードを埋め込んでおき、Webサーバで動的にWebページを生成してクライアントに返す技術。

HTMLの中でデザイン部分とプログラム部分を分けて書くためにある程度までウェブデザイナの負担を減らすこともできる。また、静的な出力が多い場合に適している。類似技術としてPHPASPASP.NETなどがある。 

クライアントからのJSPの実行がリクエストされると、アプリケーションサーバサーブレットコンテナJSPソースファイルをサーブレットソースコードに変換する。そしてさらにそのソースコードをその場でコンパイルして実行し、結果をクライアントに返信する。このため、最初はコンパイルの時間がかかるが、いちどコンパイルが実行されると2回目以降は必要なくなるため、結果としてアクセス速度が早くなる。 

Java Servletを自動生成して動作している。

JavaServer Pages - Wikipedia

Java Servlet

サーバ上でウェブページなどを動的に生成したりデータ処理を行うために、Javaで作成されたプログラム及びその仕様である。単にサーブレットと呼ばれることが多い。Java EEの一機能という位置づけになっている。 

Java Servletはサーバサイド技術として登場した。

同様の技術(すなわち対抗技術)としてはPerlなどを用いたCGIPHPプログラムのプロセスをApache HTTP Server上で動かすことができるmod_phpなどのモジュール、マイクロソフトが提供するASPなどがある。

CGIクライアントのリクエストのたびに新しいプロセスを起動するのに対して、サーブレットメモリに常駐して、リクエストのたびにプロセスより軽量なスレッドを起動するので、効率がよい。

また、サーブレットJavaで書かれているのでさまざまなプラットフォームで使うことができる。

当初、JavaAppletなどのクライアント側でJavaプログラムを稼動させるクライアントサイドの技術として注目を集めていた。

しかし、サーブレットの登場以降、サーバ側でJavaプログラムを稼動させる形態が急速に普及した。こうしたサーバ側でJavaプログラムを稼動させる形態をサーバサイドJavaと呼ぶ。 

Java Servlet - Wikipedia

 

Javaは最初からサーバーサイド言語ってわけではなかったっていうのは衝撃でした。

ServletJavaプログラムを実行しているようです。 

DI(Dependency Injection)とは?

だいぶ話が脱線しましたが、Spring Frameworkでは、DIがかなり重要みたいです。

じゃあ、DIってなんなんすか?ってことですが、日本語で『依存性の注入』というらしいです。 

依存性の注入(DI: Dependency injection)

コンポーネント間の依存関係をプログラムソースコードから排除し、外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。 

コンポーネント間の関係はインタフェースを用いて記述し、具体的なコンポーネントを指定しない。 

プログラムに依存性を注入する方法(DIの種類)としては、以下のような手法が存在する。

  • インタフェース注入
  • setter 注入
    • setter メソッドを定義して注入を行う方法
  • コンストラクタ注入

Dependency injectionという用語を作成したのはソフトウェア開発者のマーティン・ファウラーである。

類似の概念としてそれ以前から制御の反転 (IoC) と呼ばれるアイデアが存在していたが、それを整理・範囲を限定することでDIが生み出された。

現在では代表的なDIコンテナとして知られるSpring Frameworkも、誕生当初はDIではなくIoCという表現を用いていた。 

依存性の注入 - Wikipedia

流石はWikipediaさん...、まったく分からんっす! 

『Spring Framework4 (著:掌田津耶乃)』の説明によると、DI(依存性注入)は、「オブジェクトA」と「オブジェクトB」というコンポーネントで「固有の設定情報」を外部ファイル化するってことみたいです。

DI(Dependency Injection)のイメージ図

f:id:ts0818:20160807115946p:plain

 DIコンテナ(IoCコンテナ)のイメージ図

f:id:ts0818:20160807141821p:plain

Spring Frameworkでは、DIコンテナ(IoCコンテナ)として実装されたクラス

  • ApplicationContextクラス
  • BeanFactoryクラス

などを利用してコンポーネント側の機能を呼び出していくようです。

SpringでDIコンテナを使うことの特徴 

⇩  詳しくは下記サイトへ 

SpringのDIとAOPの簡潔な説明 - Qiita

 

クラスのオブジェクト(インスタンス化)を毎回作っていると、メモリの消費などが膨大になって、結果的に処理が遅くなってしまうのを、DIコンテナ利用で回避できるようです、たぶん。 

SpringでDIする方法

2パターンあるようです。 

現在は、アノテーションを使う方法が主流のようです。

ですが、前回作った『MySampleWebApp』プロジェクトではBean定義ファイルを使っている(『Spring Framework4 (著:掌田津耶乃)』)ようなので、Bean定義ファイルでDIしていくことにします。 

SpringのWebプロジェクトでBean定義ファイルでDIしてみる

STSを起動します。

f:id:ts0818:20160807150524j:plain

src/main/resources/spring/application-config.xmlというファイルが、SpringでDIで利用する情報をまとめた「Bean定義ファイル」のようです。 

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- Uncomment and add your base-package here:
         <context:component-scan
            base-package="org.springframework.samples.service"/>  -->

</beans>

ここに、Beanクラス(javaファイル)を管理するための記述をしていくようです。 

Beanクラスを作成

『MySampleWebApp』プロジェクトのsrc/main/javaに、MyBean.javaというクラスを作成します。

『ファイル』>『新規』>『クラス』を選択します。

f:id:ts0818:20160807152420j:plain

 『パッケージ名(K):』と『名前(M):』を適当につけ、『完了(F)』を選択します。 

f:id:ts0818:20160807202449j:plain

Beanクラス(MyBean.java)が作成されました。 

package jp.suzuki.spring.websample;

public class MyBean {

}

作成したBeanクラスに、いろいろ足していきます。 

package jp.suzuki.spring.websample;

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

public class MyBean {
  private List messages = new ArrayList();

  public MyBean() {
    super();
    messages.add("This is Bean sample.");
  }

  public void addMessage(String message) {
    messages.add(message);
  }

  public String getMessage(int n) {
    return messages.get(n);
  }

  public void setMessage(int n, String message) {
    messages.set(n,message);
  }

  public List getMessage() {
    return messages;
  }

  public void setMessages(List messages) {
    this.messages = messages;
  }

  @Override
  public String toString() {
    String result = "MyBean [\n";

    for(String message : messages) {
      result += "\t" + message + "\n";
    }
    result += "]";
    return result;
  }

}

application-config.xmlに追記

Beanクラス(MyBean.java)の情報をBean定義(application-config.xml)ファイルに記述していきます。

  <bean id="mybean1" class="jp.suzuki.spring.websample.MyBean" />

の一文を追加します。

※classは、作成したパッケージ名とBeanクラス名を『.』でつなげたものを指定。idは、後ほど作成するMySampleServlet.javaの中のgetBean()の引数で使います。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!-- Uncomment and add your base-package here:
         <context:component-scan
            base-package="org.springframework.samples.service"/>  -->
  <bean id="mybean1" class="jp.suzuki.spring.websample.MyBean" />
</beans>

Servletクラスを作成

Beanクラス(MyBean.java)を処理するためのServletクラスを作成していきます。

『MySampleWebApp』プロジェクト内の『src/main/java』が選択された状態で、『ファイル』>『新規』>『その他』を選択します。

f:id:ts0818:20160807222133j:plain

『Web』>『サーブレット』を選択し、『次へ』をクリックします。

f:id:ts0818:20160807222134j:plain

Javaパッケージ名』『クラス名』などを適当につけ、『次へ』をクリックします。

f:id:ts0818:20160807222135j:plain

『URLマッピング』で『/MySampleServlet』を選択した状態で『編集』をクリックします。

f:id:ts0818:20160807222136j:plain

『URLマッピング』の『パターン』に『/sample』と入力し、『OK』をクリックします。

f:id:ts0818:20160807222137j:plain

『次へ』をクリックします。

f:id:ts0818:20160807222138j:plain

『継承された抽象メソッド』『init』『doGet』『doPost』にチェックが入った状態で、『完了』をクリックします。

f:id:ts0818:20160807222139j:plain

MySampleServlet.javaクラスが作成されました。

package jp.suzuki.spring.websample;

import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MySampleServlet
 */
public class MySampleServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see Servlet#init(ServletConfig)
	 */
	public void init(ServletConfig config) throws ServletException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

MySampleServlet.javaを編集していきます。

package jp.suzuki.spring.websample;

import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Servlet implementation class MySampleServlet
 */
public class MySampleServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    ApplicationContext app;

	/**
	 * @see Servlet#init(ServletConfig)
	 */
  @Override
	public void init() throws ServletException {
		super.init();
        app = new ClassPathXmlApplicationContext("/spring/application-config.xml");
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		MyBean mybean1 = (MyBean)app.getBean("mybean1");
		request.setAttribute("mybean",mybean1);
        request.getRequestDispatcher("/index.jsp").forward(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String message = request.getParameter("message");
        MyBean mybean1 = (MyBean)app.getBean("mybean1");
        mybean1.addMessage(message);
        response.sendRedirect("sample");
	}

}

 

web.xmlを確認

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>MySampleWebApp</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/application-config.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/mvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet>
    <description></description>
    <display-name>MySampleServlet</display-name>
    <servlet-name>MySampleServlet</servlet-name>
    <servlet-class>jp.suzuki.spring.websample.MySampleServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MySampleServlet</servlet-name>
    <url-pattern>/sample</url-pattern>
  </servlet-mapping>
</web-app>

STSサーブレット作成機能を利用して、Servletクラスを作成すると作成したServletクラスの情報が自動でweb.xmlに記述されます。

index.jspを変更

<!DOCTYPE html>

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>

<html>
	<head>
		<meta charset="utf-8">
		<title>Welcome</title>
	</head>
	<body>
		<h1>Sample Page</h1>
		<pre>${mybean}</pre>
		<hr>
		<form>
		  <input type="text" id="message" name="message"/>
		  <input type="submit" value="add" />
		</form>
	</body>
</html>

 

サーバーを起動してアクセスしてみる

『Pivotal tc Server Developer Edition v3.1』を起動し、ブラウザで『http://localhost:8080/MySampleWebApp/sample』にアクセスすると変更したindex.jspの内容が表示されました。

f:id:ts0818:20160808001642j:plain

 

広告を非表示にする