Reputation: 756
This is more of a design question than a code implementation but I am hoping someone can help out.
The issue I am facing is I will need to inject a modelSerivce depending on two inputs. I am pretty sure based on some documentation and SO questions I want to use a factorybean for this.
My question goes more into the construction of these classes that will be created with the factorybean. How can I re-use a singleton bean with the factory instead of creating a new class every time the factory is called?
Here is what the code looks like:
Thing Interface:
public interface Thing {
void Run();
}
ThingA Implementation:
public class ThingA implements Thing{
public void Run() {
System.out.println("In ThingA");
}
}
ThingB Implementation:
public class ThingB implements Thing{
public void Run() {
System.out.println("In ThingB");
}
}
ThingFactory Implementation:
public class ThingFactory {
public Thing GetThing(String stateCode, Date date){
Thing result;
if(stateCode == "MA") {
result = new ThingA();
}
else {
result = new ThingB();
}
return result;
}
}
What i really want the factory to do is pull a known implementation instead of creating an implementation each time the factory is called. I also would like to not tie my factory to the Spring framework by doing something like this:
ApplicationContext.getBean()
Upvotes: 1
Views: 109
Reputation: 22553
Just keep the beans in spring context as singletons as you normally would. Inject the context into your factory and pull beans from it based on your business rules.
public class ThingFactory {
@Autowired
private ApplicationContext ctx;
public Thing GetThing(String stateCode, Date date){
Thing result;
if(stateCode == "MA") {
result = ctx.getBean("someBean")
}
else {
result = ctx.getBean("someOtherBean")
}
return result;
}
}
You can be even more clever and use a scheme to name the beans on the context:
@Service("Thing_MA")
public class ThingA implements Thing{
.
.
.
}
This buys you nice declarative look up rules for your factory:
public class ThingFactory {
public Thing GetThing(String stateCode, Date date){
return (Thing) ctx.getBean("Thing_" + stateCode);
}
}
Upvotes: 1
Reputation: 4547
If you don't want to tie your code spring framework it means you don't want to BeanFactory
and create your own factory then you will have to create objects by your self like
public class ThingFactory {
private final static Thing thingA = new ThingA();
private final static Thing thingB = new ThingB();
public Thing GetThing(String stateCode, Date date){
if(stateCode.equals("MA")) {
return thingA;
} else {
return thingB;
}
}
}
And if you have more implementations of Thing
in that case you simply create a map and get the object according to your need.
public class ThingFactory {
private final static Map<String, Thing> beanMap = new Hashmap<>()
public ThingFactory(){
addThing("ThingA", new ThingA());
addThing("ThingB", new ThingB());
}
public static Thing getThing(String name) {
return beanMap.get(name);
}
public static addThing(String name, Thing thing) {
beanMap.put(name, thing);
}
public Thing GetThing(String stateCode, Date date){
if(stateCode.equals("MA")) {
return getThing("ThingA");
} else {
return getThing("ThingB");
}
}
}
However, I suggest you to go with Spring's BeanFactory
instead of creating your own factory and go with @Robert Moskal answer.
And if the value of stateCode
is known at application startup time you can use spring profiles
to achieve so.
@Service
@Profile("ThingB")
public class ThingB implements Thing{
public void Run() {
System.out.println("In ThingB");
}
}
Upvotes: 0
Reputation: 7790
Actually I dealt with the same problem and here is the solution I came up with. The main trick that I relied to is that any bean that is defined as singletone is instanciated by Spring at the time of Spring initialization. So here is what I did:
ThingImpl
that implements your Thing
interface and will be a parent to all your ThnigA
, ThingB
... classes. (Alternatively just change your Thing
interface into abstract Class)Change your ThingFactory
to something like this:
public class ThingFactory {
private static Map<String, Thing> instancesMap = new Hashmap<>()
public static Thing getThing(String name) {
return instancesMap.get(name);
}
public static addInstance(String name, Thing thing) {
instancesMap.put(name, thing);
}
}
In your abstract parent add the following constructor
public ThingImpl() {
ThingFactory.addInstance(this.getClass().getSimpleName(), this);
}
Your factory doesn't need to be defined as bean at all, but all your Thing
classes should be defined as beans. What will happen is that when Spring initializes it will instanciate all your Thing
classes and as part of its own initialization each Thing
class will insert itself into a map in your factory. So all you have to do now is anywhere in your code you can call your Factory:
ThingFactory.getThing("ThingA");
ThingFactory.getThing("ThingB");
Upvotes: 2