This is the code I have:
...
@Autowired
@Qualifier("cfs")
private ObjectProvider<ConnectionFactory> cfs;
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public ConnectionFactory cfs(Integer bookieId) {
...
I would like to be able to use this as: cfs.getObject(123)
, and first time it sees parameter 123
, it creates new bean (so it behaves like prototype). Second time it sees in my code cfs.getObject(123)
, it should return already initialized bean from the last time (so it behaves like singleton, because parameter is the same == 123). This is something like map's computeIfAbsent
.
Comment From: JintaoXIAO
you can add a custom 'Scope' to support this.
Comment From: bojanv55
@JintaoXIAO I guess it is still "SINGLETON", but in this case it is with parameters, so it is not auto-initialized, but as need when someone calls ObjectProvider<T>.getObject(123)
.
Comment From: bojanv55
I managed to do smth. like this:
@Component
public class ParametricSingletonScope implements Scope {
public final static String NAME = "parametric_singleton";
private final Map<String, Map<Integer, Object>> parametricSingletons = new ConcurrentHashMap<>();
@SneakyThrows
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Field argumentsField = objectFactory.getClass().getDeclaredFields()[3];
argumentsField.setAccessible(true);
Object[] arguments = (Object[]) argumentsField.get(objectFactory);
int argumentsHashCode = Objects.hashCode(arguments);
return parametricSingletons
.computeIfAbsent(name, n -> new ConcurrentHashMap<>())
.computeIfAbsent(argumentsHashCode, h -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return NAME;
}
}
Is there any better way that is not using reflection? Here I just use reflection to get arguments, and then take the hash of all the arguments to store has->Bean in map.
Comment From: JintaoXIAO
i think you just need a container to cache the bean you just created(by construct arguments or some other keys), and you can't inject a specific bean into another component from spring, so you can use some container like this:
package demo;
import javax.annotation.concurrent.ThreadSafe;
import java.util.concurrent.ConcurrentHashMap;
@ThreadSafe
public class MultitonBeanContainer<T> {
private final Constructor<T> constructor;
private final ConcurrentHashMap<Object[], T> container;
public MultitonBeanContainer(Constructor<T> constructor) {
this.constructor = constructor;
this.container = new ConcurrentHashMap<>();
}
public T getBean(Object ...params) {
container.computeIfAbsent(params, p -> constructor.construct(params));
return container.get(params);
}
@FunctionalInterface
public interface Constructor<T> {
T construct(Object... params);
}
}
Comment From: snicoll
@bojanv55 we can't offer such a map-based feature in the core framework as we have no way to limit the number of instances that we would cache. We prefer that such scenario to be implemented in your own arrangement, such as a custom scope implementation.
Comment From: andrew-inzer
You can use @Cacheable
:
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Cacheable(cacheNames = "connectionFactories", sync = true)
public ConnectionFactory connectionFactory(Integer bookieId) {