1. 程式人生 > 實用技巧 >Spring_4_工廠高階特性

Spring_4_工廠高階特性

目錄

1. 物件的生命週期

  • 什麼是物件的⽣命週期?
    ⼀個物件 建立、存活、消亡 的⼀個完整過程。
  • 為什麼要學習物件的⽣命週期? 由 Spring 負責物件的 建立、存活、銷燬,瞭解⽣命週期,有利於我們使用好 Spring 為我們建立的物件。
  • ⽣命週期的 3 個階段: 建立階段 —> 初始化階段 —> 銷燬階段

1.1 建立階段

Spring 工廠何時建立物件?'

  • scope="prototype":Spring 工廠在獲取物件 ctx.getBean("xxx") 的同時,建立物件。
  • scope="singleton":Spring 工廠建立的同時,建立物件。 通過配置 <bean lazy-init="true"/> 也可以實現工廠獲取物件的同時,建立物件。 通過配置 <bean lazy-init="true"/> 也可以實現工廠獲取物件的同時,建立物件。

1.2 初始化階段

什麼時候? Spring 工廠在建立完物件後,呼叫物件的初始化方法,完成對應的初始化操作。 初始化方法提供:程式設計師根據需求,提供初始化方法,最終完成初始化操作。 初始化方法呼叫:Spring 工廠進行呼叫。

提供初始化方法的兩種方式:

  1. InitializingBean 介面:

    public class Product implements InitializingBean {
        //程序员根据需求实现的方法, 完成初始化操作
    @Override public void afterPropertiesSet() throws Exception { System.out.println("Product.afterPropertiesSet"); } }
  2. 物件中提供一個普通的初始化方法,配置檔案種配置 init-method:

    public class Product {
        public void myInit() {
            System.out.println("Product.myInit");
        }
    }
    
    <bean id="product" class="com.yusael.life.Product" init-method="myInit"/>
    
  • 初始化操作的細節分析:
    1. 如果⼀個物件既實現 InitializingBean 同時⼜提供的 普通的初始化方法,執行順序? 先執行 InitializingBean,再執行 普通初始化方法。
    2. 注入⼀定發⽣在初始化操作的前面。
    3. 初始化操作到底是什麼? 資源的初始化:資料庫、IO、網路、…

1.3 銷燬階段

Spring 銷燬物件前,會呼叫物件的銷燬方法,完成銷燬操作。

  • Spring 什麼時候銷燬所建立的物件?ctx.close(); 銷燬方法提供:程式設計師根據業務需求,定義銷燬方法,完成銷燬操作

銷燬方法呼叫:Spring 工廠進行呼叫。

開發流程與初始化操作一樣,提供銷燬方法的兩種方式:

  1. DisposableBean 介面:

    public class Product implements DisposableBean {
        // 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
        @Override
        public void destroy() throws Exception {
            System.out.println("Product.destroy");
        }
    }
    
  2. 物件中提供一個普通的銷燬方法,配置檔案種配置 destroy-method:

    public class Product {
        // 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
        public void myDestory() {
            System.out.println("Product.myDestory");
        }
    }
    
    <bean id="product" class="com.yusael.life.Product" destroy-method="myDestory"/>
    
  3. 銷燬階段細節分析: 銷燬方法的操作只適用於 scope="singleton",初始化操作都適用。
  4. 銷燬操作到底是什麼? 資源的釋放:io.close()、connection.close()、…

1.4 總結

public class Product implements InitializingBean, DisposableBean {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("Product.setName");
        this.name = name;
    }

    Product() { // 创建
        System.out.println("Product.Product");
    }

    // 程序员根据需求实现的方法, 完成初始化操作
    public void myInit() {
        System.out.println("Product.myInit");
    }

    // 程序员根据需求实现的方法, 完成初始化操作
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("Product.afterPropertiesSet");
    }

    public void myDestory() {
        System.out.println("Product.myDestory");
    }

    // 程序员根据⾃⼰的需求, 定义销毁方法, 完成销毁操作
    @Override
    public void destroy() throws Exception {
        System.out.println("Product.destroy");
    }
}
<bean id="product" class="com.yusael.life.Product" init-method="myInit" destroy-method="myDestory">
  <property name="name" value="yusael"/>
</bean>

img1

2. 配置檔案引數化

配置檔案引數化:把 Spring 配置檔案中需要經常修改的字串資訊,轉移到⼀個更小的配置檔案中。

  1. Spring 的配置檔案中是否存在需要經常修改的字串? 存在:以資料庫連線相關的引數…
  2. 經常變化字串,在 Spring 的配置檔案中,直接修改不利於專案維護(修改)
  3. 轉移到⼀個小的配置檔案(.properties)利於維護(修改)

優點:利於 Spring 配置檔案的維護(修改)

