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版)》