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