-1

Let's say there is an application that creates an instance of the Task class every time when it needs to process some data. The task instance have some other services injected into it but all this services and the task object itself are unique within a single task instance. Some global services are being injected too of course but they are true application wide singletons. So my question is what is the best way to configure injection of that local (or scoped) singleton instances? I am primarily thinking about using a child context but how to configure it properly is still a question to me. One more thing to mention is that I use annotations and java based configuration.

  • 1
    Please show the code that requires the `Task` instance. Will that code have access to the `ApplicationContext`? – Sotirios Delimanolis Dec 11 '13 at 15:51
  • So you need some services to be prototype and some singleton? Can you clarify what this means `The task instance have some other services injected into it but all this services and the task object itself are unique within a single task instance` – Taylor Dec 11 '13 at 15:54
  • [This](http://stackoverflow.com/questions/20009922/passing-state-to-cdi-container-managed-beans) might actually help you out. It sounds like the factory approach I describe is probably what you want. – Floegipoky Dec 11 '13 at 15:57
  • 1
    @SotiriosDelimanolis, Sure it will, ApplicationContext can be injected. The Task class is a spring managed bean. – Vitaly Tsaplin Dec 11 '13 at 16:03
  • Then just declare a single bean for `Task` class and inject into it anything you want. What are we misisng? – Sotirios Delimanolis Dec 11 '13 at 16:03
  • `"creates an instance of the Task class every time when it needs to process some data"` You're asking how to fill a prototype-scoped bean with both singleton and prototype-scoped beans? What sort of data is it processing? Or is it all contained in the prototype beans? – Floegipoky Dec 11 '13 at 16:08
  • 1
    @Taylor, The idea here is that when an instance of the Task class is being bootstrapped by IoC container some of the dependencies are considered as singletons within Tasks's object hierarchy. Another instance of Task will have its own singleton instances. So 'singletons' here are sort of scoped or isolated within Tasks. You can think about each Task instance as about a little application with its own singletons and prototypes. Does it make sense? – Vitaly Tsaplin Dec 11 '13 at 16:14
  • @user3091735 No, that doesn't make sense to me. Singletons are global across the entire scope of your classloader. Singleton-scoped beans are global across your entire spring container. Do you mean that you want to spin up a new container for each Task? That's a Really Bad Idea. – Floegipoky Dec 11 '13 at 16:18
  • @user3091735 Are you sure you don't actually want a [prototype](http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring-framework-reference/html/beans.html#beans-factory-scopes)-scoped bean injected with other prototype-scoped beans? – Floegipoky Dec 11 '13 at 16:24
  • So if a Task uses ServiceA and ServiceB and ServiceA also uses ServiceB you want ServiceA and Task to have the same instance of ServiceB per instance of Task? I think? What happens if ServiceA uses ServiceB without any Task on the callstack? Do you mean you want per-thread scope? – Taylor Dec 11 '13 at 17:34
  • @Taylor, You are very close to the point. I can give you one more example. Let's say we have a multiple-document interface (MDI) application. Each document (same as Task before) is a GUI component (window). Each has its own instance of UndoManager. You can inject UndoManager into the application object or somewhere else (outside of the windows) but it doesn't make sense, at least this situations is not important. My point is that currently we have 2 opposite things: a singleton and a prototype. A scoped singleton is something in between. It is shared within given object hierarchy. – Vitaly Tsaplin Dec 11 '13 at 17:59
  • @Floegipoky, Every instance of a prototype-scoped bean is unique. It is not what I want. I need to share an instance between objects within an object hierarchy. You might ask why I need this at all and why not to use just prototype-scoped beans. My point is that if the object hierarchy that needs a scoped singleton is quite large I would need to pass the instance manually to every bean that needs it as a dependency. In case of highly modular app a majority of injections would be made this way making IoC useless. – Vitaly Tsaplin Dec 11 '13 at 18:27

4 Answers4

1

I think custom scopes are what you're asking for. However, some words of caution: typically the pain point you're describing results from an overly tightly coupled design rather than a legitimate need to get up to your elbows in the internals of the IOC container. You might be one of the few people that actually do have that legitimate need, but it's far more likely that a redesign would solve your problem in a much cleaner way.

Floegipoky
  • 3,087
  • 1
  • 31
  • 47
  • The only obvious way to redesign it is to replace module-scoped dependencies by singletons and to lookup for contextual data in that singletons using task unique ids as keys. – Vitaly Tsaplin Dec 11 '13 at 20:36
  • Optimal design is unfortunately rarely obvious, and often takes many iterations to approach. Nevertheless I'd be willing to bet good money that the best thing you could do would be to refactor. There's probably some of these psuedo-singletons that you can make stateless and/or split into pieces to simplify your dependency graph. – Floegipoky Dec 11 '13 at 21:33
  • The abstract idea behind this visible complexity is revolving around fairly simple concept of reusable component which has dependencies shared within this component. I am wondering how would you split the car engine into pieces in order to move it away from the car and then install it as a standalone facility wiring it up back to the car by utilising gears or something similar :) What I am really trying to avoid is to break a logical component-centric abstraction just to make IoC happy. The last resort would be to just inject dependencies by hands. – Vitaly Tsaplin Dec 11 '13 at 22:08
  • Not everything needs to know about the entire engine. The exhaust doesn't care about the crankshaft. An engine is just a series of entry points and paths, and most of the things that connect to it only require knowledge of a small piece. Keep your components, but don't be afraid to look at the pieces they're built from with a critical eye, or consider that maybe the lines between components aren't where they should be. – Floegipoky Dec 12 '13 at 16:01
  • How would you design the injection of CarEngine into CarComputer and CarBody then? – Vitaly Tsaplin Dec 12 '13 at 17:27
  • The engine converts fuel into linear force and linear force into rotational force. The body is concerned with airflow and space. Are these things related? Does the body really care about the engine, or just that it needs to have a space in the front? – Floegipoky Dec 12 '13 at 18:33
  • 1
    CarEngine is an interface with start() and stop() methods. Fuel and forces are implementation details :) – Vitaly Tsaplin Dec 12 '13 at 19:04
  • Now you're talking, but I'd go even further and argue that the car itself is an interface and the engine is an implementation detail. – Floegipoky Dec 12 '13 at 19:09
  • @VitalyTsaplin or maybe transportation is what you really care about and the car is an implementation detail. – Floegipoky Dec 12 '13 at 20:01
  • All this design considerations are good and you may think of fuel, forces and transportation but whatever you come up with the question is still the same: what would be the best solution for injecting component-level dependencies with Spring (or Guice)? Those are not singletons neither prototypes. – Vitaly Tsaplin Dec 13 '13 at 05:03
