Skip to content

Spring

基本概念

Spring は、オープンソースで軽量な Java エンタープライズアプリケーション開発フレームワークです。

  • 制御の反転(IoC):オブジェクトの作成や依存関係の管理を、プログラムコードから外部コンテナへ移す設計思想です。コンテナがオブジェクトの作成、ライフサイクル管理、依存関係の管理を行います。
  • 依存性注入(DI):IoC の実装方式の一つです。オブジェクト作成時、または実行時に依存オブジェクトを対象オブジェクトへ注入します。対象オブジェクトが自分で依存オブジェクトを作成、検索しなくてよくなり、オブジェクト間の結合を下げられます。
  • アスペクト指向プログラミング(AOP):ログ、トランザクション管理、セキュリティチェックなど、業務ロジックとは直接関係しない横断的な処理を分離し、切面として管理する考え方です。コードの再利用性と保守性を高めます。
  • Spring コンテナ:Spring フレームワークの中核です。アプリケーション内のオブジェクトを管理、設定します。主に BeanFactoryApplicationContext があります。
  • Bean:Spring コンテナが管理するオブジェクトです。設定ファイルやアノテーションで普通の Java オブジェクトを Bean として定義できます。

役割

  • 疎結合化:IoC と DI により、コンポーネント間の依存関係をゆるくし、保守性と拡張性を高めます。
  • コード再利用性の向上:AOP により、横断的な処理を切面として再利用できます。
  • 開発フローの簡略化:データアクセス、トランザクション管理、Web 開発などの機能を提供し、エンタープライズ開発を簡単にします。
  • 設定管理のしやすさ:設定ファイルやアノテーションで Bean を柔軟に設定できます。

使用場面

  • エンタープライズアプリケーション開発:EC、ERP、CRM など、大規模で保守性と拡張性が必要なシステムに向いています。
  • Web アプリケーション開発:Spring MVC などを使い、Web アプリケーションを効率よく構築できます。
  • マイクロサービス:Spring Boot などと組み合わせ、独立してデプロイできるサービスを作れます。
  • ビッグデータとクラウド:Hadoop、Spark などとの連携や、クラウド環境での実行にも使われます。

メリット

  • 開発効率が上がる:多くの機能と便利な開発方式により、重複コードを減らせます。
  • コード品質が上がる:設計思想により、構造が分かりやすく、保守しやすいコードを書きやすくなります。
  • クロスプラットフォーム性と互換性が高い:多くの OS、アプリケーションサーバー、外部フレームワークと組み合わせられます。
  • コミュニティとエコシステムが強い:文書、チュートリアル、OSS ライブラリが豊富です。

IoC コンテナ

まず、プロジェクト構造を確認します。

java
//service と dao の二つのパッケージがあり、それぞれインターフェースと実装クラスを持つ。Service は Dao に依存する。
public interface BookService{
	public void save();
}
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    public void save(){
        System.out.println("BookService Run !");
        bookDao.save();
    }
}

public interface BookDao{
	public void save();
}
public class BookDaoImpl implements BookDao {
	public void save(){
		System.out.println("BookDao Run !");
	}
}

ここでは、従来の XML 設定方式を使って依存を IoC コンテナへ入れます。

Step 1:Spring 座標を導入する

Spring を使うため、まず Maven 座標を導入します。

xml
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.2.10.RELEASE</version>
</dependency>

Step 2:設定ファイルを作成する

Spring 設定ファイルを作り、対応するクラスを Spring 管理の Bean として設定します。
resources 配下に applicationContext.xml を作成します。

xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
</beans>

Step 3:Bean を設定する

上の設定ファイル内に、Spring が管理する Bean を設定します。

xml
<!--beanタグでBeanを設定する
	id属性はbeanの名前
	class属性はbeanの型
-->
<!--実装クラスを設定する必要がある-->
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl"/>

設定項目:

  • id 属性:Bean の一意な識別子です。Spring コンテナ内で重複できません。他の場所でこの Bean を参照するときに使います。
  • class 属性:インスタンス化するクラスの完全修飾名です。Spring はリフレクションでこのクラスのインスタンスを作成し、Bean として管理します。

Step 4:IoC コンテナを取得する

起動クラスで IoC コンテナを取得します。

