Spring Boot 2+Thymeleaf企业应用实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

4.2 高级Spring注解

前面讲解了Spring的一些基本注解,在一般情况下,这些注解已经够用了。这一节,我们再深入学习一些Spring的高级注解,这部分内容与一些基础的注解也有关联。

4.2.1 限定注解

在前面章节我们讲过一个Primary注解,当存在多个同类型的bean时,可以指定优先注入的bean。如果想对bean的注入选择做进一步的控制,则可以使用限定注解。限定注解可以与特定的参数关联起来,缩小类型匹配的范围,最后选择一个符合条件的bean来注入。代码清单4-14使用了限定注解。

代码清单4-14:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\qlf\QuaConfig.java codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\qlf\QuaApp.java

@Configuration
public class QuaConfig {

    @Bean
    public QuaBean quaBeanA() {
      return new QuaBean("a");
    }

    @Bean
    public QuaBean quaBeanB() {
      return new QuaBean("b");
    }
}

@SpringBootApplication
@RestController
public class QuaApp {

    public static void main(String[] args) {
      SpringApplication.run(QuaApp.class, args);
    }

    @Autowired
    @Qualifier("quaBeanA")
    QuaBean bean;

    @GetMapping("/qua")
    public String testGetBean() {
      System.out.println(bean.getName());
      return "";
    }
}

在配置类中,定义了两个同为QuaBean类型的bean,分别设置了不同的name属性。在启动类(同时也是控制器)中,使用@Autowired注入一个QuaBean类型的bean,并且使用@Qualifier指定注入名称为quaBeanA的bean。运行QuaApp后,访问:http://localhost:8080/qua,控制台会输出QuaBean的name属性。

4.2.2 自定义限定注解

前面我们使用了限定注解,当存在两个相同类型的注解时,可以根据bean的名称来指定注入的bean。如果需要根据特定的属性来指定注入的bean,则可以自定义限定注解。新建一个自定义注解,并且在配置bean时,使用这个注解,请见代码清单4-15。

代码清单4-15:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\ct\AnimalQualifier.java codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\ct\CustomConfig.java

@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface AnimalQualifier {

    String type();
}

@Configuration
public class CustomConfig {

    @Bean
    @AnimalQualifier(type = "person")
    public AnimalBean personBean() {
      return new AnimalBean("angus"); // 动物类型为person,名称为angus
    }

    @Bean
    @AnimalQualifier(type = "cat")
    public AnimalBean catBean() {
      return new AnimalBean("tom"); // 动物类型为cat,名称为tom
    }
}

在该代码清单中定义了一个AnimalQualifier的注解,该注解使用了@Qualifier修饰,并且需要设置type属性。在配置bean时,需要为相应的bean设置不同的类型。对于代码清单4-15,可以这样理解,Spring容器提供了不同类型的动物bean,并使用@AnimalQualifier为它们进行了标识。在使用这些bean时,同样使用@AnimalQualifier并指定type属性,就可以拿回相应的bean。代码清单4-16在控制器中获取bean。

代码清单4-16:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\ct\CustomApp.java

@SpringBootApplication
@RestController
public class CustomApp {

    public static void main(String[] args) {
      SpringApplication.run(CustomApp.class, args);
    }

    @Autowired
    @AnimalQualifier(type = "person")
    private AnimalBean person;

    @GetMapping("/cus")
    public String getAnm() {
      System.out.println(person.getName());
      return "";
    }
}

在控制器中注入bean时,额外使用了@AnimalQualifier注解,指定type为“person”。运行CustomApp后,访问:http://localhost:8080/cus,控制台输出:angus,这说明控制器已经得到期望的bean。

4.2.3 自定义bean的生命周期

前面介绍了,Scope注解主要用于配置bean在容器中的生命周期,常被配置为singleton(单态)与prototype(非单态)的,在Web环境下,还可以配置为request、session等值,表示Spring容器会为一次请求或一个会话分配一个bean的实例。如果对bean的生命周期有特殊的需求,则可以考虑使用自定义的Scope,自己写代码来定义bean的生命周期。

