piątek, 15 stycznia 2010

ArithmeticOperationFactoryBean

W ostatnim projekcie miałem potrzebę zdefiniowania w konfiguracji Spring prostych arytmetycznych zależności między wartościami propertiesów. Nie ma sensu zdefiniowanie maksymalnej ilości wątków w puli ThreadPoolTaskExecutor gdy nie skorelujemy jej z ilością połączeń puli do bazy danych (przy założeniu że nasze taski korzystają z bazy danych). W moim przypadku poszczególny task potrzebował dokładnie 2 połączenia do bazy danych.
Chciałem mieć możliwość wyrażenia, że dane property ma mieć wartość 2 * wartość innego property.
Po krótkim googlowaniu nie udało mi się nic znaleźć i stwierdziłem, że łatwo samemu coś takiego napisać. W tym celu stworzyłem własną klasę FactoryBean, której zadaniem było wykonywanie operacji mnożenia na injectowanych parametrach.

private TypeConverter typeConverter = new SimpleTypeConverter();

public void setParam1(Object param1) {
this.param1 = param1;
}

public void setParam2(Object param2) {
this.param2 = param2;
}

public Object getObject() throws Exception {
Object result = null;
BigDecimal param1Converted = convert(param1);
BigDecimal param2Converted = convert(param2);
BigDecimal result = param1Converted.multiply(param2Converted);
return result;
}

private BigDecimal convert(Object obj) {
return (BigDecimal) typeConverter.convertIfNecessary(obj, BigDecimal.class);
}
public Class getObjectType() {
return BigDecimal.class;
}

Wyglądało to całkiem,całkiem ale pomyślałem, że bardzo prosto byłoby zbudować bardziej generyczne rozwiązanie. Implementacja FactoryBean dla innej 2-argumentowej operacji byłaby prawie identyczny, a jedynie różniłaby się oepracją na klasie BigDecimal. Dodatkowo chciałem mieć możliwość specyfikowania typu danych wyniku operacji arytmetycznej.
Generyczny ArithmeticOperationFactoryBean:

public abstract class ArithmeticOperationFactoryBean implements FactoryBean,
InitializingBean {
private TypeConverter typeConverter = new SimpleTypeConverter();
private Object param1;
private Object param2;
private Class<?> resultType;

public void setResultType(Class<?> resultType) {
this.resultType = resultType;
}

public void setParam1(Object param1) {
this.param1 = param1;
}

public void setParam2(Object param2) {
this.param2 = param2;
}

@Override
public void afterPropertiesSet() throws Exception {
if (param1 == null || param2 == null) {
throw new IllegalArgumentException(
"operation arguments can not be null");
}
if(resultType == null){
resultType = BigDecimal.class;
}
}

@Override
public Object getObject() throws Exception {
Object result = null;
BigDecimal param1Converted = convert(param1);
BigDecimal param2Converted = convert(param2);
BigDecimal operationResult = executeOperation(param1Converted,
param2Converted);
result = operationResult;
if (resultType != null) {
result = convert(operationResult, resultType);
}
return result;
}

protected abstract BigDecimal executeOperation(BigDecimal param1Converted,
BigDecimal param2Converted);

@SuppressWarnings("unchecked")
private <T> T convert(Object obj, Class type) {
return (T) typeConverter.convertIfNecessary(obj, type);
}

private BigDecimal convert(Object param) {
return convert(param, BigDecimal.class);
}

@Override
public Class<?> getObjectType() {
return resultType;
}

@Override
public boolean isSingleton() {
return true;
}

}

FactoryBean odpowiedzialny za operacje:

public class MultiplicationFactoryBean extends ArithmeticOperationFactoryBean {

@Override
protected BigDecimal executeOperation(BigDecimal param1,
BigDecimal param2) {
return param1.multiply(param2);
}
}

Użycie takiego FactoryBean w konfiguracji wygląda w następujący sposób:

















Konfiguracja definiuje w context dwa beany:"foo" typu java.lang.Double oraz "myInteger" typu java.lang.Integer. Wartość foo jest 24.

Zamiast dziedziczenia może lepiej byłoby zastosować tu wzorzec strategy, ale jest to w takim przypadku chyba pomijalne...
Dodatkowo, ekspsresywność rozwiązania nie rzuca na kolana, ale dla prostych zastosowań nadaję się moim zdaniem co najmniej przyzwoicie.