Featured image of post OOPでのプログラムの設計の基本(DI、AOP)

OOPでのプログラムの設計の基本(DI、AOP)

目次

前提

  • Spring BootのPJの管理をするにあたって改めてOOP、DI、AOPなどを整理した
  • DB設計や関数型プログラミングなどのパラダイムにおける設計は省く
  • また、DDDでの設計やGoFなどのデザインパターン、UMLでの要件定義などもここでは省く

モジュール論

モジュールの定義

「複合/構造化設計」では以下のようにモジュールが定義されている。

  1. 閉じたサブルーチンであること
  2. プログラム内の他のどんなモジュールからも呼び出すことができること
  3. 独立してコンパイルできる可能性をもっていること

モジュールの独立性を高める方法

モジュールの独立性を高めるには次の2つの観点が重要になる。

  1. 各モジュール内の関連性を最大にすること => 凝集度を上げる
  2. モジュール間の関連性を最小にすること => 結合度を下げる

凝集度(Cohesion)

凝集度とは

  • 凝集度とは、単一モジュール内の要素間の関連性についての一つの尺度
  • プログラムの構造の"良さ"あるいは"悪さ"を評価する尺度

凝集形態

凝集度は、望ましいものから順に、以下のように分類される。

  1. 機能的凝集度(Functional Cohesion)
    • 1つの固有の機能を実行するモジュール
  2. 情報的凝集度(Informational Cohesion)
    • 概念、データ構造、資源などを1つのモジュール内に隔離したモジュール
  3. 連絡的凝集度(Communicational Cohesion)
    • データに関連のある複数の機能を逐次的に実行するモジュール
  4. 手順的凝集度(Procedural Cohesion)
    • 仕様によって定められた、関連の少ない複数の機能を逐次的に実行するモジュール
  5. 時間的凝集度(Temporal Cohesion)
    • 関連の少ない幾つかの機能を逐次的に実行するモジュール
  6. 論理的凝集度(Logical Cohesion)
    • 関連したいくつかの機能を持っていて、呼び出しモジュールによって選択され、実行するモジュール
  7. 暗合的凝集度(Coincidental Cohesion)
    • 何を根拠にモジュールになっているのか説明できないような、論外の状態

機能的凝集度と情報的凝集度は同列になる。

結合度(Coupling)

結合度とは

  • 結合度は、主に、モジュール間の関連性についての尺度
  • 結合度を小さく保つために次の2つが大切
    • モジュール間の不要な関連性をなくすこと
    • 必要とされる関連性の強さをできるだけ小さくする

結合形態

結合度は、望ましいものから順に、以下のように分類される。

  1. 非直接結合(Indirect Coupling)
    • モジュールが直接的に互いに依存しないが、他のモジュールを介して間接的に依存しているもの
      • モジュールAとモジュールBがイベントバスを介して通信する
  2. データ結合(Data Coupling)
    • モジュール間のインタフェースデータが単一のデータであるもの
      • モジュールAがモジュールBに単一の数値を渡す
  3. スタンプ結合(Stamp Coupling)
    • 2つのモジュール間でやり取りするデータがグローバルではないもの
      • モジュールAがモジュールBに構造体全体を渡し、その一部だけを使用する
  4. 制御結合(Control Coupling)
    • 1つのモジュールが他のモジュールの論理をはっきりと制御する関連、制御されるモジュール
      • モジュールAがフラグをモジュールBに渡し、そのフラグでモジュールBの処理を制御する
  5. 外部結合(External Coupling)
    • 内容結合でも共通結合でもなく、単一のグローバルデータを参照しているも
      • モジュールAとモジュールBが同じファイルシステムの設定ファイルを参照する
  6. 共通結合(Common Coupling)
    • グローバルな、データ構造を参照するモジュールのグループの間で生じるもの
      • モジュールAとモジュールBがグローバル変数を共有している
  7. 内容結合(Content Coupling)
    • 2つのモジュールで、1つが他の内部を「直接」参照するケース
      • モジュールAがモジュールBの内部データに直接アクセスする

凝集度と結合度の図

