...

вторник, 15 июля 2014 г.

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.<init>()





(коммит)

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



Шаг 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<IEnum>:

public interface IEnum {
}



@Component
public class SpringEnum {

@Autowired(required = false)
private Set<IEnum> fieldEnumSet;

public Set<IEnum> 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<IEnum> getEnums() {
final Map<String, Object> enumMap = context.getBeansWithAnnotation(EnumAnnotation.class);
LOG.debug("enumMap.size: {}", enumMap.size());

Set<IEnum> result = new HashSet<IEnum>();
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<? 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<IEnum> 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;
}


Запускаем тест и проверяем результат нашей работы.

(коммит)


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий