簡體   English   中英

根據實現中使用的泛型使用接口的一個實現(應用類型)

[英]Use one implementation of an interface depending on used generic in implementation (applied type)

我有兩個接口。 一個接口包含信息,第二個接口應該使用第一個接口。 第二個接口有一個通用必須是第一個接口的實現。

我想根據我收到的第一個接口的實現自動使用第二個接口的實現。

讓我展示一下界面。 (我更改了域並簡化了它,但是你得到了基本的想法。)

//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);

    }

}

我至少可以填充publishersPublishingService與思考,對不對?

我是否需要自己做,或者在其他地方有任何幫助嗎?

或者,也許你認為這種方法是錯誤的,並且存在一種更聰明的方法來完成我在這里需要做的事情,隨意說出來並告訴我你的優越方式:p做事(真的,我很欣賞它)。

編輯1開始

在Spring編寫自定義事件處理程序時,它會找到正確的實現,這就是我從這個問題中獲得靈感的地方。

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

你的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);

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名稱。

如果你有興趣,我已經為此創建了一個要點

感謝@eugenioy和@Federico Peralta Schaffne的答案,我可以得到我想要的結果。 我還在這個問題中找到了來自@Jonathan的有趣的commet( 在運行時獲取泛型類 )。 在那個評論中提到了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);

    }

}

bean publisherImplementationResolver由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<Map<T>, G<T>>這樣的東西仍然會很棘手,所以它仍然不是完美的,但更好。 由於我不需要,我已經注意到了它。 它可以通過兩層不同的集合來完成,所以如果你需要它,那么做起來並不復雜。

public class ImplementationResolver<T> implements ApplicationContextAware {

    // ...

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

    // ...

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM