根据实现中使用的泛型(应用类型)使用接口的一种实现
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,我认为在那里某个地方会有一个工具可以帮助我解决这个问题,或者可能是其他一些实用程序 class,但我找不到任何东西。
@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);
}
}
我至少可以在 PublishingService
中填充 publishers
,对吧?
我需要自己做吗,或者是否有其他地方可以提供帮助?
或者,也许你认为这种方法是错误的,并且存在一种更聪明的方法来完成我在这里需要做的事情,请随意说出来并告诉我你做事的更好方法 :p(真的,我很感激)。
编辑 1 开始
在 spring 中编写自定义事件处理程序时,它找到了正确的实现,这就是我对这个问题的灵感来源。
这是来自那个页面:
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 beans 吗?
在这种情况下,您可以使用以下方法获得所有这些:
Map<String, Publisher> pubs = context.getBeansOfType(Publisher.class);
然后您可以询问每个 Publisher
是否会接受您收到的 PubInfo
(这意味着向 Publisher
添加一个新方法,以便每个发布者可以决定哪个 PubInfo
可以处理)。
此解决方案将避免手动映射,并且每个 Publisher 将封装与其可以处理的内容相关的信息。
您还可以在每个 Publisher
class 中使用注解,然后获取所有具有该注解的 bean(注解可以指示 class 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
具体实现的简单 class 名称。
一种方法是通过 Spring 的 @Component
注释:
@Component("FacebookPubInfo")
public class FacebookPublisher implements Publisher<FacebookPubInfo> {
@Override
public void publish(FacebookPubInfo pubInfo, String message) {
// ... do something
}
}
那么你只需要让 Spring 扫描这个 class 并按照与 TwitterPubInfo
class.
相同的方法
注意:如果您使用的是 XML 配置,则可以使用相同的方法。无需使用 @Component
并扫描您的 classes,只需在 XML.
中明确设置每个发布者的 bean 名称
如果你有兴趣,我已经为此创建了一个Gist。
解决方案
感谢@eugenioy 和@Federico Peralta Schaffne 的回答,我可以得到我想要的最终结果。在这个问题 (Get generic type of class at runtime) 中,我还发现了来自@Jonathan 的有趣评论。在那条评论中提到了 TypeTools,那是我需要把它放在一起的最后一块。
我最终写了一个自动生成映射的小组件,并且可以 return 实现 class。
如果您知道库中是否存在这样的组件,请告诉我。这实际上是我首先要搜索的内容(我会将我的答案标记为正确答案,但如果您在 public 存储库的维护库中找到这样的组件,我很乐意将您的答案设为正确的一个,它只需要能够做我的组件可以做的事情。
为了获取 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);
}
}
class ImplementationResolver
获取所有实现 Publisher
的 beans 并使用 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());
}
}
为了测试它是否按预期工作,我有这个 class:
@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");
}
}
当我 运行 我得到这个结果的程序(FacebookPublisher
和 TwitterPublisher
写 FACEBOOK 和 TWITTER respectevly):
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 提供的解决方案?
此解决方案在实施 classes 时不需要任何额外信息。另一方面,它需要特殊的设置,但我认为这是值得的。也可以在其他地方使用 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
}
}
我有两个界面。一个接口包含信息,第二个接口应该使用第一个接口。第二个接口有一个泛型,它必须是第一个接口的实现。
我想根据收到的第一个接口的实现自动使用第二个接口的实现。
让我展示一下界面。 (我更改了域并简化了它,但您已经了解了基本概念。)
//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,我认为在那里某个地方会有一个工具可以帮助我解决这个问题,或者可能是其他一些实用程序 class,但我找不到任何东西。
@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);
}
}
我至少可以在 PublishingService
中填充 publishers
,对吧?
我需要自己做吗,或者是否有其他地方可以提供帮助?
或者,也许你认为这种方法是错误的,并且存在一种更聪明的方法来完成我在这里需要做的事情,请随意说出来并告诉我你做事的更好方法 :p(真的,我很感激)。
编辑 1 开始
在 spring 中编写自定义事件处理程序时,它找到了正确的实现,这就是我对这个问题的灵感来源。
这是来自那个页面:
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 beans 吗?
在这种情况下,您可以使用以下方法获得所有这些:
Map<String, Publisher> pubs = context.getBeansOfType(Publisher.class);
然后您可以询问每个 Publisher
是否会接受您收到的 PubInfo
(这意味着向 Publisher
添加一个新方法,以便每个发布者可以决定哪个 PubInfo
可以处理)。
此解决方案将避免手动映射,并且每个 Publisher 将封装与其可以处理的内容相关的信息。
您还可以在每个 Publisher
class 中使用注解,然后获取所有具有该注解的 bean(注解可以指示 class 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
具体实现的简单 class 名称。
一种方法是通过 Spring 的 @Component
注释:
@Component("FacebookPubInfo")
public class FacebookPublisher implements Publisher<FacebookPubInfo> {
@Override
public void publish(FacebookPubInfo pubInfo, String message) {
// ... do something
}
}
那么你只需要让 Spring 扫描这个 class 并按照与 TwitterPubInfo
class.
注意:如果您使用的是 XML 配置,则可以使用相同的方法。无需使用 @Component
并扫描您的 classes,只需在 XML.
如果你有兴趣,我已经为此创建了一个Gist。
解决方案
感谢@eugenioy 和@Federico Peralta Schaffne 的回答,我可以得到我想要的最终结果。在这个问题 (Get generic type of class at runtime) 中,我还发现了来自@Jonathan 的有趣评论。在那条评论中提到了 TypeTools,那是我需要把它放在一起的最后一块。
我最终写了一个自动生成映射的小组件,并且可以 return 实现 class。
如果您知道库中是否存在这样的组件,请告诉我。这实际上是我首先要搜索的内容(我会将我的答案标记为正确答案,但如果您在 public 存储库的维护库中找到这样的组件,我很乐意将您的答案设为正确的一个,它只需要能够做我的组件可以做的事情。
为了获取 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);
}
}
class ImplementationResolver
获取所有实现 Publisher
的 beans 并使用 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());
}
}
为了测试它是否按预期工作,我有这个 class:
@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");
}
}
当我 运行 我得到这个结果的程序(FacebookPublisher
和 TwitterPublisher
写 FACEBOOK 和 TWITTER respectevly):
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 提供的解决方案?
此解决方案在实施 classes 时不需要任何额外信息。另一方面,它需要特殊的设置,但我认为这是值得的。也可以在其他地方使用 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
}
}