How to scan Spring components in OSGi
TL;DR
Spring cannot scan components in OSGi environment. This problem come from the fact that Spring uses classpath scanning to find components. In OSGi environment, classes are loaded from bundles. So, Spring cannot find them.
Workaround
To solve this problem we cannot use @ComponentScan
annotation. Instead, we need to use @Import
annotation to import all the components we need.
It’s not a good solution, but it works. However, it is not a good practice to import all the components manually. So, we need to find a better solution.
We need to:
- find a way to scan components in OSGi environment
- add found components to the Spring context
Solution
- adding classgraph library to the project which is able to scan classes in OSGi environment. It can be found here: https://github.com/classgraph/classgraph.
- we will not ovveride
doScan
method inClassPathBeanDefinitionScanner
class from Spring Framework. Instead, a more simpler solution is inject classes annotated with@Component
annotation to@Import
annotation:Import importAnnotation = PortalConfig.class.getAnnotation(Import.class); PortalUtils.changeAnnotationValue(importAnnotation, "value", getSpringComponents()); public static Class<?>[] getSpringComponents() { List<Class<?>> list = null; try (ScanResult scanResult = new ClassGraph() // .acceptPackages("co.icreated.portal") // .enableAnnotationInfo() .enableClassInfo().scan()) { ClassInfoList beans = scanResult.getClassesWithAnnotation(Component.class); list = beans .filter(classInfo -> classInfo.getName().startsWith("co.icreated") && !classInfo.getName().endsWith("PortalConfig")) .loadClasses(); } return list.toArray(new Class<?>[list.size()]); }
Value of @Import
annotation can be injected with helper method changeAnnotationValue
:
public static void changeAnnotationValue(Annotation annotation, String attributeName, Object newValue) {
try {
Field f = annotation.getClass().getDeclaredField("memberValues");
f.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) f.get(annotation);
memberValues.put(attributeName, newValue);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Not forget to add @Import
annotation to the configuration class with empty value:
@Configuration
@Import({})
public class PortalConfig {
...
}
Conclusion
Hope that this solution will help you to solve the same problem when integrating Spring Framework with OSGi environment!