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基本可以满足我们的需求。