Reputation: 1662
I would like to enable application features based on the presence of a custom annotation that marks the ApplicationConfig.class
as below:
@FooBar(enabled = true)
@Configuration
@ComponentScan(basePackageClasses = Application.class, excludeFilters = @Filter({Controller.class, Configuration.class}))
@EnableJpaRepositories("com.package.repository")
class ApplicationConfig {
// Application specific configs and bean defs.
}
The custom annotation is named FooBar
:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FooBar {
boolean enabled() default true;
}
During application startup I would like to detect that this class (or any other class/bean) is annotated with this annotation.
Here is my attempt, partly based on this similar question it includes two ways to determine that the annotation is being used.
@Component
public class MyClassWithEventListeners implements ApplicationContextAware {
@Autowired
ApplicationEventPublisher applicationEventPublisher;
@Autowired
ApplicationContext applicationContext;
@EventListener
void contextRefreshedEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
applicationContext.getClassLoader();
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
String[] names = event.getApplicationContext().getBeanDefinitionNames();
for (String name : names) {
Object o = autowireCapableBeanFactory.getBean(name);
if (AopProxyUtils.ultimateTargetClass(o).isAnnotationPresent(FooBar.class)) {
System.out.println("Found class annotated with FooBar");
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
List<String> beanNames = getBeansWithAnnotation(FooBar.class);
if(beanNames !=null){
System.out.println("Found class annotated with FooBar");
}
}
public List<String> getBeansWithAnnotation(Class<? extends Annotation> type) {
Predicate<Map<String, Object>> filter = Predicates.alwaysTrue();
return getBeansWithAnnotation(type, filter);
}
public List<String> getBeansWithAnnotation(Class<? extends Annotation> type, Predicate<Map<String, Object>> attributeFilter) {
List<String> result = Lists.newArrayList();
ConfigurableListableBeanFactory factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
for (String name : factory.getBeanDefinitionNames()) {
BeanDefinition bd = factory.getBeanDefinition(name);
if (bd.getSource() instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) bd.getSource();
Map<String, Object> attributes = metadata.getAnnotationAttributes(type.getName());
if (null == attributes) {
continue;
}
if (attributeFilter.apply(attributes)) {
result.add(name);
}
}
}
return result;
}
}
Both the contextRefreshedEvent()
and setApplicationContext()
methods are called and neither are able to detect the custom annotation.
What I have observed is that my ApplicationConfig.class
is present in the list of beans/classes but appears as follows:
com.package.config.ApplicationConfig$$EnhancerBySpringCGLIB$$15073fb3@196887
EnhancedBySpring
?@EnableJpaRepositories
adds repositories. I would like to replicate this functionality for my own purposes.Upvotes: 0
Views: 506
Reputation: 1662
I have looked in to this further and found two possible solutions. The first simply detects if the custom annotation is present:
@Component
public class MyClassWithEventListeners {
@EventListener
void contextRefreshedEvent(ContextRefreshedEvent event) throws NoSuchFieldException, IllegalAccessException {
ApplicationContext applicationContext = event.getApplicationContext();
String[] annotations = applicationContext.getBeanNamesForAnnotation(FooBar.class);
if (annotations != null && annotations.length > 0) {
System.out.println("Annotation found");
} else {
System.out.println("Annotation not found");
}
}
}
The second uses reflection to get the value set in the annotation. In this case the value of enabled
.
@Component
public class MyClassWithEventListeners {
@EventListener
void contextRefreshedEvent(ContextRefreshedEvent event) throws NoSuchFieldException, IllegalAccessException {
ApplicationContext applicationContext = event.getApplicationContext();
AnnotationConfigWebApplicationContext annotationContext = ((AnnotationConfigWebApplicationContext) applicationContext);
for (Field field : annotationContext.getClass().getDeclaredFields()) {
if ("annotatedClasses".equals(field.getName())) {
field.setAccessible(true);
Set<Class<?>> classes = (Set<Class<?>>) field.get(annotationContext);
for (Class clazz : classes) {
for (Annotation annotation : clazz.getDeclaredAnnotations()) {
if (annotation.annotationType().isAssignableFrom(FooBar.class)) {
enabled = ((ServiceRegistration) annotation).enabled();
System.out.println("Enabled: " + enabled);
}
}
}
}
}
}
}
This seem a little clumsy. I wonder if there is not a more elegant way to resolve this issue.
Upvotes: 0
Reputation: 77
Maybe you can use utilities provided by Class instead of utils provided by AoP?
@Test
public void testAccess(){
SomeAnnotatedClass annotatedClassInstance = new SomeAnnotatedClass();
Assert.assertNotNull(annotatedClassInstance.getClass().getAnnotation(FooBar.class));
}
Upvotes: 0
Reputation: 33141
The core container has actually support for that stuff so there is no need to hack the context. You may benefit from reading what's happening when one adds @EnableXYZ
on a configuration class. For instance @EnableCaching
or @EnableJms
work pretty much the same way:
The interface is meta-annotated with @Import
which leads to more beans being loaded by the context. The enabled
part is a bit useless IMO. The presence or the absence of the annotation plays the same role and is much more explicit.
Upvotes: 2