0
 private static final SingletonObject singleton = new SingletonObject();

Private to make only accessible locally. Static to make only one. Final to stop it getting changed.

If you needed to prevent other people creating more SingletonObjects I would need more context info and may not be possible - but in most cases not blocking that is not actually a problem.

Tim B
  • 40,716
  • 16
  • 83
  • 128
  • I think the goal is to get the instance from Spring's container. – Sotirios Delimanolis Dec 11 '13 at 16:02
  • In which case its not a local instance, its a local reference to the global instance. Trying to use Spring for this seems like massive amounts of work for something that can be a single line. The only possible gain is dependency injection, you would need a bit more work to support that but its still only a couple of lines. – Tim B Dec 11 '13 at 16:12
  • @TimB, It is not a couple of lines actually. You could have quite complex dependency graph which could be different depending on selected profile. – Vitaly Tsaplin Dec 11 '13 at 19:04
0

If i am not wrong , you wanted to have
"Task" class two instance variables as
"SubTask" should be only one instance per Task instance
"GlobalTask" Should be only one instance per Application / Spring IOC Context
And Each time you create "Task" instance you want to create unquie "SubTask" and use the same GlobalTask instance.

if this is true , i dont understand what is the problem
you can achieve by declaring "Task" and "SubTask" as prototype and "GlobalTask" as default as SingleTon as below

@Component("Task")

@Scope("prototype") public class Task {

public Task(){
    System.out.println("Task Created");
}

@Autowired
SubTask subTask;

@Autowired
GlobalTask globalTask;

public GlobalTask getGlobalTask() {
    return globalTask;
}

public void setGlobalTask(GlobalTask globalTask) {
    this.globalTask = globalTask;
}

public SubTask getSubTask() {
    return subTask;
}

public void setSubTask(SubTask subTask) {
    this.subTask = subTask;
}

}

