【问题标题】:Use one implementation of an interface depending on used generic in implementation (applied type)根据实现中使用的泛型(应用类型)使用接口的一种实现
【发布时间】:2015-10-27 18:06:50
【问题描述】:

我有两个接口。一个接口包含信息,第二个接口应该使用第一个接口。第二个接口有一个泛型必须是第一个接口的实现。

我想根据收到的第一个接口的实现自动使用第二个接口的实现。

让我展示一下接口。 (我更改了域并对其进行了简化,但您了解基本概念。)

//This contains information needed to publish some information
//elsewhere, on a specific channel (MQ, Facebook, and so on)
public interface PubInfo {

    String getUserName();
    String getPassword();
    String getUrl();

    Map<String, String> getPublishingSettings();
} 



//Implementation of this interface should be for one implementation 
//PubInfo
public interface Publisher<T extends PubInfo> {
    void publish(T pubInfo, String message);
}

假设我会有这些 PubInfo...

的实现
public class FacebookPubInfo implements PubInfo {
    // ...
}

.

public class TwitterPubInfo implements PubInfo {
    // ...
}

...以及Publisher

的这些
@Component
public class FacebookPublisher implements Publisher<FacebookPubInfo> {

    @Override
    public void publish(FacebookPubInfo pubInfo, String message) {
        // ... do something
    }
}

.

@Component
public class TwitterPublisher implements Publisher<TwitterPubInfo> {
    // ...
}    

你明白了基本的想法,两个接口,每个接口都有两个实现。

最后是问题

现在我将谈到棘手的部分,那就是我希望能够在我的服务获得 TwitterPubInfo 时自动使用 TwitterPublisher。

正如您在下面的示例中看到的那样,我可以通过手动映射来做到这一点,但我不禁想到它会存在一种更自动地执行此操作的方法,而不是依赖于手动映射。我使用spring,我认为在那里的某个地方会存在一个工具来帮助我解决这个问题,或者可能是其他一些实用程序类,但我找不到任何东西。

@Service
public class PublishingService {

    private Map<Class, Publisher> publishers = new HashMap<Class, Publisher>();

    public PublishingService() {

        // I want to avoid manual mapping like this
        // This map would probably be injected, but 
        // it would still be manually mapped. Injection
        // would just move the problem of keeping a 
        // map up to date.
        publishers.put(FacebookPubInfo.class, new FacebookPublisher());
        publishers.put(TwitterPubInfo.class, new TwitterPublisher());
    }

    public void publish(PubInfo info, String message) {

        // I want to be able to automatically get the correct
        // implementation of Publisher

        Publisher p = publishers.get(info.getClass());
        p.publish(info, message);

    }

}

我至少可以用反射填充publishers 中的PublishingService,对吧?

我需要自己做吗,或者在其他地方有什么帮助吗?

或者,也许你认为这种方法是错误的,并且存在一种更聪明的方法来完成我需要在这里做的事情,请随意说出来并告诉我你做事的优越方式:p(真的,我很感激它)。

编辑 1 个开始

在春季编写自定义事件处理程序时,它会找到正确的实现,这就是我对这个问题的启发。

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#context-functionality-events

这是来自那个页面:

public class BlackListNotifier implements ApplicationListener<BlackListEvent> {

    // ...

    public void onApplicationEvent(BlackListEvent event) {
        // as you can see spring solves this, somehow, 
        // and I would like to be able to something similar
    }

}

我能以某种方式获得相同的功能吗?

结束编辑 1

【问题讨论】:

  • 你的意思是FacebookPublisher implements Publisher&lt;FacebookPubInfo&gt;
  • 您对这个解决方案的第一大问题是什么?您必须手动枚举所有映射?
  • "...遇到问题时...我知道,我会使用 DI...现在他们有两个问题" :D
  • @PaulBoddington 是的,我会改变的
  • 您可以在接口PubInfo中放置一个方法Publisher&lt;? extends PubInfo&gt; publisher();,而不是使用Map

标签: java spring generics reflection


【解决方案1】:

您的 Publisher 是 Spring bean 实现吗?

