2017年12月19日 星期二

Java Spring - DI, AOP, Boilerplate Code核心概念


Spring:
簡單的JavaBean實現EJB才能完成的事情
解決企業級應用開發的複雜性創建,簡化JAVA開發
  1. 基於POJO的輕量級和最小侵入性coding
  2. 通過DI AOP
  3. 基於切面和慣例進行聲明式coding
  4. 透過切面和模板減少樣板式coding

  • 不會強迫實現sping規範或繼承Spring的class (非侵入式)
    • HelloWorld 還是那個HelloWorld
    • 使用spring,class通常會看不出來使用了Spring
  • DI (Dependency Injection): 關係注入,保持low coupling
    • 通過DI,對象的依賴關係將由系統負責協調對象的第三方組建,在創建對象的時候進行設定,無需自行創建或管理依賴關係
    • 在runtime的時候賦予他們所依賴的對象
    • 通常會透過interface注入對象,如此能確保low coupling
    • constructor injection: 在constructor內將interface(原本要使用的object implements的interface)當作parameter注入,而不是在constructor內自行new object。好處是只要implements此interface,具體是哪一個object就不重要了。這就是DI所帶來最大的好處loose coupling
      • 使用interface表明依賴關係
      • 依賴能夠使用不同的具體object進行替換
    • 建造應用組建之間的協作行為通常稱為wiring
      • 使用XML配置需要DI需要的class需要被聲明為bean
      • <bean id="knight" class="com.spring.knights.BraveKnight">
         <constructor-arg ref="quest" />
        </bean> // BraveKnight class需要有一個實作Quest的parameter
        
        <bean id="quest" class="com.spring.knights.SlayDragonQuest">
         <constructor-arg value="#{T(System).out}" />
        </bean> // BSlayDragonQuest實作了Quest並且傳入BraveKnight,且傳入了#{T(System).out}當作parameter進入SlayDragonQuestconstructor
        
      • 使用ClassPathXmlApplicationContext加載Knight的Spring application context(此class將會加載路徑下的一個或多個XML配置文件),在main內可做加載。
      •   public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = 
                new ClassPathXmlApplicationContext(
                    "META-INF/spring/knight.xml"); // 加載
            Knight knight = context.getBean(Knight.class); // 編譯器不會知道是哪個 knight執行哪個quest,只有knights.xml知道
            knight.embarkOnQuest();
            context.close();
          }
        
      • 注意這個class完全不知道是誰
      • 使用Java描述配置(與XML功能相同)
      • @Configuration
        public class KnightConfig {
         @Bean
         public Knight kngiht() { // Knight interface
          return new BraveKnight(quest()); 
         }
         @Bean
         public Quest quest() {// Quest interface
           return new SlayDragonQuest(System.out); 
         }
        }
        
      • 上述的好處是,BraveKnight雖然依賴Quest,但並不知道真正傳入的是哪一個實作Quest的Object。相同,SlayDragonQuest雖然依賴PrintStream,但也不需要了解這個PrintStream是什麼樣子
      • 結論:可以在不改變依賴的class的情況下修改依賴關係
      • 藉由Spring通過配置,才能了解這些組成部分如何整合
  • AOP (Aspect-Oriented Programming) 面向切面 high cohesion
    • 允許把應用各處的功能分離,形成可重用的組件
    • 將散落各處的logic匯集於一處(aspect)
    • runtime的時候能夠把bean wire在一起,這樣就能夠有效地賦予bean新的行為
    • 問題
      • 一系統有學生module、課程module、講師module
      • 有一些common module: log、security、事務管理的服務
      • 每個module都要使用到這些服務
      • 這些服務的調用散布到服務中,但這些服務卻不是module的核心業務,會導致系統變得複雜
    • AOP能夠讓服務模組化 ,以聲明的方式將這些服務應用到需要的module上。
    • 結果能夠使得module有high cohesion,且能夠更關注在自身的業務邏輯上,完全不需要了解使用服務時所帶來的複雜性
    • AOP能夠確保POJO的簡單性
    • 範例如下:假設Minstrel在作戰前後BraveKnight要唱歌
    • public class Minstrel {
      
        private PrintStream stream;
        
        public Minstrel(PrintStream stream) {
          this.stream = stream;
        }
      
        public void singBeforeQuest() {
          stream.println("Fa la la, the knight is so brave!");
        }
      
        public void singAfterQuest() {
          stream.println("Tee hee hee, the brave knight " +
            "did embark on a quest!");
        }
      
      }
      
    • 如此,在BraveKnight class內可能一開始會這樣寫
    • public class BraveKnight implements Knight {
      
        private Quest quest;
        private Minstrel minstrel;
        public BraveKnight( Quest quest, Minstrel minstrel) {
          this.quest = quest;
          this.minstrel = minstrel;
        }
      
        public void embarkOnQuest() {
          minstrel.singBeforeQuest(); // 開戰前唱歌
          quest.embark();
          minstrel.singBeforeQuest(); // 開戰後唱歌
        }
      }
      
    • 然後將minstrel bean的配置injects到BraveKnight constructor中。
    • 但是,這會繁衍出一些問題:
      • BraveKnight必須去管理Minstrel唱歌。而吟遊詩人唱歌應該份內的事情,不應該由BraveKnight提醒。
      • BraveKnight必須將Minstrel注入,導致BraveKnight代碼複雜化
      • 是否需要再寫一個不需要Minstrel的BraveKnight? 若Minstrel為null時,還得需要引入空值?
    • 使用AOP可以聲明Minstrel必須要唱歌,BraveKnight本身不需要直接使用Minstrel的方法
    • AOP實例
      •   <bean id="knight" class="sia.knights.BraveKnight">
            <constructor-arg ref="quest" />
          </bean>
        
          <bean id="quest" class="sia.knights.SlayDragonQuest">
            <constructor-arg value="#{T(System).out}" />
          </bean>
        
          <bean id="minstrel" class="sia.knights.Minstrel"> //先聲明為bean
            <constructor-arg value="#{T(System).out}" />
          </bean>
        
          <aop:config>
            <aop:aspect ref="minstrel">
              <aop:pointcut id="embark"
                 expression="execution(* *.embarkOnQuest(..))"/>//定義切點
                
              <aop:before pointcut-ref="embark"  
                  method="singBeforeQuest"/> // 前置通知before advice,引用了embark的method
        
              <aop:after pointcut-ref="embark"  
                  method="singAfterQuest"/>// 後置通知after advice,引用了embark的method
            </aop:aspect>
          </aop:config>
        
    • 即使Mistrel變成Spring aspect,但仍然是一個POJO,並不會修改到原本的代碼
    • Minstrel可以被應用到BraveKnight,且BraveKnight不需要invoke Minstrel的method,實際上,BraveKnight也不知道Minstrel的存在
  • 使用模版消除樣板式代碼 boilerplate code
    • 為了實現通用的及簡單的任務,不得不一遍遍地重複代碼
    • 例如JDBC
      • create connection
      • create statement
      • do query
      • catch SQL exception
      • close connection
      • catch ...
      • 等等這些使用JDBC都需要用到的code,就是JDBC boilerplate code
    • Spring通過模版encapsulation來消除boilerplate code

沒有留言:

張貼留言