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
@Autowired (required = false)
private Set
public Set
@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
Set
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 extends Type> aClass = (Class extends Type>) 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
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; } Запускаем тест и проверяем результат нашей работы.(коммит)