在这种情况下,您可以使用:

Map<String, Publisher> pubs = context.getBeansOfType(Publisher.class);

然后您可以询问每个Publisher 是否会接受您收到的PubInfo(这意味着向Publisher 添加一个新方法,以便每个发布者可以决定它可以处理哪个PubInfo)。

此解决方案将避免手动映射,每个发布者将封装与其可以处理的内容相关的信息。

您还可以在每个Publisher 类中使用一个注解,然后获取所有具有该注解的bean(并且该注解可以指示Publisher 可以处理的特定类)。这是一种类似的方法,但也许你会发现它带有注释更好

你想做的是下面的......但据我所知,这不起作用。我建议的解决方案接近于此。

// does not work...
context.getBeansOfType(Publisher<pubInfo.getClass()>.class);

【讨论】:

  • 这不是我想要的,但让发布者决定他们是否可以处理比手动映射更好。如果接口强迫你实现它,它不会被遗忘,如果需要在某个地方进行手动映射,它会被遗忘(或者被现在不知道神奇事物如何工作的人错过)。
【解决方案2】:

Spring 实际上将抽象类型的 bean 自动装配到映射中,键是 bean 名称,值是实际的 bean 实例:

@Service
public class PublishingService {

    @Autowired
    private Map<String, Publisher> publishers;

    public void publish(PubInfo info, String message) {
        String beanName = info.getClass().getSimpleName();
        Publisher p = publishers.get(beanName);
        p.publish(info, message);
    }
}

为此,您需要将每个发布者的 bean 名称设置为与其对应的 PubInfo 具体实现的简单类名称相匹配。

一种方法是通过 Spring 的 @Component 注释:

@Component("FacebookPubInfo")
public class FacebookPublisher implements Publisher<FacebookPubInfo> {

    @Override
    public void publish(FacebookPubInfo pubInfo, String message) {
        // ... do something
    }
}

那么你只需要让 Spring 扫描这个类并按照与 TwitterPubInfo 类相同的方法。

注意:如果您使用 XML 配置,您可以使用相同的方法。无需使用@Component 并扫描您的类,只需在 XML 中明确设置每个发布者的 bean 名称。

【讨论】:

  • 最好从 bean 上的方法返回目标类。
  • 这很有帮助!如果我不需要在@Component 中添加"FacebookPubInfo",那将是完美的(该字符串不会有任何编译时安全性)。
【解决方案3】:

如果你有兴趣,我已经为此创建了一个Gist

解决方案

感谢@eugenioy 和@Federico Peralta Schaffne 的回答,我可以得到我想要的最终结果。我还在这个问题 (Get generic type of class at runtime) 中发现了来自@Jonathan 的有趣评论。在那条评论中提到了 TypeTools,这是我需要把它放在一起的最后一块。

我最终编写了一个小组件,它可以自动生成映射,并可以返回实现类。

如果您知道库中是否存在此类组件,请告诉我。这实际上是我首先要搜索的内容(我会将我的答案标记为正确的答案,但是如果您在公共存储库的维护库中找到这样的组件,我很乐意让您的答案正确,它只需要能够做我的组件可以做的事情)。

为了获得 TypeTools,我将其添加到我的 pom 中:

    <dependency>
        <groupId>net.jodah</groupId>
        <artifactId>typetools</artifactId>
        <version>0.4.3</version>
    </dependency>

现在PublishingService 的实现如下所示:

@Service
public class PublishingService {

    //This bean is manually defined in application context. If spring could
    //understand how to do this from the Generic I specified below (Publisher)
    //it would be close to perfect. As I understand it, this information is
    //lost in runtime.

    @Autowired
    private ImplementationResolver<Publisher> publisherImplementationResolver;

    public void publish(PubInfo info, String message) {

        Publisher p = publisherImplementationResolver.getImplementaion(info);
        p.publish(info, message);

    }

}

beanpublisherImplementationResolver由SpringContext构造

@SpringBootApplication
public class Main  {