図のように、凝集度(Cohesion)が高く、結合度(Coupling)が低いと良い。

凝集度と結合度

OOP, DI, AOPと結合度および凝集度の関係

それぞれの関係は次のようになる。

パラダイム結合度凝集度
OOP- インターフェースやポリモーフィズム- SOLID原則
DI- 依存関係の外部注入- 依存関係の管理を明示的に
AOP- クロスカッティング関心事を分離- モジュールが特定の責務に集中
GoFインターフェースと抽象クラスを利用責務の分離

OOP

OOPの三大要素

いわゆるOOPの三大要素。

ポリフォーリズム

  • ポリモーフィズムとは、多態性とも呼ばれ、同じインターフェースを使用して異なる実装を利用できる能力
  • interfaceやprotocolをimplementsせずに、ダックタイピングすることもある
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def make_animal_speak(animal):
    print(animal.speak())

dog = Dog()
cat = Cat()

make_animal_speak(dog)  # "Woof!"
make_animal_speak(cat)  # "Meow!"

カプセル化

  • カプセル化とは、データ(属性)とそれに関連するメソッド(操作)を1つのオブジェクトとしてまとめること
  • アクセッサー(ゲッター・セッター)やインデクサーを使い、隠蔽する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age

    def get_name(self):
        return self._name

    def set_name(self, name):
        self._name = name

    def get_age(self):
        return self._age

    def set_age(self, age):
        if age > 0:
            self._age = age

person = Person("John", 30)
print(person.get_name())  # "John"
person.set_age(31)
print(person.get_age())  # 31

継承

  • 継承とは、既存のクラス(親クラスまたはスーパークラス)の属性とメソッドを、新しいクラス(子クラスまたはサブクラス)に引き継ぐこと
  • いわゆるis-a関係を表現するために利用する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Vehicle:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model

    def drive(self):
        return "Driving"

class Car(Vehicle):
    def __init__(self, brand, model, num_doors):
        super().__init__(brand, model)
        self.num_doors = num_doors

    def honk(self):
        return "Honking"

car = Car("Toyota", "Corolla", 4)
print(car.drive())  # "Driving"
print(car.honk())  # "Honking"
print(car.brand)  # "Toyota"
print(car.model)  # "Corolla"
print(car.num_doors)  # 4

継承とコンポジションと委譲

  • 明確な「is-a」関係がある場合は継承をする
  • それ以外の「has-a」関係がある時はコンポジションをする
  • 一般的に、継承より委譲を優先する

コンポジション(has-a関係)の例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()  # Car has-a Engine

    def start(self):
        self.engine.start()  # Delegating the start action to the engine

my_car = Car()
my_car.start()

委譲の例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Printer:
    def print_document(self, document):
        print(f"Printing: {document}")

class Office:
    def __init__(self):
        self.printer = Printer()  # Office has-a Printer

    def print_report(self, report):
        self.printer.print_document(report)  # Delegating the print action to the printer

office = Office()
office.print_report("Annual Report")

NTOE:

  • コンポジションと委譲の違いは、意味論的なモノにある
  • 車はエンジンを持っているが、OfficeはPrinterを持っていない
  • 違いは次
    • コンポジット: Car has an Engine.
    • 委譲: Office uses a Printer.

システム設計の原則

SOLID原則

クラス設計は最低限次の原則に従うべき。

単一責任の原則(Single Responsibility Principle, SRP)

  • クラスは、単一の責任を持つべき
  • その責任に対する変更理由は一つだけであるべき

オープン/クローズド原則(Open/Closed Principle, OCP)

  • ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張に対して開かれているべき
  • 変更に対して閉じられているべき

リスコフ置換原則(Liskov Substitution Principle, LSP)

  • 基底クラスのインスタンスは、派生クラスのインスタンスに置き換え可能でなければならない

インターフェース分離の原則(Interface Segregation Principle, ISP)

  • クライアントは、それが使用しないインターフェースに依存してはならない
  • つまり、大きなインターフェースを小さなインターフェースに分割するべき

