У нас и rich client, и сервер активно используют Spring. И очень быстро возникла проблема — как использовать спринг бины из обычных классов (которые сами — не бины).
Сначала возникли две идеи — передавать им нужные бины как аргументы в конструкторе или использовать какое то статическое поле для хранения Spring context.
Первая идея была признана порочной. Получается, что ныжные сервисы надо тянуть через длинную череду конструкторов.
Вторая идея тоже была признана порочной — возникает вопрос кто и когда будет инициализировать это поле и что будет происходить с юнит тестами.
Вскоре я нагуглил в интернетах такой красивый вариант:
И это работает отлично.
Однако же для юнит тестов пришлось это всё немного модифицировать.
У нас есть тесты, которые создают спринг контекст. Поэтому я добавил в этот класс такое метод:
Во вторых, если юнит тест не создаёт спринг котекст, но тестируемый класс использует StaticContextHolder, надо чтобы зависимости он получал из него.
Я сделал свой фиктивный контекст:
Теперь инициализация юнит теста выглядит так:
Теперь остаётся еще одна проблема: prototype beans, которые создаются через init method, например
Для этого заведём в FakeBeanfactory еще один Map:
и перепишем один метод:
и инициализируем этот Map статических методов его в инициализации юнит теста.
Ну вот. Как то так.
Буду рад, если кто то подскажет как лучше решать все эти проблемы.
Сначала возникли две идеи — передавать им нужные бины как аргументы в конструкторе или использовать какое то статическое поле для хранения Spring context.
Первая идея была признана порочной. Получается, что ныжные сервисы надо тянуть через длинную череду конструкторов.
Вторая идея тоже была признана порочной — возникает вопрос кто и когда будет инициализировать это поле и что будет происходить с юнит тестами.
Вскоре я нагуглил в интернетах такой красивый вариант:
@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.example.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 статических методов его в инициализации юнит теста.
Ну вот. Как то так.
Буду рад, если кто то подскажет как лучше решать все эти проблемы.