    @Bean
    public ImplementationResolver publisherImplementationResolver() {
        //If spring could figure out what class to use, It would be even better
        //but now I don't see any way to do that, and I manually create this
        //bean
        return new ImplementationResolver<Publisher>(Publisher.class);
    }

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

ImplementationResolver 类获取所有实现 Publisher 的 bean,并使用 TypeTools 映射指定的泛型(或者说应用类型更正确)。

/**
 * Created on 2015-10-25.
 */
public class ImplementationResolver<T> implements ApplicationContextAware {

    private Class<T> toBeImplemented;


    private Map<String, T> implementations;


    private Map<Class, T> implemenationMapper;


    public ImplementationResolver(Class<T> toBeImplemented) {
        this.toBeImplemented = toBeImplemented;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        implementations = applicationContext.getBeansOfType(toBeImplemented);
    }

    @PostConstruct
    public void intializeMapper() {
        implemenationMapper = new HashMap<Class, T>();
        for (String beanName : implementations.keySet()) {
            T impl = implementations.get(beanName);

            Class<?>[] typeArgs = TypeResolver.resolveRawArguments(toBeImplemented, impl.getClass());

            implemenationMapper.put(typeArgs[0], impl);
        }
    }

    public T getImplementaion(Object whatImplementationIsDoneForMe) {
        return implemenationMapper.get(whatImplementationIsDoneForMe.getClass());
    }
}

为了测试它是否按预期工作,我有这个类:

@Component
public class ImplementationDemoResolver  implements CommandLineRunner {

    @Autowired
    PublishingService publishingService;

    @Override
    public void run(String... strings) throws Exception {
        FacebookPubInfo fb = new FacebookPubInfo();
        TwitterPubInfo tw = new TwitterPubInfo();

        PubInfo fb2 = new FacebookPubInfo();
        PubInfo tw2 = new TwitterPubInfo();

        publishingService.publish(fb, "I think I am a interesting person, doing interesting things, look at this food!");
        publishingService.publish(tw, "I am eating interesting food. Look! #foodpicture");
        publishingService.publish(fb2, "Wasted Penguinz is the sh17");
        publishingService.publish(tw2, "Join now! #wpArmy");
    }
}

当我运行程序时,我得到了这个结果(FacebookPublisherTwitterPublisher 分别写 FACEBOOK 和 TWITTER):

FacebookPubInfo will provide information on how to publish on FACEBOOK. Message: I think I am a interesting person, doing interesting things, look at this food!
TwitterPubInfo will provide information on how to publish on TWITTER. Message: I am eating interesting food. Look! #foodpicture
FacebookPubInfo will provide information on how to publish on FACEBOOK. Message: Wasted Penguinz is the sh17
TwitterPubInfo will provide information on how to publish on TWITTER. Message: Join now! #wpArmy

动机

为什么选择这个而不是 Federico Peralta Schaffne 提供的解决方案?

此解决方案不需要实现类中的任何额外信息。另一方面,它需要一个特殊的设置,但我认为这是值得的。也可以在其他地方使用ImplementationResolver,因为它是一个单独的组件。一旦安装到位,它也会自动运行。

如果我相信我的同事,我可以采用 Federico Peralta Schaffne 提供的解决方案(并非所有人都想了解 Spring 和 tdd),它感觉比我的解决方案更轻量级。如果 bean 的名称在字符串中,重构可能会导致一些问题(但 Intellij 会找到它,也可能是其他 ide:s)。

改进

现在组件仅限于只有一个泛型的接口,但使用下面代码中的签名可以涵盖更多用例。像AdvancedPublisher&lt;Map&lt;T&gt;, G&lt;T&gt;&gt; 这样的东西仍然会很棘手,所以它仍然不是完美的,但会更好。因为我不需要它,所以我已经注意到实现了它。它可以用两层不同的集合来完成,所以如果你需要,它不会那么复杂。

public class ImplementationResolver<T> implements ApplicationContextAware {

    // ...

    public ImplementationResolver(Class<T> ... toBeImplemented) {
        this.toBeImplemented = toBeImplemented;
    }

    // ...

    public T getImplementaion(Object ... whatImplementationIsDoneForMe) {
        // .... implementation
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-28
    • 2021-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多