Spring的@Primary注释在起作用

Spring是一个永不止息的框架。 这是因为它提供了许多不同的解决方案,使我们(开发人员)无需编写数百万行代码即可完成我们的任务。 取而代之的是,我们能够以更具可读性,更标准化的方式进行操作。 在这篇文章中,我将尝试描述最有可能为大家所熟知的功能之一,但我认为其重要性被低估了。 我将要讨论的功能是@Primary批注。

问题

在我从事的几个项目中,我们遇到了一个常见的业务问题–我们有一个进入更复杂逻辑的入口–一些容器,该容器会将其他几个处理器的结果收集到一个输出中(例如map-filter-reduce函数编程中的函数)。 在某种程度上,它类似于Composite模式。 综上所述,我们的方法如下:

  1. 我们有一个容器,其中包含自动实现共同接口的处理器列表
  2. 我们的容器实现了与自动装配列表元素相同的接口
  3. 我们希望使用该容器的客户端类使整个处理工作透明化-他只对结果感兴趣
  4. 处理器具有一些逻辑(谓词),处理器可将其应用于当前输入数据集
  5. 然后将处理结果合并到一个列表中,然后缩减为单个输出

有很多方法可以解决此问题-我将介绍一种使用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的每个实现都必须定义合同的两点:

  1. 是否适用于当前人员
  2. 它如何处理一个人。

现在,让我们看一下我们拥有的一些处理器-由于它毫无意义,所以我不会在此处发布代码-您可以稍后在GithubBitbucket上查看代码。 我们有以下@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']
    }
}

测试非常简单:

  1. 我们证明自动装配字段实际上是我们的容器– PersonProcessingServiceContainer。
  2. 然后,我们证明在PersonProcessingService的自动装配实现的集合中找不到对象,该对象属于PersonProcessingServiceContainer类型
  3. 在接下来的两个测试中,我们证明处理器背后的逻辑正在运行
  4. 最后但并非最不重要的一点是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存储库中找到此处提供的代码。

参考:来自我们的JCG合作伙伴 Marcin Grzejszczak(位于Blog上)的 Spring @Primary注释 适用于编码成瘾者博客。

翻译自: https://www.javacodegeeks.com/2013/12/springs-primary-annotation-in-action.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值