依存関係逆転の原則(Dependency Inversion Principle, DIP)

  • 高レベルのモジュールは低レベルのモジュールに依存してはならず、両者は抽象に依存すべき
  • 具体的な実装ではなく、抽象に依存する設計を行う
  • importing cycleなどが悪いパターン

分離

関心の分離と責任の分離

クラスを分ける時の分離の基準は以下になる。

  • 関心の分離(Separation of Concerns)
    • ソフトウェアの各部分が異なる機能やロジックに集中することを指す
      • MVC、レイヤードアーキテクチャ
  • 責任の分離(Separation of Responsibilities)
    • システムの各部分が特定の機能やロジックに対して責任を持つようにする設計
      • 単一責任の原則(Single Responsibility Principle, SRP)

DIとAOPが必要な理由

クラスに対して次を担保するため。

  • クリーンにする事
  • 単一責任(SRP: Single Responsibility Principle)にする事
  • 再利用性を高める事
  • 疎結合にする事

共通の機能はAOPで注入し、依存するインスタンスはDIで注入する。

GRASP

GRASPとは

  • Applying UML and Patterns(実践UMLパターン)で紹介された9つのOOPの設計パターン
  • GRASPの意味は、General Responsibility Assignment Software Patterns(汎用責任割り当てソフトウェアパターン)

GRASPの9つのパターン

オブジェクト指向設計において適切な責任の割り当てを行い、保守性、再利用性、拡張性の高いシステムを構築するための9つの指針。

  1. Information Expert(情報エキスパート)
    • 情報を持っているオブジェクトに、その情報に基づく責任を割り当てるという原則
  2. Creator(生成者)
    • あるオブジェクトが他のオブジェクトの生成責任を持つべき場合についてのガイドライン
  3. Controller(コントローラー)
    • システム操作を調整し、利用者からの入力を処理するオブジェクトに責任を割り当てるという原則
  4. Low Coupling(低結合)
    • オブジェクト同士の依存関係を最小限に抑えることで、システムの柔軟性と保守性を向上させるという原則
  5. High Cohesion(高凝集)
    • クラスやモジュールが一貫した責任を持ち、それを中心に機能がまとまっていることを目指す原則
  6. Polymorphism(ポリモーフィズム)
    • 異なるオブジェクトが同じインターフェースを実装することで、多態性を実現し、柔軟性を向上させる原則
  7. Pure Fabrication(純粋な構築)
    • ドメインモデルに直接関連しないが、システムの設計をシンプルにするために導入される人工的なクラスやオブジェクト
  8. Indirection(間接化):
    • 関係性や依存性を間接的に管理することで、柔軟性や再利用性を向上させる原則
  9. Protected Variations(保護された変動)
    • 変動する可能性のある部分を保護し、変動の影響を最小限に抑えるための設計手法

DI

DIの基礎

なぜDIが必要か?

  • 下の例の通り、X1 < X2 < X3 の順で疎結合になっている
  • プログラムの再利用性や変更可能性を上げるために、疎結合に作るのが基本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Dep():
    pass

# フィールド
class X1:
    """プログラムロード時(クラス読み込み時)に初期化する"""
    dep = Dep() 

# コンストラクター
class X2:
    """クラスを利用時に初期化する"""
    dep: Dep
    def __init__(self, dep):
        self.dep = dep

# DI
class X3:
    """DIコンテナを利用する"""
    dep: Dep
    def __init__(self, ctx):
        self.dep = ctx.get_by_name("dep")

DIの利点

  • 疎結合:
    • オブジェクト間の依存関係を外部から注入することで、クラス同士の結合度が低くなり、変更に強くなる
  • テストの容易さ:
    • 依存関係を外部から注入するため、モックオブジェクトを用いたユニットテストが容易になる
  • 再利用性の向上:
    • 同じオブジェクトが異なる文脈で再利用しやすくなる

DISの種類

次の3つの方法がある。

