У нас и rich client, и сервер активно используют Spring. И очень быстро возникла проблема — как использовать спринг бины из обычных классов (которые сами — не бины).
Сначала возникли две идеи — передавать им нужные бины как аргументы в конструкторе или использовать какое то статическое поле для хранения Spring context.
Первая идея была признана порочной. Получается, что ныжные сервисы надо тянуть через длинную череду конструкторов.
Вторая идея тоже была признана порочной — возникает вопрос кто и когда будет инициализировать это поле и что будет происходить с юнит тестами.
Вскоре я нагуглил в интернетах такой красивый вариант:
<a rel="nofollow" name="habracut">
@Service public class StaticContextHolder implements BeanFactoryAware { public static BeanFactory CONTEXT; public StaticContextHolder() { } public static Object getBean(String s) throws BeansException { return CONTEXT.getBean(s); } public static <T> T getBean(String s, Class<T> tClass) throws BeansException { return CONTEXT.getBean(s, tClass); } public static <T> T getBean(Class<T> tClass) throws BeansException { return CONTEXT.getBean(tClass); } public static Object getBean(String s, Object... objects) throws BeansException { return CONTEXT.getBean(s, objects); } public static boolean containsBean(String s) { return CONTEXT.containsBean(s); } @Override public void setBeanFactory(BeanFactory applicationContext) throws BeansException { logger.assertNull(CONTEXT, "CONTEXT is not null. Double Spring context creation?"); CONTEXT = applicationContext; } }
И это работает отлично.
Однако же для юнит тестов пришлось это всё немного модифицировать.
У нас есть тесты, которые создают спринг контекст. Поэтому я добавил в этот класс такое метод:
@PreDestroy public void resetStatics() { CONTEXT=null; }
Во вторых, если юнит тест не создаёт спринг котекст, но тестируемый класс использует StaticContextHolder, надо чтобы зависимости он получал из него.
Я сделал свой фиктивный контекст:
public class FakeBeanFactory implements BeanFactory { private Map<String, Object> beans; public FakeBeanFactory (Map<String, Object> beans) { this.beans = beans; } @Override public Object getBean(String s) throws BeansException { return beans.get(s); } @Override public <T> T getBean(String s, Class<T> tClass) throws BeansException { return (T) beans.get(s); } @Override public <T> T getBean(Class<T> tClass) throws BeansException { return (T) beans.get(tClass.getName()); } @Override public Object getBean(String s, Object... objects) throws BeansException { return beans.get(s); } @Override public boolean containsBean(String s) { return false; // мне он не нужен } @Override public boolean isSingleton(String s) throws NoSuchBeanDefinitionException { return false; // мне он не нужен } @Override public boolean isPrototype(String s) throws NoSuchBeanDefinitionException { return false; // мне он не нужен } // .... }
Теперь инициализация юнит теста выглядит так:
@Before public void init() { Map<String,Object> beans = new Map<String,Object>(); beans.put("service-dependency", new MockupDependencyImpl()); StaticContextHolder.CONTEXT = new FakeBeanFactory(beans)); }
Теперь остаётся еще одна проблема: prototype beans, которые создаются через init method, например
<bean id="PlanDefinitionReader" class="com.cadence.mdv.vplanDef.PlanDefinitionReader" scope="prototype" factory-method="createPlanDefinitionReader"> <constructor-arg index="0" value="null"/> <constructor-arg index="1" value="null"/> <constructor-arg index="2" value="null"/> </bean>
Для этого заведём в FakeBeanfactory еще один Map:
Map<String s, Method m> initMethods
и перепишем один метод:
public Object getBean(String s, Object... objects) throws BeansException { return initMethods.get(s).invoke(null, objects); }
и инициализируем этот Map статических методов его в инициализации юнит теста.
Ну вот. Как то так.
Буду рад, если кто то подскажет как лучше решать все эти проблемы.
Автор: javax