Spring是一个永不止息的框架。 这是因为它提供了许多不同的解决方案,使我们(开发人员)无需编写数百万行代码即可完成我们的任务。 取而代之的是,我们能够以更具可读性,更标准化的方式进行操作。 在这篇文章中,我将尝试描述最有可能为大家所熟知的功能之一,但我认为其重要性被低估了。 我将要讨论的功能是@Primary批注。
问题
在我从事的几个项目中,我们遇到了一个常见的业务问题–我们有一个进入更复杂逻辑的入口–一些容器,该容器会将其他几个处理器的结果收集到一个输出中(例如map-filter-reduce函数编程中的函数)。 在某种程度上,它类似于Composite模式。 综上所述,我们的方法如下:
- 我们有一个容器,其中包含自动实现共同接口的处理器列表
- 我们的容器实现了与自动装配列表元素相同的接口
- 我们希望使用该容器的客户端类使整个处理工作透明化-他只对结果感兴趣
- 处理器具有一些逻辑(谓词),处理器可将其应用于当前输入数据集
- 然后将处理结果合并到一个列表中,然后缩减为单个输出
有很多方法可以解决此问题-我将介绍一种使用Spring和@Primary批注的方法。
解决方案
让我们从定义用例如何适应上述前提开始。 我们的数据集是一个Person类,如下所示:
人.java
package com.blogspot.toomuchcoding.person.domain;
public final class Person {
private final String name;
private final int age;
private final boolean stupid;
public Person(String name, int age, boolean stupid) {
this.name = name;
this.age = age;
this.stupid = stupid;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public boolean isStupid() {
return stupid;
}
}
没有什么不寻常的。 现在让我们定义合同:
PersonProcessingService.java
package com.blogspot.toomuchcoding.person.service;
import com.blogspot.toomuchcoding.person.domain.Person;
public interface PersonProcessingService {
boolean isApplicableFor(Person person);
String process(Person person);
}
如前提条件所述,PersonProcessingService的每个实现都必须定义合同的两点:
- 是否适用于当前人员
- 它如何处理一个人。
现在,让我们看一下我们拥有的一些处理器-由于它毫无意义,所以我不会在此处发布代码-您可以稍后在Github或Bitbucket上查看代码。 我们有以下@Component注释的PersonProcessingService实现:
- AgePersonProcessingService
- 如果某人的年龄大于或等于18,则适用
- IntelligencePersonProcessingService
- 适用于某人是愚蠢的人
- NamePersonProcessingService
- 如果某人有名字,则适用
逻辑很简单。 现在,我们的PersonProcessingServices容器将要针对处理器上的给定Person进行迭代,检查当前处理器是否适用(过滤器),如果是这种情况,则将响应处理Person的结果字符串添加到响应列表中(映射-将Person转换为String的函数),并最终以逗号将这些响应合并(减少)。 让我们检查一下它是如何完成的:
PersonProcessingServiceContainer.java
package com.blogspot.toomuchcoding.person.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.blogspot.toomuchcoding.person.domain.Person;
@Component
@Primary
class PersonProcessingServiceContainer implements PersonProcessingService {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonProcessingServiceContainer.class);
@Autowired
private List<PersonProcessingService> personProcessingServices = new ArrayList<PersonProcessingService>();
@Override
public boolean isApplicableFor(Person person) {
return person != null;
}
@Override
public String process(Person person) {
List<String> output = new ArrayList<String>();
for(PersonProcessingService personProcessingService : personProcessingServices){
if(personProcessingService.isApplicableFor(person)){
output.add(personProcessingService.process(person));
}
}
String result = StringUtils.join(output, ",");
LOGGER.info(result);
return result;
}
public List<PersonProcessingService> getPersonProcessingServices() {
return personProcessingServices;
}
}
如您所见,我们有一个用@Primary注释的容器,这意味着如果必须注入PersonProcessingService的实现,则Spring将选择要注入的PersonProcessingServiceContainer。 很棒的事情是,我们有一个自动连接的PersonProcessingServices列表,这意味着该接口的所有其他实现都将在那里自动连接(容器不会自动将其自身连接到该列表!)。
现在,让我们检查一下Spock测试 ,这些测试证明我没有在说谎。 如果您尚未在项目中使用Spock,则应立即将其移动。
PersonProcessingServiceContainerIntegrationSpec.groovy
package com.blogspot.toomuchcoding.person.service
import com.blogspot.toomuchcoding.configuration.SpringConfiguration
import com.blogspot.toomuchcoding.person.domain.Person
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification
import spock.lang.Unroll
import static org.hamcrest.CoreMatchers.notNullValue
@ContextConfiguration(classes = [SpringConfiguration])
class PersonProcessingServiceContainerIntegrationSpec extends Specification {
@Autowired
PersonProcessingService personProcessingService
def "should autowire container even though there are many implementations of service"(){
expect:
personProcessingService instanceof PersonProcessingServiceContainer
}
def "the autowired container should not have itself in the list of autowired services"(){
expect:
personProcessingService instanceof PersonProcessingServiceContainer
and:
!(personProcessingService as PersonProcessingServiceContainer).personProcessingServices.findResult {
it instanceof PersonProcessingServiceContainer
}
}
def "should not be applicable for processing if a person doesn't exist"(){
given:
Person person = null
expect:
!personProcessingService.isApplicableFor(person)
}
def "should return an empty result for a person not applicable for anything"(){
given:
Person person = new Person("", 17, false)
when:
def result = personProcessingService.process(person)
then:
result notNullValue()
result.isEmpty()
}
@Unroll("For name [#name], age [#age] and being stupid [#stupid] the result should contain keywords #keywords")
def "should perform different processing depending on input"(){
given:
Person person = new Person(name, age, stupid)
when:
def result = personProcessingService.process(person)
then:
keywords.every {
result.contains(it)
}
where:
name | age | stupid || keywords
"jan" | 20 | true || ['NAME', 'AGE', 'STUPID']
"" | 20 | true || ['AGE', 'STUPID']
"" | 20 | false || ['AGE']
null | 17 | true || ['STUPID']
"jan" | 17 | true || ['NAME']
}
}
测试非常简单:
- 我们证明自动装配字段实际上是我们的容器– PersonProcessingServiceContainer。
- 然后,我们证明在PersonProcessingService的自动装配实现的集合中找不到对象,该对象属于PersonProcessingServiceContainer类型
- 在接下来的两个测试中,我们证明处理器背后的逻辑正在运行
- 最后但并非最不重要的一点是Spock最出色的– where子句,它使我们能够创建漂亮的参数化测试。
每个模块的功能
想象一下您在核心模块中定义了接口的实现的情况。
@Component
class CoreModuleClass implements SomeInterface {
...
}
如果您在与核心模块有依赖性的其他模块中决定不想使用此CoreModuleClass并希望在SomeInterface自动连线的任何地方都具有一些自定义逻辑该怎么办? 好吧–使用@Primary!
@Component
@Primary
class CountryModuleClass implements SomeInterface {
...
}
通过这种方式,您可以确保必须自动装配SomeInterface的位置将是您的CountryModuleClass,将其插入到该字段中。
结论
在这篇文章中,您可以看到如何
- 使用@Primary批注创建类似接口实现的复合容器
- 使用@Primary批注提供接口的每个模块实现,在自动装配方面,该实现将优先于其他@Components
- 编写出色的Spock测试:)
编码
您可以在Too Much Coding的Github存储库或Too Much Coding的Bitbucket存储库中找到此处提供的代码。
翻译自: https://www.javacodegeeks.com/2013/12/springs-primary-annotation-in-action.html