# IoCコンテナ&AOPについて ## IoCコンテナとは ### IoCについて IoCを略さずに言うと Inversion of Controlで、直訳すると「制御の反転」という意味らしい。直訳してもよくわからないことが多すぎてほんと困る 簡単に説明すると、A→Bに依存関係がある時に制御の主導権を持っているのはAであるが、これの主導権をBが握っている状態のこと。 例をだすと、自作プログラムとライブラリの関係は自作プログラムがライブラリを呼び出すため主導権を持っているのは、自作プログラムとなる。(これが普通の考え方) 次に自作プログラムとフレームワークの場合はフレームワークが自作プログラムを呼び出すため主導権はフレームワークとなる。(これがIoCの考え方) なので基本的にはライブラリVSフレームワークとおもっていればいい(どちらが呼び出すかの違い) また一番大事なのはIoCは「プログラミングの手法・原則の1つ」であるということ ### IoCコンテナについて IoCを実現するために、オブジェクトを生成、管理する「フレームワーク側のコンテナ(入れ物)」のこと ### DIについて DIを略さずに言うと、Dependency Injectionという。 よくIoC=DIといわれるがあくまで、IoC(プログラミングの手法)とDI(IoCの中のデザインパターンの1つ)なので完全に一緒というわけではないが、ほぼ一緒のものと考えていい。 ### プログラム #### 今回のプログラム構造 ![](https://i.imgur.com/RFu41mC.png) #### プロジェクトの作成 今回も今までと同様の方法で作成していきます 参考(https://hackmd.io/@ka-777/H1gJjOF__) また、今回導入する依存関係は、Spring Boot DevTools,Lombok,検証,Spring Data JPA,H2 Database,Thymeleaf,Spring Webの7個を使います。 #### アプリケーションクラスの作成 SpringIocAopApplication.java ``` package com.example.demo; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @RequiredArgsConstructor @SpringBootApplication public class SpringIocAopApplication implements CommandLineRunner { private final ApplicationContext appContext; public static void main(String[] args) { SpringApplication.run(SpringIocAopApplication.class, args); } @Override public void run(String... args) throws Exception { String[] allBeanNames = appContext.getBeanDefinitionNames(); for (String beanName: allBeanNames) { log.info("Bean名: {}", beanName); } } } ``` また、IoCコンンテナに管理されるオブジェクトを、Beanと呼び、IoCコンテナはこのBeanの生成、管理をしている。 @Slf4jというアノテーションはLombokのロギング用で、定型コードを自動生成してくれます #### モデルの作成 Coffee.java ``` package com.example.demo.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import lombok.Getter; import lombok.Setter; @Getter @Setter @Entity public class Coffee { @Id @GeneratedValue private Long id; private String name; } ``` ここはDB関係のなので前回のを参照推奨 https://hackmd.io/@ka-777/SkPfjXIYd #### リポジトリの作成 CoffeeRepository.java ``` package com.example.demo.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.example.demo.model.Coffee; public interface CoffeeRepository extends JpaRepository<Coffee, Long> { } ``` #### コントローラの作成 HomeController.java ``` package com.example.demo.controller; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.example.demo.repository.CoffeeRepository; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller public class HomeController { private final ApplicationContext appContext; @GetMapping("/") public String showList(Model model) { CoffeeRepository repository = (CoffeeRepository)appContext.getBean("coffeeRepository"); model.addAttribute("toString", this.toString()); model.addAttribute("allCoffee", repository.findAll()); return "index"; } } ``` > getBean("bean名")でIoCコンテナから指定したBeanを取得することができる > またIoCコンテナに登録されているBean名は小文字から始まるので注意が必要。大文字から書くとエラーになる ScopeController.java ``` package com.example.demo.controller; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.example.demo.repository.CoffeeRepository; import lombok.RequiredArgsConstructor; @Scope("request") @RequiredArgsConstructor @Controller public class ScopeController { private final CoffeeRepository repository; @GetMapping("/scope") public String showList(Model model) { model.addAttribute("toString", this.toString()); model.addAttribute("allCoffee", repository.findAll()); return "index"; } } ``` > @Scope・・・コンポーネントの生成タイミングを制御することが可能 > 今回の@Scope("request")だとHttpのリクエスト毎にBeanの生成がされる > デフォルトだとSingletonが指定されて、1つだけのインスタンスが生成される #### DBにデータを追加 今から作成する2つのsqlファイルはこの名前だとデフォルトのまま読み込むことが可能(resourcesの配下の場合) 1つ目schema.sql ``` CREATE TABLE coffee ( id BIGINT PRIMARY KEY, name VARCHAR(60) ); ``` 2つ目data.sql ``` INSERT INTO coffee(id, name) VALUES (1, 'ブレンドコーヒー'), (2, 'ジャワコーヒー'); ``` 1つ目のSQLでテーブルを作成し、2つ目のSQLでデータを追加しています またここから@Entityのテーブル自動作成機能とschema.sqlでテーブルを作るときの競合を避けるためにapplication.propertiesに以下の内容を付け加えます ``` spring.jpa.hibernate.ddl-auto=none ``` これにより@Entityからではなくschema.sqlからテーブルを作成することができます #### Thymeleaf(HTML)の作成 今回は使うページはindex.htmlのみです ``` <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <span th:text="${toString}"></span> <ol> <th:block th:each="coffee : ${allCoffee}" th:object="${coffee}"> <li th:text="*{name}"></li> </th:block> </ol> </body> </html> ``` #### 実行 ここで記述は終わりなのでいつも通り実行するとSpringtoolsuiteのコンソール画面を開くとBeanがIoCコンテナに登録されているのがわかります ![](https://i.imgur.com/6hMaIkf.png) >Bean名:と書かれたのが大量にあると思います >これらはControllerとRepositoryです また、実行した後はいつも通り(http://localhost:8080/) にアクセスすると実行された画面が見れます ![](https://i.imgur.com/LGS2pAz.png) 今回はこのように表示されますが何度更新をしても一番上にある@以降の値(ハッシュ値)は変わりません なぜならデフォルトのScopeの設定がSingletonなので1つのインスタンスのみを共有しているためです。 また今度は(http://localhost:8080/scope)にアクセスすると何度も更新ボタン(F5)を押すと@以降の値(ハッシュ値)がどんどん変わっていきます これはScopeの設定をrequestにしているためHttpリクエストごとにオブジェクト(ハッシュ値)を作成しているので変わっていきます ## AOPについて Aspect Oriented Programming の略でこれはプログラミング手法の概念の1つ(Javaのオブジェクト指向言語みたいなもの) ### AOPの使い道 クラスを横断(割込み)するような機能で使える 簡単に言うと「処理の注入」みたいなもの 例えばよく出力のチェックで使うprintln文を使いますが、毎回入力したりプログラムが完成したら消したりと手間が多いですがこれが自動的に挿入、削除ができるようになると考えるのが一番簡単です。 ### プログラム #### 今回のプログラム構造 ![](https://i.imgur.com/Me7bNlA.png) 上のIoCコンテナで作成したプログラムをコピーしてください そこに付け足すと簡単にできます #### Spring AOPの追加 プログラム直下にあるpom.xmlに追加していきます ``` <!-- dependenciesタグ内に、以下を追加します --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 次に今回はAOPを使ってログを表示させるのでその処理を記述していきます LoggingAspect.java ``` package com.example.demo.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; @Slf4j @Aspect @Component public class LoggingAspect { @Before("execution(String com.example.demo.controller.HomeController.showList(..))") public void startLog(JoinPoint jp) { log.info("{}: Before処理", jp.getSignature()); } @After("execution(* com.example.demo.controller.HomeController.*(..))") public void endLog(JoinPoint jp) { log.info("{}: After処理", jp.getSignature()); } @Around("execution(* com.example.demo..*(..))") public Object startAndEndLog(ProceedingJoinPoint pjp) throws Throwable { log.info("{}: Around前処理", pjp.getSignature()); Object result = pjp.proceed(); log.info("{}: Around後処理", pjp.getSignature()); return result; } @AfterReturning( pointcut = "within(com.example.demo.controller.*Controller)", returning = "result") public void afterReturning(JoinPoint jp, Object result) { log.info("{}: return結果 = {}", jp.getSignature(), result); } @AfterThrowing( pointcut = "bean(homeController)", throwing = "e") public void afterThrowing(JoinPoint jp, Throwable e) { log.error("{}: 処理中に例外が発生しました: {}", jp.getSignature(), e.getMessage()); } } ``` > @Aspect・・・このクラスが、アスペクト(横断的な処理とそれを実行する場所を定義したモジュール)であることを宣言するアノテーション > IoCコンテナに管理されるようにするために@Componentもつける > @Beforeや@After等は全てアドバイス(抽出した共通処理)を実行するタイミングを表しています > 例えば@Beforeならメソッドの実行前にアドバイスを実行します > @Before("execution(String com.example.demo.controller.HomeController.showList(..))") > >これはプログラムの一部ですがこの形で記述することをポイントカット式といい()の左から順番に指定子、戻り値、パッケージ、クラス、メソッド、引数を記述していっています 指定子はexecution,within,bean等の種類があります これらはメソッドやクラス、Beanの何がマッチしたら対象になるかを判断するだけの違いです またワイルドカードと呼ばれるものを使うことでプログラムを省略することができます 例えば*(1文字以上の任意の文字)や..(任意の0文字以上の文字)を表すことができるので戻り値や、メソッド、パッケージやクラスまでも省略することができます >execution(* com.example.demo..*(..)) >↑のプログラムはワイルドカードを使って省略しまくってますが意味は本文のプログラムにも使われている@Beforeの()内と同じです #### 実行 いつも通り実行します。 そしてここ(http://localhost:8080/)にアクセスすると↓の画像のようないろんな処理の結果が表示されます。 ![](https://i.imgur.com/Rwkg9Ow.png) また、index.htmlは前回同様更新してもハッシュ値は変わりません ![](https://i.imgur.com/0nmxDD6.png) 次にここ(http://localhost:8080/scope)にアクセスすると↓の画像のように処理が減ります ![](https://i.imgur.com/96QXXcT.png) この2つは行っている処理の違いによって表示される処理の数が違います ## 終わり むずかしい