brain storm
brain storm

Reputation: 31262

What happens when autowired bean is initialized with new constructor?

I have used Spring in the past. I moved to a different team where I am getting familiarized with codebase. I found the following code and trying to understand how it works and how spring injects autowired objects in the case. From my basics of Spring, this is definitely not the right way to do. But surprisingly, this code is in production for a long time and no issues were identified.

@Controller
@RequestMapping("/start")
public class AController implements Runnable, InitializingBean {

    @Autowired
    private StartServiceImpl service = new StartServiceImpl(); // 1

    Thread thread;

    public void run() {

        service.start();
    }

    public void stop()  {
            try {
                thread.join(); 
            } catch (InterruptedException e) {
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        thread = new Thread(this);
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
    }
}

@Component
public class StartServiceImpl {
    //methods
}

Q1) What does localhost:8080/project/start is expected to do. There is NO GET or POST methods defined.

Q2) on the commented line 1, StartServiceImpl is both autowired and constructed with "new". So what happens here. Does the container inject bean or just an object is instantiated.

 @Controller
    @RequestMapping("/stop")

public class BController {

    @Autowired
    private StartServiceImpl service = new StartServiceImpl();

    @RequestMapping(value = "**", method = RequestMethod.GET)
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {

            service.shutdownRequested();
            new AController().stop(); // 2
        } catch (Exception e) {
        }
    }
}

Q3) Again in commented line 2, does calling stop, calls the stop on the bean in the application context or a new object gets created and the stop method is called. What would happen in the latter case? Are we really stopping the service that was started or not? I think we are not stopping the service.

I have read this post. It is very useful. But it did not answer my question.

Upvotes: 1

Views: 2928

Answers (3)

Ken Bekov
Ken Bekov

Reputation: 14015

The code you posted is very strange.

Q1 ) What does localhost:8080/project/start is expected to do. There is NO GET or POST methods defined.

I think localhost:8080/project/start will return 404 error (The requested resource is not available). Because there is no mapped methods in AController. @RequestMapping annotation on class level is not enough for make request to controller. There is have to be mapped method.

But service will be started anyway. Because AController implements InitializingBean. Method afterPropertiesSet() will be invoked by Spring after controller will be created and all fields will be initialized.

Q2) on the commented line 1, StartServiceImpl is both autowired and constructed with "new". so what happens here. does the container inject bean or just an object is instantiated.

Another strange snippet. Java will create new instance of StartServiceImpl on creation of instance of AController class. But after that, Spring will assign it's own instance(declared as component) to this field. And reference to firs instance (created by constructor) will be lost.

Q3) Again in commented line 2, does calling stop, calls the stop on the bean in the appication context or a new object gets created and the stop method is called. what would happen in the latter case? Are we really stopping the service that was started or not? I think we are not stopping the service

Actually service will be stopped. Because of invocation of service.shutdownRequested();. But thread in AController bean will continue to work. new AController().stop(); will invoke method of just created instance, but not method of controller (instance created by Spring).


This code is totally wrong usage of Spring framework.

Upvotes: 1

Sean Patrick Floyd
Sean Patrick Floyd

Reputation: 299048

This code is flawed on many levels.

  1. Ever since Java 5, manually starting threads is an antipattern. It's messy and way too low-level. ExecutorServices should be used.
  2. A Rest controller that is a Runnable? That's a monstrous mingling of concerns.
  3. A service is created via new but then overwritten with an autowired dependency? WTF!
  4. etc.

I'd keep the thread running all the time, scheduling the task with the @Scheduled annotation and use the controller to toggle a flag that decides if the thread actually does somethin, e.g.

@Service
class StartService{
   private boolean active;
   public void setActive(boolean active){this.active=active;}


   @Scheduled(fixedRate=5000)
   public void doStuff(){
      if(!active)return;
      // do actual stuff here
   }

}

And now all the rest controllers do is toggle the value of the "active" field. Benefits:

  • every class does one thing only
  • you always know how many threads you have

Upvotes: 1

Marius Bogoevici
Marius Bogoevici

Reputation: 2410

I will try to answer the questions specifically, as the purpose of the code is hard to understand (for me at least).

Q1) It is unclear for me what this code tries to achieve. As you noticed, it is not a controller, and I suspect that the only reason why it is registered this way is so that it gets auto-scanned (which might as well get done by using @Controller. This is just a hunch, I don't quite understand its purpose.

Q2) The answer is that two instances will be created, one via new, the other as a bean. When running in Spring, the final value of the field is the bean, because dependency injection happens after the construction. Typically this is done when the class is envisioned to be used outside Spring (e.g. a unit test), so that the field can be initialized with a default value.

Q3) stop() will be invoked on a new instance, and not the bean. The service bean is stopped because of the direct call above that line to the injected bean, but the next one will be an NPE, I guess, because afterPropertiesSet is not invoked on the target object created via new. the only reason why this doesn't show an NPE in the logs is because the exception is swallowed below. The thread variable is not initialized and remains NULL.

Hope this helps,

Upvotes: 1

Related Questions