简体   繁体   中英

How to determine that the ApplicationConfig.class is marked with a custom annotation

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

  • What is this EnhancedBySpring ?
  • How does Spring add functionality? The @EnableJpaRepositories adds repositories. I would like to replicate this functionality for my own purposes.

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.

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));
}

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.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM