Recently a project I was working on required the ability to load up a number of unknown services that implemented a Calendar Provider Interface. As this was a Spring server, I started looking around trying to find the right way to do this. I began by looking at being able to add an annotation to a class to identify the class as a Calendar Service Provider.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CalendarServiceProvider {
}
Nothing fancy about the annotation, just a flag on the class at runtime. Next was a way to find the instances with the annotation.
ClassPathScanningCandidateComponentProvider provider
= new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(CalendarServiceProvider.class));
Set<BeanDefinition> providers = provider.findCandidateComponents("com.blue.project");
Now this was much easier than I had thought it would be. I scan com.blue.project for any classes with the annotation type of CalendarServiceProvider.class. However this gives me a BeanDefinition for the class. After a little trial and error I came up with the following:
providers.stream().map(BeanDefinition::getBeanClassName).map(name -> {
return name.substring(name.lastIndexOf('.')+1);
}).map(beanName -> {
return Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
}).map(beanName -> {
return applicationContext.getBean(beanName);
}).map(bean -> {
if(bean instanceof CalendarServiceProviderInterface) {
return (CalendarServiceProviderInterface)bean;
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList());
Couple of things to point out I take the simple name of the class, then I convert the first character to lowercase, and this allows me to load an existing bean of that type. Then for good measure I run it through another map to check verify it has the Interface I want implemented on the bean, if it does I pass it along, else I return a null. Then a final filter to remove the nulls, and then collect everything else to a list.
My completed method looks like this.
@Autowired
private ApplicationContext applicationContext;
private List<CalendarServiceProviderInterface> findCalendarServiceProviders() {
ClassPathScanningCandidateComponentProvider provider
= new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(CalendarServiceProvider.class));
Set<BeanDefinition> providers = provider.findCandidateComponents("com.blue.project");
return providers.stream().map(BeanDefinition::getBeanClassName).map(name -> {
return name.substring(name.lastIndexOf('.')+1);
}).map(beanName -> {
return Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1);
}).map(beanName -> {
return applicationContext.getBean(beanName);
}).map(bean -> {
if(bean instanceof CalendarServiceProviderInterface) {
return (CalendarServiceProviderInterface)bean;
}
return null;
}).filter(Objects::nonNull).collect(Collectors.toList());
}
You will need to autowire in the ApplicationContext. Simple enough of a process to implement.
Discover more from GhostProgrammer - Jeff Miller
Subscribe to get the latest posts sent to your email.