Reputation: 31262
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
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
Reputation: 299048
This code is flawed on many levels.
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:
Upvotes: 1
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