アノテーションによるDI

  • 次のアノテーションをクラスに付与することで、Springのコンテナがそれらのクラスを管理する
    • @Component、@Service、@Repository、@Controllerなど
  • 依存関係を注入するためには、@Autowiredアノテーションをフィールドやコンストラクタに付与する
  • 自動でコンポーネントスキャンされる
    • コンポーネントスキャンとは、Bean定義用のアノテーションが付与されたクラスをDIコンテナに登録すること
    • Bean定義用のアノテーションとは、@Component, @Controller, @Serviceといったアノテーションのこと
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Bean定義
@Service
public class GreetingService {
    public String greet() {
        return "Hello, World!";
    }
}

@RestController
public class GreetingController {
    private final GreetingService greetingService;

    @Autowired
    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.greet();
    }
}

Java ConfigによるDI

  • @Configurationアノテーションを付与したクラス(Java Config)で、@Beanアノテーションを使ってBeanを定義する
  • これにより、明示的にBeanを定義して管理することができる
  • DIコンテナに登録したコンポーネントの事をBean、Configurationの事をBean定義と言う
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class GreetingService {
    public String greet() {
        return "Hello, World!";
    }
}

@Configuration
public class AppConfig {
    
    @Bean
    @Scope("prototype")
    public GreetingService greetingService() {
        return new GreetingService();
    }
}


@RestController
public class GreetingController {
    private final ApplicationContext context;

    // これがBean定義
    @Autowired
    public GreetingController(ApplicationContext context) {
        this.context = context;
    }

    @GetMapping("/greet")
    public String greet() {
        GreetingService greetingService = context.getBean(GreetingService.class); // これがBean
        return greetingService.greet();
    }
}

XMLによるDI

  • 以前はXMLファイルを使用してDIの設定を行う方法が主流だった
  • Spring Bootでは主にアノテーションベースの設定が推奨されている

Beanのスコープ

Beanのスコープは、Beanのライフサイクルと可視性を定義する事。
以下のようなスコープがある。

  • Singleton(シングルトン)
    • グローバルに共有されるオブジェクトに適している
  • Prototype(プロトタイプ)
    • 各Beanのリクエストごとに新しいインスタンスが生成される
  • Request(リクエスト)
  • Session(セッション)
  • Application(アプリケーション)
  • WebSocket(ウェブソケット)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package com.example.demo.service;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

@Service
@Scope("prototype")
public class GreetingService {
    public String greet() {
        return "Hello, World!";
    }
}

NOTE:

  • シングルトンの中のプロトタイプの注意
    • なお、シングルトンBeanにプロトタイプBeanを注入すると、プロトタイプBeanは1回だけインスタンス化される
    • そして、それ以降は同じインスタンスが使用されてしまう
    • そのため、@Lookupメソッドインジェクションを利用する事で、毎回新しいインスタンスを取得でるようになる
  • 当然singletonはスレッドアンセーフなので注意

インジェクション

Spring BootのDIには3つのインジェクションがある。

コンストラクタインジェクション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
public class GreetingController {
    private final GreetingService greetingService;

    @Autowired
    public GreetingController(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.greet();
    }
}

セッターインジェクション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
public class GreetingController {
    private GreetingService greetingService;

    @Autowired
    public void setGreetingService(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.greet();
    }
}

フィールドインジェクション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@RestController
public class GreetingController {
    @Autowired
    private GreetingService greetingService;

    @GetMapping("/greet")
    public String greet() {
        return greetingService.greet();
    }
}

オートワイヤリング

@Autowiredアノテーションは型ベースと名前ベースの2種類で推測する。

例えば、同じ型名が複数あったときに分からなくなる。
その場合は、それぞれ次の方法で解決する。

  • 型ベース
    • @Qualiferアノテーション
    • @Primaryアノテーション
  • 名前ベース
    • @Resourceアノテーション
      • NOTE: Resourceはコンストラクタインジェクションでは利用できない
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service("greetingService1")
public class GreetingService1 implements GreetingService {
    public String greet() {
        return "Hello, World! from GreetingService1";
    }
}

@Service("greetingService2")
public class GreetingService2 implements GreetingService {
    public String greet() {
        return "Hello, World! from GreetingService2";
    }
}

@RestController
public class GreetingController {
    private GreetingService greetingService;

