I have two interfaces. One interface contains information, and the second interface is supposed to use the first interface. The second interface have one generic(s) that has to be a implementation of the first interface.
I want to automatically use the implementation of the second interface depending on what implementation of the first interface I receive.
Let me show the interfaces. (I changed domain and simplified it, but you get the basic idea.)
//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);
}
Lets assume I would have these implementations of PubInfo ...
public class FacebookPubInfo implements PubInfo {
// ...
}
.
public class TwitterPubInfo implements PubInfo {
// ...
}
...and these of 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> {
// ...
}
You get the basic idea, two interfaces with two implementations each.
To the question, finally
Now I'll come to the tricky part for me, and that is that I want to be able to automatically use TwitterPublisher when my service gets a TwitterPubInfo.
I can do that with manual mapping, as you see in the example below, but I can't help to think that it would exist a way to do this more automatically, and not depending upon manual mapping. I use spring, and I think that in there, somewhere it would exist a tool to help me with this, or maybe some other utility class, but I can't find anything.
@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);
}
}
I could at least populate publishers
in PublishingService
with reflections, right?
Do I need to do it myself, or is there any help somewhere else with this?
Or, maybe you think the approach is wrong, and that there exists a smarter way to accomplish what I need to do here, feel free to say that and tell me your superior way :p of doing things (really, I appreciate it).
edit 1 start
When writing custom eventhandlers in spring it finds the correct implementation, and that is where I got my inspiration to this question.
This is from that page:
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
}
}
Can I get the same functionality, somehow?
end edit 1
Are your Publisher implementations Spring beans?
In that case, you can get all of those using:
Map<String, Publisher> pubs = context.getBeansOfType(Publisher.class);
And then you can ask each Publisher
if it will accept the PubInfo
you received (that would mean adding a new method to Publisher
so that each publisher can decide which PubInfo
it can process).
This solution would avoid the manual mapping, and each Publisher would encapsulate the information related to what it can process.
You can also use an annotation in each Publisher
class, then get all beans that have that annotation (and the annotation can indicate the particular class that Publisher
can process). It's a similar approach but perhaps you'd find it nicer with annotations.
What you would like to do is this below... but that does not work as far as I know. The solution I suggest comes close to that.
// does not work...
context.getBeansOfType(Publisher<pubInfo.getClass()>.class);
Spring actually autowires beans of an abstract type to a map, with the key being the bean name and the value being the actual bean instance:
@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);
}
}
For this to work, you'd need to set the bean name of every publisher to match the simple class name of its corresponding PubInfo
concrete implementation.
One way to do it is by means of Spring's @Component
annotation:
@Component("FacebookPubInfo")
public class FacebookPublisher implements Publisher<FacebookPubInfo> {
@Override
public void publish(FacebookPubInfo pubInfo, String message) {
// ... do something
}
}
Then you'd only need to make Spring scan this class and follow the same approach with the TwitterPubInfo
class.
Note: If you're using XML configuration, you could use the same approach. Instead of using @Component
and scanning your classes, just set every publisher's bean name explicitly in the XML.
If you are interested, I have created a Gist for this.
Thanks to @eugenioy and @Federico Peralta Schaffne answers I could get the endresult I wanted. I also found an interesting commet from @Jonathan in this question ( Get generic type of class at runtime ) . In that comment TypeTools were mentioned, and that was the last piece I needed to put this togheter.
I ended up writing a small component that automatically makes the mapping, and can return the implementing class.
If you know if there exists such component in a library, please let me know . That was actually what I was searching for in the first place (I'll mark my answer as the correct one, but if you find such a component in a maintained library in a public repo, I gladly make your answer the correct one, it just have to be able to do what my component can do).
To get TypeTools I added this to my pom:
<dependency>
<groupId>net.jodah</groupId>
<artifactId>typetools</artifactId>
<version>0.4.3</version>
</dependency>
Now the implementaion of PublishingService
looks like this:
@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);
}
}
The bean publisherImplementationResolver
is constructed by the 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);
}
}
The class ImplementationResolver
gets all beans that implements Publisher
and uses TypeTools to map the specified generic (or maybe applied type is more correct to say).
/**
* 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());
}
}
To test that this works as intended I have this 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");
}
}
When I run the program I get this result ( FacebookPublisher
and TwitterPublisher
writes FACEBOOK and 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
Why choose this instead of the solution provided by Federico Peralta Schaffne?
This solution does not need any extra information in the implementing classes. On the other hand it requires a special setup, but I think it is worth while. It is also possible to use the ImplementationResolver
in other places, as it is a separate component. It also works automatically once it is in place.
If I would trust my collegues I could go with the solution provided by Federico Peralta Schaffne (not all of them wants to understand Spring and tdd), it feels a bit more lightweight then mine solution. Refactoring might cause some problems if the name of the bean is in a string (but Intellij would find it, maybe other ide:s as well).
Now the component is limited to interfaces with only one generic, but with a signature like in the code below it could cover more use cases. It would still be tricky with something like this AdvancedPublisher<Map<T>, G<T>>
, so it will still not be perfect, but better. Since I don't need that, I have note implemented it. It could be done with two layers of different collections, so if you would need that, it would not be to complicated to do.
public class ImplementationResolver<T> implements ApplicationContextAware {
// ...
public ImplementationResolver(Class<T> ... toBeImplemented) {
this.toBeImplemented = toBeImplemented;
}
// ...
public T getImplementaion(Object ... whatImplementationIsDoneForMe) {
// .... implementation
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.