@Component("SubTask")

@Scope("prototype") public class SubTask { public SubTask() { System.out.println("SubTask Created"); } public void performTask(){ System.out.println("Perform Task"); } }

@Component("GlobalTask")

public class GlobalTask {

public GlobalTask(){
    System.out.println("Global task created");
}

public void performTask(){
    System.out.println("Perform Global Task");
}

}

Mani
  • 3,274
  • 2
  • 17
  • 27
  • The problem with this solution is that when SubTask is being injected twice into Task or somewhere into Task's object hierarchy we will and up with 2 SubTask instances which is not desirable. A good example is a Car simulator. In the game you may have many instances of Car. Every Car has an instance of Engine. Engine is unique within Car object hierarchy. You can inject Engine into CarComputer or into CarBody but it will be the same instance of Engine. – Vitaly Tsaplin Dec 11 '13 at 18:59
  • Oh ok. i understand your question now. You may try to implement CustomScope by implementing Scope and Registring your Scope Implementation instead of having Seperate IOC for Each Task. which can can act based on Task id ( Some Unquie Property with in Task) – Mani Dec 11 '13 at 19:14
  • Any idea on how Scope annotation being defined on SubTask class will look like? I mean how would you bind a task-scoped bean to a task scope which is linked to a Task instance which is somewhere below this task-scoped bean in the object hierarchy taking into account that instantiation is dynamic? – Vitaly Tsaplin Dec 11 '13 at 19:50
  • I would recomend to take look how SessionScope.java implementation.
    let assume,Your Task instance is not visible in multiple threads at a given time. When you start your task you can register your task id in ThreadLocal and in the CustomScope implementation you can get the TaskId from Same ThreadLocal and map it to the SubTask instance.( So in reality SubTask instances maintained by CustomScope and tracked by TaskId registered / given to thread local when ever Task created).Then whereever you do inject with in Thread for the Task same instance will be delivered.what is your TASK scope
    – Mani Dec 11 '13 at 20:09
  • What is your Task Instance Scope ? Will it span multiple threads ? In above approach , you can have n number of Task instance with in Single Thread but if your single Task spawn multiple thread at given time then above approach will not work, we may need to find differen context – Mani Dec 11 '13 at 20:11
  • In case of thread-scoped beans everything would be just tremendously easier. Thread info is available at any point to any object. The scope here is, well, a task module, a task instance itself and some related dependencies. With the Car game example it could be a Car module with CarEngine, CarComputer and CarBody as module-scoped beans. With all that said the child context solution could be the easiest one. All we need is a separate spring config for a module and TaskFactory which would instantiate a child context with the Task module config. – Vitaly Tsaplin Dec 11 '13 at 20:31
  • Even When you Implement Child Context, you have to give any one the following scope for your Task Class right ?
    1.singleton – Return a single bean instance per Spring IoC container 2.prototype – Return a new bean instance each time when requested
    3.request – Return a single bean instance per HTTP request. *
    4.session – Return a single bean instance per HTTP session. *
    5.globalSession – Return a single bean instance per global HTTP session. *
    that is what i am asking for what is the scope your Task Scope . Your SubTask can be any scope
    – Mani Dec 11 '13 at 20:42
  • Task is a prototype-scoped bean. – Vitaly Tsaplin Dec 11 '13 at 20:53
0

The solution I finally came up with requires to create a child context. The key point is to specify different child configuration so that parent context is unaware about child component dependencies. The simplest solution is to create a separate java config with enabled component scanning and to place it into a dedicated package.

@Configuration
@ComponentScan
public class TaskConfig {}

public interface TaskFactory {
    Task createTask();  
}

@Component
public class TaskFactoryImpl implements TaskFactory {

    private ApplicationContext parentContext;

    @Autowired
    public void setParentContext(ApplicationContext parentContext) {
        this.parentContext = parentContext;
    }

    @Override
    public Task createTask() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.register(TaskConfig.class);
            context.setParent(parentContext);
            context.refresh();
            return context.getBean(Task.class);
        }
    }
}