    @Resource(name = "greetingService1")
    public void setGreetingService(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    @GetMapping("/greet")
    public String greet() {
        return greetingService.greet();
    }
}

コンポーネントスキャン

  • Spring Bootは、特定のパッケージやそのサブパッケージ内のBeanを自動的に検出して登録する
  • コンポーネントスキャン(@ComponentScan)を使用する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.example.custom")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • @Component
    • 汎用的なSpring Beanとして定義されるクラスに使用
    • 例: 汎用的なサービスやヘルパークラス
  • @Service
    • サービスレイヤーのBeanを示す
    • 例: ビジネスロジックを実装するクラス
  • @Repository
    • データアクセスオブジェクト(DAO)クラスに使用
    • 例: データベース操作を行うクラス
  • @Controller
    • プレゼンテーションレイヤー、特にWeb MVCのコントローラとして使用
    • 例: HTTPリクエストを処理し、レスポンスを返すクラス
  • @RestController
    • 特にRESTful Webサービスのエンドポイントを定義するコントローラに使用
    • 例: REST APIのエンドポイントを提供するクラス
  • @Configuration
    • JavaベースのSpring設定クラスに使用
    • 例: Bean定義や設定を行うクラス

Beanのライフサイクル

Spring BootのBeanのライフサイクルはJava EE環境でのBeanのライフサイクル(JSR 250)と同じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    @Resource(name = "java:comp/env/jdbc/MyDataSource")
    private DataSource dataSource;

    @PostConstruct
    public void initialize() {
        System.out.println("Initializing MyBean");
        // 初期化処理
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("Cleaning up MyBean");
        // クリーンアップ処理
    }

    public void performService() {
        // サービスのロジック
        System.out.println("Performing service in MyBean");
    }
}

プロファイル管理

@Profileアノテーションの利用してBeanを定義する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

@Component
@Profile("dev")
public class DevService {
    // 開発環境専用のサービス
}

@Component
@Profile("prod")
public class ProdService {
    // 本番環境専用のサービス
}

Bean Validation

  • バリデーション(入力チェック)用のフレームワーク
  • 以下がBean Validationの例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package sample.bean_validation.ejb;

import java.util.Set;
import javax.ejb.Stateless;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import sample.bean_validation.bean.HelloBean;

@Stateless
public class HelloEjb {

    public void hello() {
        // Validator を取得
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        // Bean を作成
        HelloBean bean = new HelloBean();

        // バリデーションを実行
        Set<ConstraintViolation<HelloBean>> result = validator.validate(bean);

        // 結果の確認
        System.out.println("size = " + result.size());
        System.out.println("message = " + result.iterator().next().getMessage());
    }
}
1
2
3
4
5
6
7
8
package sample.bean_validation.bean;

import javax.validation.constraints.NotNull;

public class HelloBean {
    @NotNull
    private String hoge;
}

JSR 330との比較

  • JSR 330アノテーションは、Javaの依存性注入に関する標準仕様で、Springとの対応は次になる
  • Spring BootでもJSR 330の一部のアノテーションは使用可能である
機能Spring アノテーションJSR 330 アノテーション
コンポーネントの定義@Component@Named
依存関係の注入(フィールド/メソッド/コンストラクタ)@Autowired@Inject
依存関係のクオリファイ@Qualifier@Named
ライフサイクルコールバック(初期化)@PostConstruct (JSR 250)@PostConstruct (JSR 250)
ライフサイクルコールバック(破棄)@PreDestroy (JSR 250)@PreDestroy (JSR 250)
スコープの設定@Scope@Singleton
プロバイダの使用@Autowired (もしくは ObjectProvider)Provider

AOP

AOPとは

アスペクト指向プログラミング(AOP: Aspect-Oriented Programming)は横断的関心事を効果的に管理する為のパラダイムの事

横断的関心事

横断的関心事とは

横断的関心事(Cross-cutting concerns)ソフトウェア開発において、アプリケーション全体にわたって共通して発生する機能や要件など。

  • ロギング
  • トランザクション管理
  • エラーハンドリング
  • メトリクスとモニタリング

