DI(Dependency Injection)?


良好なオブジェクト向け設計五原則(SOLID)

  • 単一責任原則(SRP):1つのクラスは1つの責任しか負いません.
  • オープン-クローズの原則(OCP):ソフトウェア要素は拡張でオープンで、変更でクローズである必要があります.
  • リスコフ互換原則(LSP)
  • インターフェース分離原則(ISP)
  • 依存関係逆転原則(DIP):プログラマーは「具体化ではなく抽象化に依存しなければならない」
  • 客体向けの核心は多形性である.柔軟で変更しやすいプログラムを設計することが主な目的と目標であるが,多形性だけではOCP,DIPの原則を遵守できない.スプリングのDIとDI容器はこれらの制限を解決した.言い換えれば、クライアントコードを変更せずに機能を自由に拡張することができる.
    AppConfigがインプリメンテーションオブジェクトの作成と接続を担当する役割を考慮すると、制御反転とともに動作原理を考慮することができる.

    DI?


    Dependency Injectionの略で、依存注入と呼ばれています.
    import java.time.LocalDateTime;
    
    public class MemberRegisterService {
        // MemberRegisterService 클래스가 MemberDao와 의존관계에 있다.
        private MemberDao memberDao = new MemberDao();
        
        public void regist(RegisterRequest req) {
        	// 이메일로 회원 데이터(Member) 조회
            Member member = memberDao.selectByEmail(req.getEmail());
            /**
               코드 생략
            **/
        }
    }
    上記のコードから,MemberRegisterServiceクラスはMemberDaoクラスに依存していることが分かる.これは、MemberDaoが変更されると、連鎖反応を受ける可能性があることを意味します.このような関係にあると,前述したSOLIDの原則が破られる.(特にOCP、DIP原則)
    クラス内で依存オブジェクトを直接作成するのは簡単ですが、長期的には機能拡張やメンテナンスの観点から問題を引き起こすコードです.
    これとは異なり,DIは依存オブジェクトを直接生成するのではなく,依存オブジェクトを受信する方式を用いる.
    import java.time.LocalDateTime;
    
    public class MemberRegisterService {
        private MemberDao memberDao;
        // 생성자 주입
        public MemberRegisterService(MemberDao memberDao) {
        	this.memberDao = memberDao;
        }
        
        public void regist(RegisterRequest req) {
        	// 이메일로 회원 데이터(Member) 조회
            Member member = memberDao.selectByEmail(req.getEmail());
            /**
               코드 생략
            **/
        }
    }
    上のコードから見ると,以前とは異なり,依存オブジェクトを直接生成するのではなく,ジェネレータを介して依存オブジェクトを伝達する.このようにDIを用いることで,変更の柔軟性を保つことができる.MemberDaoクラスはメンバーデータをDBに格納し、クエリーを高速化するためにキャッシュを適用する必要がある場合は、クラスを継承して新しいクラスを作成する必要があります.このプロセスでは、クラスがMemberDaoに関連付けられている場合は、コードを同じ変更する必要がありますが、DIを適用すると、変更するコードが減少します.すなわち,注入オブジェクトのコードを1つ変更するだけでよい.
    ここでは、オブジェクトを作成し、依存オブジェクトに注入する部分をそれぞれAppConfigとして作成します.
    import test1.ChangePasswordService;
    import test1.MemberDao;
    import test1.MemberRegisterService;
    
    public class AppConfig {
        private MemberDao memberDao;
        private MemberRegisterService regSvc;
        private ChangePasswordService pwdSvc;
        
        public AppConfig() {
        	memberDao = new MemberDao();
            regSvc = new MemberRegisterService(memberDao);
            pwdSvc = new ChangePasswordService();
            pwdSvc.setMemberDao(memberDao);
        }
        
        public MemberDao getMemberDao() {
        	return memberDao;
        }
        
        public MemberRegisterService getMemberRegisterService() {
        	return regSvc;
        }
        
        public ChangePasswordService getChangePasswordService() {
        	return pwdSvc;
        }
    }
    このように単独で管理すれば、クラスの変更に柔軟に対応できます.Spring Frameworkの構造は、特定のタイプのクラスだけでなく、これらのAppConfigを汎用化することです.

    SpringでのDI


    次のコードを見ると、AppConfigとあまり変わらないと思います.
    package config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import test1.ChangePasswordService;
    import test1.MemberDao;
    import test1.MemberRegisterService;
    
    @Configuration
    public class AppCtx {
        
        @Bean
        public MemberDao memberDao() {
        	return new MemberDao();
        }
        
        @Bean
        public MemberRegisterService memberRegSvc() {
        	return new MemberRegisterService(memberDao());
        }
        
        @Bean
        public ChangePasswordService changePwdSvc() {
        	ChangePasswordService pwdSvc = new ChangePasswordService();
            pwdSvc.setMemberDao(memberDao());
            return pwdSvc;
        }
    }
    前述したように、スプリングが作成するオブジェクトと、依存関係をどのように注入するかを記述すればよい.
  • @Configuration:スプリング設定クラス
  • @Bean:このメソッドで作成したオブジェクトをスプリング空席に設定します.
  • AnnotationConfigApplicationContext:オブジェクトを作成し、依存オブジェクトを注入するスプリングコンテナ
  • @Autowired:スプリングシートに依存する他の空孔を自動的に注入するための
  • 📌 SpringでのDI方式は?

  • DI:空のオブジェクトを作成すると、すべての依存オブジェクトが注入されます.
  • javabeanのGetter/Setter Property設定方法により、DI:設定方法名によって、どの依存オブジェクトが注入されるかを知ることができます.
  • @Bean設定とモノトーン?

    @Configuration
    public class AppCtx {
        
        @Bean
        public MemberDao memberDao() {
        	return new MemberDao();
        }
        
        @Bean
        public MemberRegisterService memberRegSvc() {
        	return new MemberRegisterService(memberDao());
        }
        
        @Bean
        public ChangePasswordService changePwdSvc() {
        	ChangePasswordService pwdSvc = new ChangePasswordService();
            pwdSvc.setMemberDao(memberDao());
            return pwdSvc;
        }
    }
    memberRegSvc()およびchangePwdSvc()が実行されるたびにmemberDao()が実行され、結果として新しいMemberDaoオブジェクトが生成され、返される.
    🤔 ではメンバーごとに対象が違うのでしょうか???
    いいえ、ちがいます
    スプリングコンテナによって生成される空は、モノトーンオブジェクトです.スプリングコンテナは、@Bean追加のメソッドに対してのみオブジェクトを作成します.これは、上記のコードでmemberDao()が複数回呼び出されたときに、常に同じオブジェクトが返されることを意味します.
    これは、スプリングが実行時に作成した設定クラス内のmemberDao()メソッドが、一度に作成したオブジェクトを保持し、同じオブジェクトを返すためです.

    References

  • スプリングコア原理-基本編
  • Spring 5プログラミング入門