举一个特别的例子,假设有这样一个需求,一个bean被使用4次后,就需要获取新的bean实例,针对这样的需求,可编写一个自定义的Scope,请见代码清单4-17。

代码清单4-17:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\scope\MyScope.java

public class MyScope implements Scope {

    // 记录bean的使用次数
    private Map<String, Integer> beanCounts = new HashMap<String, Integer>();

    // 保存实例
    private Map<String, Object> beans = new HashMap<String, Object>();

    @Override
    public Object get(String name, ObjectFactory<? > objectFactory) {
      if(beanCounts.get(name) == null) {
          beanCounts.put(name, 0);
      }
      // 第一次使用,放到实例的Map中
      Integer beanCount = beanCounts.get(name);
      if(beanCount == 0) {
          Object newObject = objectFactory.getObject();
          beans.put(name, newObject);
      }
      Object bean = beans.get(name);
      // 计数器加1
      Integer newBeanCount = beanCount + 1;
      // 使用次数超过4,设置为0
      if(newBeanCount >= 4) {
          newBeanCount = 0;
      }
      // 设置新的次数
      beanCounts.put(name, newBeanCount);
      //  返回实例
      return bean;
    }

    ...省略其他的实现方法
}

代码清单4-17实现了Scope接口,添加了两个Map,一个Map用于保存bean被调用的次数,另一个Map则用于保存bean实例,两个Map的key均为bean的名称。代码清单4-17的逻辑较为简单,即bean每次被调用时,都会让计数器加1,当一个bean的实例调用次数超过4次时,计数器会被重新设置为0,此时会设置一个新的实例放到bean实例的Map中。

注意:本例并没有实现其他Scope接口方法。

接下来,需要将我们自定义的Scope注册到Spring容器,新建一个配置类,请见代码清单4-18。

代码清单4-18:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\scope\ScopeConfig.java

@Configuration
public class ScopeConfig {

    @Autowired
    BeanFactory factory;

    @PostConstruct
public void customScopeConfigurer() {
    // ScopeConfig初始化后执行本方法,创建MyScope的实例
      CustomScopeConfigurer config = new CustomScopeConfigurer();
      config.addScope("four", new MyScope());
      config.postProcessBeanFactory((ConfigurableListableBeanFactory)factory);
    }

    @Bean
    @Scope(scopeName = "four")
    public ScopeBean beanA() {
      return new ScopeBean();
    }
}

ScopeConfig的customScopeConfigurer方法,新建了一个CustomScopeConfigurer类,添加了一个名称为“four”的Scope,这就是我们的自定义Scope。在ScopeConfig中还定义了一个ScopeBean,该bean使用@Scope进行修饰,指定使用的Scope为“four”。

编写控制器,直接使用ApplicationContext来获取ScopeBean的实例,验证自定义生命周期是否生效。启动类(同控制器)的代码请见代码清单4-19。

代码清单4-19:codes\04\4.2\ants-adv\src\main\java\org\crazyit\boot\c4\scope\ScopeApp.java

@SpringBootApplication
@RestController
public class ScopeApp {

    public static void main(String[] args) {
      SpringApplication.run(ScopeApp.class, args);
    }

    @Autowired
    ApplicationContext ctx;

    @GetMapping("/scope")
    public String scope() {
      for(int i = 0; i < 6; i++) {
          System.out.println(ctx.getBean("beanA"));
      }
      return "";
    }
}

启动Spring Boot应用后,访问:http://localhost:8080/scope,控制台输出如下:

org.crazyit.boot.c4.scope.ScopeBean@4b0578
org.crazyit.boot.c4.scope.ScopeBean@4b0578
org.crazyit.boot.c4.scope.ScopeBean@4b0578
org.crazyit.boot.c4.scope.ScopeBean@4b0578
org.crazyit.boot.c4.scope.ScopeBean@1fa33f4
org.crazyit.boot.c4.scope.ScopeBean@1fa33f4

前面4次getBean调用,都会得到同一个ScopeBean实例,在第5次调用getBean时,会得到一个全新的ScopeBean实例。根据结果可知,我们自定义的Scope已经生效。在实际应用中,需要自定义bean生命周期的情况并不多见,Spring内置的几个Scope基本可以满足我们的需求。