横断的関心事の分離

  • 関心の分離があるように、横断的関心事の分離もある
  • アプリケーション全体にわたって共通して必要となる機能やロジックを、ビジネスロジックから独立させて管理すること

SpringとAOP

  • SpringはAOPの機能がある
  • 内部ではAspectJのライブラリを利用している

AOPの主要な用語

AOPの概念図

簡潔に表現すると、次のようになる。

  • プログラム実行時、Adviceという処理はさむ
  • はさむ箇所(メソッド)はPointcut式定義する
  • そのメソッドの一つ一つの事をjoint Pointという

AdviceとPointcutとJoint Points

アスペクト(Aspect)

  • 横断的関心事の事
    • 例えば、ログを管理するなど
  • 横断的関心事を定義するモジュール

$$ Aspect = Advice + Pointcut $$

ジョインポイント(Join Point)

  • アプリケーション実行中の特定のポイント
  • 通常はメソッドの呼び出しや実行がジョインポイントになる
  • ポイントカットはjoin pointsの集合の事

Joint Points

アドバイス(Advice)

  • 実際に横断的な機能を実行するコード
  • アドバイスは、特定のジョインポイント(Join Point)に適用される

アドバイスの種類

Srpingには以下の種類がある。

  • Before Advice:
    • 対象メソッドが実行される前に実行される
  • After Advice:
    • 対象メソッドが実行された後に実行される
  • Around Advice:
    • 対象メソッドの前後で実行され、メソッドの実行を制御できる
  • After Returning Advice:
    • 対象メソッドが正常に終了した後に実行される
  • After Throwing Advice:
    • 対象メソッドが例外をスローした後に実行される

ポイントカット(Pointcut)

ポイントカットはJoint Pointsの集合の事。

$$ Pointcuts : Joint Points = 1 : N $$

ポイントカット式

下のような形式に従う。

ポイントカット式

ポイントカット式の例

Method Signature Patterns

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Pointcut("execution(public * *(..))")
public void anyPublicMethod();

@Pointcut("execution(* com.example.app.service.*.*(..))")
public void anyMethodInServicePackage();

@Pointcut("execution(* com.example.app.service..*.*(..))")
public void anyMethodInServicePackageAndSubPackage();

@Pointcut("execution(public Account com.example.app.service.TransferService.*(..))")
public void allPublicMethodOfTransferServiceReturnTypeAccount();

@Pointcut("execution(void com.example.app.service.TransferService.*(Account account, ..))")
public void allMethodOfTransferServiceVoidReturnTypeFirstArgumentAccount();

@Pointcut("execution(public * com.example.app.service.*.tranfer(Account account1, Account account2))")
public void allTranferMethodsInServicePackageWithTwoArgumentsOfAccountType();

Type Signature Patterns

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Pointcut("within(com.example.app.service.*)")
public void allMethodsInServicePackage();

@Pointcut("within(com.example.app.service..*)")
public void allMethodsInServicePackageAndSubPackages();

@Pointcut("within(com.example.app.service.TransferService)")
public void allMethodsOfTransferService();

@Pointcut("within(com.example.app.service.TransferService+)")
public void allMethodsOfTransferServiceImpl();

Bean Name Patterns

1
2
3
4
5
@Pointcut("bean(transferService)")
public void allMethodsOfTransferServiceBean();

@Pointcut("bean(*Service)")
public void allMethodsOfBeanNameAsTransferService();

Combining Pointcut Expressions

1
2
3
4
5
6
7
8
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.example.app.service..*)")
private void inService() {}

@Pointcut("anyPublicOperation() && inService()")
private void serviceOperation() {}

ウィービング(Weaving)

  • アスペクトがアプリケーションコードに統合するプロセスの事
  • コンパイル、クラスロード、実行時などのタイミングがある

ターゲット(Target)

  • アスペクトが適用されるオブジェクトまたはメソッドを指す

まとめ

  • モジュールを分割してdivide and conquerする
  • クラスの設計にはSOLIDの原則に従い設計する
  • クラスを綺麗に保つにはDIやAOPが効果的

参考文献

Built with Hugo
テーマ StackJimmy によって設計されています。