Spring入門:The IoC Container,實踐篇(下)

  • 2019 年 10 月 4 日
  • 筆記

8. 基於【註解(Annotation)】的容器配置

8.1. 應該用 XML 還是 Annotation?

  • 各有特點,應由開發人員根據情況挑選適合自己的方案。
  • 註解配置更簡潔,會侵入業務邏輯;(XML 恰好反過來);
  • Spring 支援註解與 XML 配置混合使用;
  • Spring 通過 BeanPostProcessor 實現基於註解的配置;
  • <context:annotation-config/> 可隱式註冊那些用於實現註解配置的 BeanPostProcessor:AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor
  • <context:annotation-config/> 的影響範圍近限它所在的容器;
  • 基於註解注入的執行時機要比基於XML的注入靠前;

8.2. @Required

  • 應用於 Bean 屬性的 setter 方法上。實現必填的依賴項控制;
  • Spring 5.1 中廢棄了,建議使用【構造器注入】或【InitializingBean.afterPropertiesSet()】解決;

示例1:

package webj2ee;    import org.springframework.beans.factory.annotation.Required;    public class ExampleBean {      private String name;        @Required      public void setName(String name) {          this.name = name;      }        @Override      public String toString() {          return "ExampleBean{" +                  "name='" + name + ''' +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>      <bean id="exampleBean" class="webj2ee.ExampleBean"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);          System.out.println(exampleBean);          context.close();      }  }

8.3. @Autowired

  • @Autowired 註解可用於構造函數、setter方法、欄位;
  • @Autowired 可以收集 ApplicationContext 中匹配某種特殊類型的所有 Bean;(注1:可收集為 Map、Set、Array)(注2:可通過 @Order 註解,調整順序)
  • @Autowired 標記的方法或欄位,默認是 required 的,可通過 @Autowired(required = false) 調整為非必填欄位。也可以通過@Nullable、java.util.Optional (JDK8)作為替代方案;
  • 當 @Autowired 標記到構造函數上時:如果只有一個構造函數,實際沒必要通過@Autowired標註。如果存在多個構造函數,最多只能有一個構造函數被標記為(required=true);多個構造函數可同時被標記為(required=false),都將作為候選,滿足最大依賴項的構造函數將會被選用。
  • 構造函數參數中的【多元素注入(arrays、set、map)】的注入比較特殊,如果沒有找到可注入項,則會被解析為空(Empty)
  • @Autowired 可以直接解析一些Spring 內部依賴項:BeanFactory, ApplicationContext, Environment, ResourceLoader, ApplicationEventPublisher, and MessageSource。

示例1:(只有一個構造函數,沒必要標記 @Autowired)

package webj2ee;  public class MovieFinder {      @Override      public String toString() {          return "MovieFinder{}";      }  }
package webj2ee;    public class SimpleMovieLister {      private MovieFinder movieFinder;        public SimpleMovieLister(MovieFinder movieFinder) {          this.movieFinder = movieFinder;      }        @Override      public String toString() {          return "SimpleMovieLister{" +                  "movieFinder=" + movieFinder +                  '}';      }        // ...  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="movieFinder" class="webj2ee.MovieFinder"/>      <bean id="simpleMovieLister" class="webj2ee.SimpleMovieLister"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          SimpleMovieLister simpleMovieLister = context.getBean("simpleMovieLister", SimpleMovieLister.class);          System.out.println(simpleMovieLister);          context.close();      }  }

示例2:(@Autowired + setter)

package webj2ee;  public class MovieFinder {      @Override      public String toString() {          return "MovieFinder{}";      }  }
package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;    public class SimpleMovieLister {      private MovieFinder movieFinder;        @Autowired      public void setMovieFinder(MovieFinder movieFinder) {          this.movieFinder = movieFinder;      }        @Override      public String toString() {          return "SimpleMovieLister{" +                  "movieFinder=" + movieFinder +                  '}';      }        // ...  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="movieFinder" class="webj2ee.MovieFinder"/>      <bean id="simpleMovieLister" class="webj2ee.SimpleMovieLister"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          SimpleMovieLister simpleMovieLister = context.getBean("simpleMovieLister", SimpleMovieLister.class);          System.out.println(simpleMovieLister);          context.close();      }  }

示例3:(@Autowired + required=false)

package webj2ee;  public class MovieFinder {      @Override      public String toString() {          return "MovieFinder{}";      }  }
package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;    public class SimpleMovieLister {      private MovieFinder movieFinder;        @Autowired(required = false)      public void setMovieFinder(MovieFinder movieFinder) {          this.movieFinder = movieFinder;      }    //    @Autowired  //    public void setMovieFinder(@Nullable MovieFinder movieFinder) {  //        this.movieFinder = movieFinder;  //    }    //    @Autowired  //    public void setMovieFinder(Optional<MovieFinder>  movieFinder) {  //        this.movieFinder = movieFinder.orElse(null);  //    }        @Override      public String toString() {          return "SimpleMovieLister{" +                  "movieFinder=" + movieFinder +                  '}';      }        // ...  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="simpleMovieLister" class="webj2ee.SimpleMovieLister"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          SimpleMovieLister simpleMovieLister = context.getBean("simpleMovieLister", SimpleMovieLister.class);          System.out.println(simpleMovieLister);          context.close();      }  }

示例4:(@Autowired + Set、Array、Map)

package webj2ee;  public interface MovieCatalog {  }
package webj2ee;    import org.springframework.core.annotation.Order;    @Order(1)  public class Comedy implements MovieCatalog {      @Override      public String toString() {          return "Comedy{}";      }  }
package webj2ee;    import org.springframework.core.annotation.Order;    @Order(2)  public class Dracula implements MovieCatalog {      @Override      public String toString() {          return "Dracula{}";      }  }
package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;    import java.util.Arrays;  import java.util.Map;  import java.util.Set;    public class MovieRecommender {        @Autowired      private MovieCatalog[] movieCatalogsArr;        @Autowired      private Set<MovieCatalog> movieCatalogsSet;        @Autowired      private Map<String, MovieCatalog> movieCatalogsMap;        @Override      public String toString() {          return "MovieRecommender{" +                  "movieCatalogsArr=" + Arrays.toString(movieCatalogsArr) +                  ", movieCatalogsSet=" + movieCatalogsSet +                  ", movieCatalogsMap=" + movieCatalogsMap +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="dracula" class="webj2ee.Dracula"/>      <bean id="comedy" class="webj2ee.Comedy"/>      <bean id="movieRecommender" class="webj2ee.MovieRecommender"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          MovieRecommender movieRecommender = context.getBean("movieRecommender", MovieRecommender.class);          System.out.println(movieRecommender);          context.close();      }  }

示例5:(@Autowired + Constructors + Multi-Elements)

package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;    import java.util.Arrays;  import java.util.Map;  import java.util.Set;    public class MovieRecommender {      private MovieCatalog[] movieCatalogsArr;      private Set<MovieCatalog> movieCatalogsSet;      private Map<String, MovieCatalog> movieCatalogsMap;        @Autowired(required = true)      public MovieRecommender(              MovieCatalog[] movieCatalogsArr,              Set<MovieCatalog> movieCatalogsSet,              Map<String, MovieCatalog> movieCatalogsMap) {          this.movieCatalogsArr = movieCatalogsArr;          this.movieCatalogsSet = movieCatalogsSet;          this.movieCatalogsMap = movieCatalogsMap;      }        @Override      public String toString() {          return "MovieRecommender{" +                  "nmovieCatalogsArr=" + Arrays.toString(movieCatalogsArr) +                  ", nmovieCatalogsSet=" + movieCatalogsSet +                  ", nmovieCatalogsMap=" + movieCatalogsMap +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="movieRecommender" class="webj2ee.MovieRecommender"/>  </beans>

示例6:(@Autowired + ApplicationContext …)

package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.context.ApplicationContext;    public class SimpleMovieLister {        @Autowired      private ApplicationContext context;        @Override      public String toString() {          return "SimpleMovieLister{" +                  "context=" + context +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="simpleMovieLister" class="webj2ee.SimpleMovieLister"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          SimpleMovieLister simpleMovieLister = context.getBean("simpleMovieLister", SimpleMovieLister.class);          System.out.println(simpleMovieLister);          context.close();      }  }

8.4. @Autowired + @Primary

  • @Autowired 是按【類型】裝配,如果存在多個同類型候選Bean,就會報錯;
  • 可通過 @Primary 在一堆同類型 Bean 中標記其中某一個是主要的;
package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;    public class MovieRecommender {      @Autowired      private MovieCatalog movieCatalog;        @Override      public String toString() {          return "MovieRecommender{" +                  "movieCatalog=" + movieCatalog +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>        <bean id="dracula" class="webj2ee.Dracula" primary="true"/>      <bean id="comedy" class="webj2ee.Comedy"/>      <bean id="movieRecommender" class="webj2ee.MovieRecommender"/>  </beans>
package webj2ee;    import org.springframework.context.support.ClassPathXmlApplicationContext;    public class Test {      public static void main(String[] args) {          ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");          MovieRecommender movieRecommender = context.getBean("movieRecommender", MovieRecommender.class);          System.out.println(movieRecommender);          context.close();      }  }  

8.5. @Autowired + @Qualifier

  • @Qualifier 用於進一步精確限制候選 Bean 範圍;

標準用法:

<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>        <bean id="dracula" class="webj2ee.Dracula">          <qualifier value="level1"></qualifier>      </bean>        <bean id="comedy" class="webj2ee.Comedy">          <qualifier value="level2"></qualifier>      </bean>        <bean id="movieRecommender" class="webj2ee.MovieRecommender"/>  </beans>
package webj2ee;    import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.beans.factory.annotation.Qualifier;    public class MovieRecommender {      @Autowired      @Qualifier("level1")      private MovieCatalog movieCatalog;        @Override      public String toString() {          return "MovieRecommender{" +                  "movieCatalog=" + movieCatalog +                  '}';      }  }
  • @Qualifier(value) 的回退匹配特性:如果在候選 Bean 中沒有找到複合 qualifier value 的 Bean,則會去找一個 id 與 qualifier value 相等的 Bean;但!不要將 @Qualifier(value) 的這種回退匹配的特性用於實現 byName 的依賴注入,應考慮其語義化特徵。如果要實現 byName,應當選擇@Resource。

8.6. @Resource

  • 隸屬 JSR-250 規範。
  • 可應用於欄位、Bean 的 setter 方法。
  • 語義是 by-name。
  • @Resource 的回退匹配策略,會尋找主類型匹配元素 Bean。
  • @Resource 也可以解析像 BeanFactory, ApplicationContext, ResourceLoader, ApplicationEventPublisher, MessageSource 這些 Spring 依賴。

示例:

package webj2ee;    import org.springframework.context.ApplicationContext;    import javax.annotation.Resource;    public class SimpleMovieLister {      private MovieFinder movieFinder;        @Resource      private ApplicationContext context;        @Resource(name = "movieFinder")      public void setMovieFinder(MovieFinder movieFinder) {          this.movieFinder = movieFinder;      }        @Override      public String toString() {          return "SimpleMovieLister{" +                  "movieFinder=" + movieFinder +                  ", context=" + context +                  '}';      }  }
<?xml version="1.0" encoding="UTF-8"?>  <beans ...>      <context:annotation-config/>      <bean id="movieFinder" class="webj2ee.MovieFinder"/>      <bean id="simpleMovieLister" class="webj2ee.SimpleMovieLister"/>  </beans>

9. Classpath Scanning and Managed Components

  • 前面提到的 @Autowired、@Resource 等只是解決了注入層面問題,但還是要在 XML 中手寫 Bean 定義。
  • @Component 是標記 Bean 的基礎註解;其衍生有:@Repository(持久層), @Service(服務層), @Controller(展示層)。
  • <context:component-scan> 用於實現 Bean 的掃描與註冊;
  • <context:component-scan> 隱式啟用了 <context:annotation-config>;
  • 可通過 include-filter、exclude-filter 訂製掃描策略;
<beans>      <context:component-scan base-package="org.example">          <context:include-filter type="regex"                  expression=".*Stub.*Repository"/>          <context:exclude-filter type="annotation"                  expression="org.springframework.stereotype.Repository"/>      </context:component-scan>  </beans>

下一波就輪到寫

AOP實踐篇咯

參考:

Spring Framework Runtime: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/overview.html The IoC Container: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core Inversion of Control Containers and the Dependency Injection pattern: https://martinfowler.com/articles/injection.html 《Java Web 高級編程技術》 《Spring 入門經典》 《精通 Spring 4.x 企業應用開發實戰》 《Spring5 高級編程》 《Spring實戰(第3版)》