Oleg Atamanenko

thoughts about programming

Пишем @Enable*-аннотацию для Spring

Начиная с версии 3.1 Spring поддерживает декларативное включение необходимой функциональности через, так называемые, @Enable* аннотации. Пример таких аннотаций: org.springframework.web.servlet.config.annotation.EnableWebMvc, org.springframework.cache.annotation.EnableCaching, org.springframework.scheduling.annotation.EnableAsync и другие.

В продолжение темы прошлого поста, я хочу показать, как можно добавить собственную @Enable аннотацию.

Для автоматического создания клиента для REST-ресурса нам необходима следующая информация:

  1. Пакет, в котором искать интерфейсы, проаннотированные аннотацией @Path()
  2. Базовый адрес REST-приложения.

Для того, чтобы передать эту информацию, мы создадим два атрибута в аннотации: scanPackage() и endpoint()

Кроме того, для обработки аннотации необходимо указать обработчик. Это можно сделать передав подкласс org.springframework.context.annotation.ImportBeanDefinitionRegistrar через аннотацию @Import

Пишем аннотацию:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(JaxRsRestClientRegistrar.class)
public @interface EnableJaxRsRestClient {

    String scanPackage();

    String endpoint();
}

Теперь осталось написать реализацию интерфейса org.springframework.context.annotation.ImportBeanDefinitionRegistrar. Так как у нас уже есть написанный ранее BeanFactoryPostProcessor в виде RestClientPostProcessor, реализация интерфейса будет простой - мы создадим описание бина для Spring и зарегистрируем его в реестре бинов.

public class JaxRsRestClientRegistrar implements ImportBeanDefinitionRegistrar {

    protected static final String PROPERTY_ENDPOINT = "endpoint";

    protected static final String PROPERTY_SCAN_PACKAGE = "basePackage";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        EnableJaxRsRestClient annotation =
                (EnableJaxRsRestClient) ((StandardClassMetadata) importingClassMetadata).getIntrospectedClass()
                        .getAnnotation(EnableJaxRsRestClient.class);
        String scanPackage = annotation.scanPackage();
        String endpoint = annotation.endpoint();

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setAbstract(false);
        beanDefinition.setBeanClass(RestClientPostProcessor.class);
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.add(PROPERTY_ENDPOINT, endpoint);
        propertyValues.add(PROPERTY_SCAN_PACKAGE, scanPackage);
        beanDefinition.setPropertyValues(propertyValues);

        registry.registerBeanDefinition(ClassUtils.getShortName(getClass()), beanDefinition);
    }
}

Теперь мы можем использовать аннотацию:

@Configuration
@EnableJaxRsRestClient(scanPackage = "<base package>", endpoint = "${remote.rest.endpoint}")
class TestAutowiringConfiguration {

    @Bean
    public static PropertyPlaceholderConfigurer configurer() {
        PropertyPlaceholderConfigurer pph = new PropertyPlaceholderConfigurer();
        pph.setLocation(new ClassPathResource("application.properties"));
        return pph;
    }

}

Обратите внимание, что endpoint можно передавать с использованием подстановочных свойств.