2.1 配置檔案引數的開發步驟

  1. 提供⼀個小的配置檔案(.properities) 名字:沒有要求 放置位置:沒有要求

    jdbc.driverClassName = com.mysql.jdbc.Driver
    jdbc.url = jdbc:mysql://localhost:3306/spring?useSSL=false
    jdbc.username = root
    jdbc.password = 1234
    
  2. Spring 的配置檔案與小配置檔案進行整合:

    <!--Spring的配置文件与⼩配置文件进行整合-->
    <!--resources 下的文件在整个程序编译完后会被放到 classpath 目录下,src.main.java中的文件也是-->
    <context:property-placeholder location="classpath:/db.properties"/>
    
  3. 在 Spring 配置檔案中通過 ${key} 獲取小配置檔案中對應的值:

    <bean id="conn" class="com.yusael.factorybean.ConnectionFactoryBean">
      <property name="driverClassName" value="${jdbc.driverClassName}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
    </bean>
    

3. 自定義型別轉換器

產生原因:當 Spring 內部沒有提供特定型別轉換器時,而程式設計師在應用的過程中還需要使用,那麼 就需要程式設計師⾃⼰定義型別轉換器。

3.1 自定義型別轉換器開發步驟

  1. 類 implements Converter 介面

    public class MyDateConverter implements Converter<String, Date> {
        /*
          convert方法作用: String ---> Date
          SimpleDateFormat sdf = new SimpleDateFormat();
          sdf.parset(String) ---> Date
    
          参数:
          source : 代表的是配置文件中, 日期字符串 <value>2020-10-11</value>
          return : 当把转换好的 Date 作为 convert 方法的返回值后,
          Spring ⾃动的为birthday属性进行注入(赋值)
        */
        @Override
        public Date convert(String source) {
            Date date = null;
            try {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                date = sdf.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
        }
    }
    
  2. 在 Spring 的配置檔案中進行配置; 先建立 MyDateConverter 物件,再註冊型別轉換器;

    <!--创建 MyDateConverter 对象-->
    <bean id="myDateConverter" class="com.yusael.converter.MyDateConverter"/>
    <!--用于注册类型转换器-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
      <property name="converters">
        <set>
          <ref bean="myDateConverter"/>
        </set>
      </property>
    </bean>
    
    <bean id="good" class="com.yusael.converter.Good">
      <property name="name" value="zhenyu"/>
      <property name="birthday" value="2012-12-12"/>
    </bean>
    
  3. 自定義型別轉換器細節
    1. MyDateConverter 中的日期的格式,通過 依賴注入 的方式,由配置檔案完成賦值。

      public class MyDateConverter implements Converter<String, Date> {
          private String pattern;
      
          @Override
          public Date convert(String source) {
              Date date = null;
              try {
                  SimpleDateFormat sdf = new SimpleDateFormat(pattern);
                  date = sdf.parse(source);
              } catch (ParseException e) {
                  e.printStackTrace();
              }
              return date;
          }
      
          public String getPattern() {
              return pattern;
          }
      
          public void setPattern(String pattern) {
              this.pattern = pattern;
          }
      }
      
      <!-- 配置文件完成对日期格式的赋值 -->
      <bean id="myDateConverter" class="com.yusael.converter.MyDateConverter">
        <property name="pattern" value="yyyy-MM-dd"/>
      </bean>
      
    2. ConversionSeviceFactoryBean 定義 id屬性,值必須是 conversionService;

      <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
          <set>
            <ref bean="myDateConverter"/>
          </set>
        </property>
      </bean>
      
    3. Spring 框架其實內建了日期型別的轉換器:日期格式必須是 2020/05/01。

      <bean id="good" class="com.yusael.converter.Good">
        <property name="name" value="zhenyu"/>
        <property name="birthday" value="2012/12/12"/>
      </bean>
      

4. 後置處理 Bean

BeanPostProcessor 作用:對 Spring 工廠所建立的物件,進行再加工。(AOP 的底層實現)

4.1 後置處理 Bean 原理分析

img2

4.2 實現 BeanPostProcessor 介面中規定的兩個方法

  • postProcessBeforeInitialization 作用:Spring 建立完物件,並進行注入後,可以執行 Before ⽅法進行加工; 通過方法的引數獲得 Spring 建立好的物件,最終通過返回值交給 Spring 框架。

    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }
    
  • postProcessAfterInitialization 作⽤:Spring 執行完物件的初始化操作後,可以執行 After ⽅法進行加工; 通過方法的引數獲得 Spring 建立好的物件,最終通過返回值交給 Spring 框架。

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
    
  • 應用 實戰中:很少處理 Spring 的初始化操作,沒有必要區分 Before,After。只需要實現其中一個,建議是 After 方法即可

4.3 BeanPostProcessor 開發步驟

  1. 類 實現 BeanPostProcessor 介面

    public class MyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return null;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            Category category = (Category) bean;
            category.setName("yusael");
            return category;
        }
    }
    
  2. Spring 配置檔案中進行配置

    <bean id="myBeanPostProcessor" class="com.yusael.beanpost.MyBeanPostProcessor"/>
    
  3. 細節 BeanPostProcessor 會對 Spring 工廠建立的所有物件進行加工。如果工廠建立了多個不同的物件,要注意區別傳入的物件:

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof  Category) {
            Category category = (Category) bean;
            category.setName("yusael");
            return category;
        }
        return bean;
    }