java
public class App {
    public static void main(String[] args){
        //IoCコンテナを取得する
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

Step 5:Bean を取得する

java
public class App {
    public static void main(String[] args){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Object bean = ctx.getBean("bookDao");
        BookDao bookDao = (BookDao)bean;
        bookDao.save();
    }
}

これで、従来の XML 方式でプロジェクト内の依存を Spring の IoC コンテナに入れられました。依存関係が明確になり、構造と保守性が上がります。

DI

依存性注入は、Java アプリケーション開発で重要な設計思想です。コード間の結合度を下げ、各モジュール間の依存関係を分かりやすくします。DI を使うと、オブジェクト内部で依存オブジェクトを new する必要がなくなり、外部コンテナが対象オブジェクトへ依存を注入します。

new を削除する

自分で new してオブジェクトを作る必要がないため、業務コード内の new を削除し、setter メソッドを用意します。

java
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    
    public void save(){
        System.out.println("BookService Run !");
        bookDao.save();
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

設定ファイルを変更する

Spring に、Dao を Service に入れる必要があることを伝えます。

xml
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <!-- ServiceとDaoの関係を設定する -->
    <property name="bookDao" ref="bookDao"/>
</bean>

注意:

  • property タグ:Bean の属性値を設定します。ここでは BookServiceImplbookDao 属性を設定します。
  • name 属性:設定する属性名です。クラス内の属性名と一致する必要があります。
  • ref 属性:注入する別の Bean の参照です。ここでは bookDao という id の Bean を注入します。

在这里插入图片描述

この時点で main メソッドを実行すると、Dao が Service に正しく注入されていることを確認できます。

依存性注入

四つの注入方式

前では Dao を Service に注入しました。new ではなく setter メソッドを使いました。
クラスへデータを渡す方式は主に二つです。

  • 通常メソッド(set)
  • コンストラクタ

依存性注入では、コンテナ内で Bean と Bean の依存関係を作ります。渡すデータは二種類です。

  • 参照型
  • 単純型(基本データ型と String)

そのため、依存性注入は次の四種類に分けられます。

  • setter 注入
    • 単純型
    • 参照型
  • コンストラクタ注入
    • 単純型
    • 参照型

setter 注入 - 参照型

Bean に set メソッドを定義し、設定ファイルで property タグを追加して参照型を注入します。

java
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    
    public void save(){
        System.out.println("BookService Run !");
        bookDao.save();
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
xml
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
</bean>

setter 注入 - 単純型

java
public class BookServiceImpl {
    private int connectionNum;
    private String databaseName;

    public void setConnectionNum(int connectionNum) {
        this.connectionNum = connectionNum;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }
}
xml
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <property name="connectionNum" value="10"/>
    <property name="databaseName" value="mysql"/>
</bean>

ref は別の Bean を参照します。具体的な値を設定する場合は value を使います。

コンストラクタ注入 - 参照型

コンストラクタ注入では set メソッドは不要です。コンストラクタを用意します。

java
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    
    public BookServiceImpl(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    public void save(){
        System.out.println("BookService Run !");
        bookDao.save();
    }
}
xml
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>
</bean>

constructor-arg は Bean のコンストラクタへ引数を渡すために使います。name はコンストラクタ引数名、ref は注入する Bean の参照です。

コンストラクタ注入 - 単純型

java
public class BookServiceImpl {
    private int connectionNum;
    private String databaseName;

    public BookServiceImpl(int connectionNum, String databaseName) {
        this.connectionNum = connectionNum;
        this.databaseName = databaseName;
    }
}
xml
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <property name="connectionNum" value="10"/>
    <property name="databaseName" value="mysql"/>
</bean>

依存性注入方式の選択

  1. 必須依存はコンストラクタ注入を使います。setter 注入では注入漏れにより null が発生する可能性があります。
  2. 任意依存は setter 注入を使います。柔軟性が高いです。
  3. Spring はコンストラクタ注入を推奨しています。第三者フレームワーク内部でも、データ初期化にコンストラクタ注入が多く使われます。
  4. 必要であれば両方を使えます。必須依存はコンストラクタ、任意依存は setter で注入します。
  5. 管理対象オブジェクトに setter がない場合は、コンストラクタ注入を使う必要があります。
  6. 自分で開発するモジュールでは setter 注入もよく使われます。

自動装配

IoC コンテナが Bean の依存リソースをコンテナ内から自動で探し、Bean に注入する処理を自動装配と呼びます。

自動装配方式:

  • 型による装配(主に使う)
  • 名前による装配
  • コンストラクタによる装配
java
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    
    public void save(){
        System.out.println("BookService Run !");
        bookDao.save();
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
xml
<bean id="bookDao" class="com.angelday.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl" autowire="byType">
</bean>

特徴:

  • 自動装配は参照型の依存注入に使います。単純型には使えません。
  • byType を使う場合、同じ型の Bean がコンテナ内で一つだけである必要があります。
  • byName を使う場合、指定名の Bean がコンテナ内に存在する必要があります。変数名と設定が結合するため、あまり推奨されません。
  • 自動装配の優先度は setter 注入やコンストラクタ注入より低いです。同時に存在する場合、自動装配は無効になります。

集合注入

主に設定形式を理解します。

java
public class BookServiceImpl {
    private int[] array;
    private List<String> list;
    private Set<String> set;
    private Map<String,String> map;
    private Properties properties;

    public void setArray(int[] array) { this.array = array; }
    public void setList(List<String> list) { this.list = list; }
    public void setSet(Set<String> set) { this.set = set; }
    public void setMap(Map<String, String> map) { this.map = map; }
    public void setProperties(Properties properties) { this.properties = properties; }
}
xml
<bean id="bookService" class="com.angelday.service.impl.BookServiceImpl">
    <property name="array">
        <array>
            <value>100</value>
            <value>200</value>
            <value>300</value>
        </array>
    </property>
    <property name="list">
        <list>
            <value>angel</value>
            <value>day</value>
            <value>hello</value>
        </list>
    </property>
    <property name="set">
        <set>
            <value>angel</value>
            <value>day</value>
            <value>hello</value>
        </set>
    </property>
    <property name="map">
        <map>
            <entry key="country" value="china"/>
            <entry key="province" value="hebei"/>
            <entry key="city" value="handan"/>
        </map>
    </property>
    <property name="properties">
        <props>
            <prop key="country">china</prop>
            <prop key="province">hebei</prop>
            <prop key="city">handan</prop>
        </props>
    </property>
</bean>

第三者 Bean の注入

Druid を例にします。

Step 1:座標を導入する

xml
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

Step 2:設定ファイルを書く

xml
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

センシティブ情報の処理

アカウントやパスワードを XML に直接書くのは適切ではありません。通常は properties 設定ファイルに書きます。

  1. context 名前空間を有効にする。
  2. context 名前空間で指定した properties ファイルを読み込む。
xml
<context:property-placeholder location="classpath:*.properties"/>
  1. ${} で読み込んだ値を使う。
xml
<property name="username" value="${jdbc.username}"/>

まとめ

コンテナ取得方式

java
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");

Bean の取得方式

java
BookDao bookDao = (BookDao)ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
BookDao bookDao = ctx.getBean(BookDao.class);

Bean 関連

xml
<bean
	id="bookDao"								beanのid
	name="dao bookDaoImpl daoImpl"			    beanの別名
	class="com.Angelday.dao.impl.BookDaoImpl"	beanの型、静的ファクトリクラス、FactoryBeanクラス
	scope="singleton"							beanのインスタンス数を制御する
	init-method="init"							ライフサイクル初期化メソッド
	destroy-method="destory"					ライフサイクル破棄メソッド
	autowire="byType"							自動装配タイプ
	factory-method="getInstance"				beanファクトリメソッド
	factory-bean="com.itheima.factory.BookDaoFactory"インスタンスファクトリ
	lazy-init="true"							beanの遅延ロードを制御する
/>

純粋なアノテーション開発

導入

以前は Spring コンテナが管理する Bean を宣言するとき、XML に書く必要がありました。

xml
<bean id="userDao" class="com.angelday.dao.UserDao">

アノテーション開発では、Bean として宣言したいクラスに @Component() を付け、括弧内に Bean の id を書くだけです。

java
@Component("id")
public class UserDao{
	public void save(){
		System.out.println("userDao-->run!");
	}
}

ただし、これだけでは Spring コンテナは直接認識できません。Spring にどこをスキャンするかを伝える設定クラスを作成します。

java
@Configuration
@ComponentScan("com.angelday")
public class SpringConfig{
}

設定クラスを作成した後、XML 設定ファイルを削除できます。コンテナ取得時も、設定ファイルではなく設定クラスを読み込みます。

java
public class App{
	public static void main(){
		ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
		UserDao userDao = (UserDao)ctx.getBean("userDao");
		userDao.save();
	}
}

設定ファイルとの対応

  • xml 設定ファイルSpringConfig クラス に置き換わる
  • XML 形式は @Configuration に置き換わる
  • <bean> タグは @Component に置き換わる
  • id 属性は @Component("id") に書く
  • class 属性は @ComponentScan("com.angelday") で指定したスキャン範囲に置き換わる
  • ClassPathXmlApplicationContextAnnotationConfigApplicationContext に置き換わる

Spring は @Component の派生アノテーションも提供しています。

アノテーション説明
@Controller表示層 Bean 定義
@Service業務層 Bean 定義
@Repositoryデータ層 Bean 定義

自動装配

装配したい属性の前に @Autowired を付けるだけです。XML 開発で使っていた setter メソッドは省略できます。

java
public class BookService{
    @Autowired
	private BookDao bookDao;

    public void save(){
        bookDao.save();
    }
}

型による自動装配:@Autowired
名前による自動装配:@Qualifier("bean's id")

注意:@Qualifier@Autowired と一緒に使う必要があります。

単純型の注入では、属性に直接 @Value() を使います。

java
@Repository("userDao")
public class UserDao{
	@Value("Zhangsan")
	private String name;
}

外部 Properties を注入する場合は、設定クラスに @PropertySource() を追加します。

java
@Configuration
@ComponentScan("com.angelday")
@PropertySource("classpath:Xxx.properties")
public class SpringConfig{
}

第三者 Bean の注入

Druid を例にします。

java
public class JdbcConfig{
	@Bean
	public DataSource dataSource(){
		DruidDataSource dds = new DruidDataSource();
		ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/db_name");
		ds.setUsername("root");
		ds.setPassword("123456");
		return dds;
	}
}

設定クラスで @Import を使って導入します。

java
@Configuration
@Improt(JdbcConfig.class)
public class SpringConfig{
}

センシティブ情報は @Value で外部から注入できます。

java
public class JdbcConfig{
	@Value("com.mysql.jdbc.Driver")
	private String driver;
	@Value("jdbc:mysql://localhost:3306/db_name")
	private String url;
	@Value("root")
	private String userName;
	@Value("123456")
	private String password;

	@Bean
	public DataSource dataSource(){
		DruidDataSource dds = new DruidDataSource();
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(userName);
		ds.setPassword(password);
		return dds;
	}
}

参照型は、Bean 定義メソッドの仮引数として直接追加できます。Spring が型に基づいて自動装配します。

java
public class JdbcConfig{
	@Bean
	public DataSource dataSource(BookDao bookDao){
        System.out.println(bookDao);
		DruidDataSource dds = new DruidDataSource();
		return dds;
	}
}

对比

AOP

AOP(Aspect Oriented Programming)はアスペクト指向プログラミングです。プログラム構造をどう組み立てるかを示す考え方です。
OOP(Object Oriented Programming)はオブジェクト指向プログラミングです。

役割:元の設計を変えずに機能を強化します。
Spring の考え方:非侵入式です。

核心概念

  • 接続点(JoinPoint):プログラム実行中の任意の位置です。Spring AOP では、通常はメソッド実行を指します。
  • 切入点(Pointcut):接続点をマッチさせる式やルールです。具体的な一つのメソッドも、複数メソッドのパターンも指定できます。
  • 通知(Advice):切入点で実行される処理です。共通機能とも呼べます。前置通知、後置通知、環绕通知などがあります。
  • 通知クラス:通知を定義するクラスです。一つまたは複数の通知メソッドを持ちます。
  • 切面(Aspect):通知と切入点の対応関係を表します。たとえばログ記録の切面では、複数の業務メソッドにログ処理を適用できます。

Spring トランザクション

Spring では、AOP の仕組みを使ってトランザクション管理を簡単に実現できます。業務メソッドに対して、開始、コミット、ロールバックなどの処理を共通機能として追加できます。

Released under the MIT License.