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