Spring и @Autowired для ENUM-типов. Факультатив

Как известно, в Spring нельзя сделать бины для перечисляемых типов без «костылей» — у этого типа «нет» конструктора. Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'demoEnum0' defined in file […\DemoEnum0.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [ru.itbasis.demo.spring.enums.DemoEnum0]: No default constructor found; nested exception is java.lang.NoSuchMethodException: ru.itbasis.demo.spring.enums.DemoEnum0.()

(коммит)В данном посте я попробую обойти это ограничение.

Шаг 1. Подменяем factory-метод Создадим класс EnumHandlerBeanFactoryPostProcessor, реализующий интерфейс BeanFactoryPostProcessor. @Component public class EnumHandlerBeanFactoryPostProcessor implements BeanFactoryPostProcessor { private static final transient Logger LOG = LoggerFactory.getLogger (EnumHandlerBeanFactoryPostProcessor.class.getName ());

@Override public void postProcessBeanFactory (final ConfigurableListableBeanFactory beanFactory) throws BeansException { final String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames ();

for (String beanDefinitionName: beanDefinitionNames) { final BeanDefinition beanDefinition = beanFactory.getBeanDefinition (beanDefinitionName); LOG.debug («beanDefinitionName: {}», beanDefinitionName);

final String beanClassName = beanDefinition.getBeanClassName (); LOG.debug («beanClassName: {}», beanClassName);

try { final Class beanClass = Class.forName (beanClassName); if (beanClass.isEnum ()) {

LOG.trace («found ENUM class: {}», beanClass); LOG.trace («interfaces: {}», beanClass.getInterfaces ());

beanDefinition.setFactoryMethodName («values»); }

} catch (ClassNotFoundException e) { LOG.error (e.getMessage (), e); } } } } Здесь мы ищем в будущих бинах все классы типа ENUM и заменяем для них factory-конструктор на вызов метода «values» от бина. Теперь результатом при создании данного бина будет не ошибка, а массив из его констант.(коммит)

Но этого недостаточно — Spring не даст просто взять, да и подсунуть созданный бин, ибо нельзя получить такой бин по его типу.«Ну да лиха беда начало», сказал Иван-дурак, да пошёл копать дальше.

Шаг 2. «Черновые» работы «Обернём» наш enum-тип в интерфейс и в тестируемом классе изменим тип с enum-типа на Set: public interface IEnum { } @Component public class SpringEnum {

@Autowired (required = false) private Set fieldEnumSet;

public Set getFieldEnum () { return fieldEnumSet; } } И добавим новую аннотацию @EnumAnnotation на базе аннотации @Component @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.TYPE) @Component public @interface EnumAnnotation { } @EnumAnnotation public enum DemoEnum0 implements IEnum { VALUE_0, VALUE_1 } (коммит)Шаг 3 Заставляем Spring сделать Autowire для нашего бина Создадим BeanPostProcessor, который «осведомим» о контексте: @Component public class EnumBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware { private ApplicationContext context;

@Override public Object postProcessBeforeInitialization (final Object bean, final String beanName) throws BeansException { return bean; }

@Override public Object postProcessAfterInitialization (final Object bean, final String beanName) throws BeansException { return bean; }

@Override public void setApplicationContext (final ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } } Нам понадобится метод «getEnums», возвращающий список объектов значений реализующих интерфейс IEnum:

private Set getEnums () { final Map enumMap = context.getBeansWithAnnotation (EnumAnnotation.class); LOG.debug («enumMap.size: {}», enumMap.size ());

Set result = new HashSet(); for (Object o: enumMap.values ()) {

if (o.getClass ().isArray ()) { final IEnum[] o1 = (IEnum[]) o; Collections.addAll (result, o1);

} else { result.add ((IEnum) o);

} }

LOG.debug («result: {}», result); return result; } тест на внимательность Внимательный читатель обратит внимание, что я добавляю не только список значений, но и простые объекты в список — это сделано по причине того, что таким образом можно заанотировать не только enum-типы, но и любой класс для данного интерфейса.

Disclamer: проверку на то, что возвращённые бины точно реализуют интерфейс IEnum я описывать в данном примере не стал.Добавляем метод «isAutowiredEnumSetField», проверяющий, что поле бина ожидает инъекции:

@SuppressWarnings («unchecked») private boolean isAutowiredEnumSetField (Field field) { if (! AnnotatedElementUtils.isAnnotated (field, Autowired.class.getName ())) { return false; }

final Class fieldType = field.getType ();

if (! Set.class.isAssignableFrom (fieldType)) { return false; }

final ParameterizedType type = (ParameterizedType) field.getGenericType (); final Type[] typeArguments = type.getActualTypeArguments ();

final Class aClass = (Class) typeArguments[0];

return aClass.isAssignableFrom (IEnum.class); } Ну и, наконец, пробегаемся по полям бина и делаем инъекцию:

@Override public Object postProcessBeforeInitialization (final Object bean, final String beanName) throws BeansException { LOG.debug («bean: {}», bean); LOG.debug («beanName: {}», beanName);

final Set enums = getEnums (); if (enums.size () < 1) { return bean; }

final Class beanClass = bean.getClass (); final Field[] fields = beanClass.getDeclaredFields ();

for (Field field: fields) {

if (isAutowiredEnumSetField (field)) { LOG.trace («field inject values.»); field.setAccessible (true); ReflectionUtils.setField (field, bean, enums); } }

return bean; } Запускаем тест и проверяем результат нашей работы.(коммит)

© Habrahabr.ru