1

I have a problem that I have a ClassA needs RoomService to be injected, and it works fine that I find in ClassA, the roomService's id is the same.

While for some reason, I need roomservice to create room instance based on some input param for me, so I use below config to achieve this:

@Configuration
@EnableAspectJAutoProxy
public class Application {

private static ApplicationContext ctx = new AnnotationConfigApplicationContext(Application.class);

    public static ApplicationContext getApplicationContext(){
    return ctx;
}

    @Bean        
    public RoomService roomService(){
        return new RoomService();//Singleton
    }

    @Bean
    @Scope("prototype")
    public AbstractRoom room(AbstractRoom.Mode roomMode){
        RoomService roomService = (RoomService) ctx.getBean(RoomService.class);
        LogUtil.debug("--------from application:" +roomService.id1);//here, I find the id is different every time
        return roomService.newRoom(roomMode);
    }
}

The problem is that I need RoomService to be singleton, but I find that in the Application.java , the ctx.getBean(roomService) always returns a different bean which has different id. Isn't Spring should reuse the same bean? Why is that?

Here is how I create a room in RoomService.java

public AbstractRoom createRoom(String userID,int playerCount,Mode roomMode){

    ApplicationContext ctx =Application.getApplicationContext();
    AbstractRoom room = (AbstractRoom)ctx.getBean(AbstractRoom.class,roomMode);

}

Update: I tried reusing the same ctx and it does not work. One hint is that I find my constructor of RoomService() is called several times(I put a break point in it.) when I run tomcat to start it

Here is my web.xml

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>wodinow</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

Please help!

alexbt
  • 16,415
  • 6
  • 78
  • 87
JaskeyLam
  • 15,405
  • 21
  • 114
  • 149
  • 1
    The RoomService is probably different each time you get it from application context because each time you call room() you create and the close the application context. I think the RoomService is guaranteed to be singleton within a single application context instance. – Bohuslav Burghardt Nov 02 '14 at 11:54
  • @BohuslavBurghardt, I tried that I do not call ctx.close(), the same. I guess I am getting different context since I use `new` to get the context. – JaskeyLam Nov 02 '14 at 12:00

1 Answers1

2

Each time you retrieve your RoomService you are creating a new ApplicationContext instance. But singleton beans are only guaranteed to be singleton within a single instance of ApplicationContext.

So if you want the bean to be singleton you must use the same ApplicationContext instance each time you retrieve it.

From Spring documentation:

singleton: (Default) Scopes a single bean definition to a single object instance per Spring IoC container.

Update 1

You can just call roomService() in your room() method to get the room service without creating application context and Spring will ensure that they are the same instance since it is marked as @Bean which has implicit singleton scope.

Update 2

Based on the updated question here are couple of issues with your code in general:

1. Do not create ApplicationContext in your configuration class. When you start your Spring application in Tomcat, application context is created for you by Spring. You just need to tell Spring which configuration classes it should register.

2. Remove the room() bean definition from your configuration class. Method for creating a room should be in RoomService. So for example if you needed to create a new room in Spring MVC controller you would inject the RoomService and call createRoom method on it. The injected service would be singleton. Example:

@Controller
@RequestMapping("/rooms")
public class RoomController {

    @Autowired
    private RoomService roomService;

    @RequestMapping(value="/create", method=POST)
    public String createRoom() {
        roomService.createRoom(/* Parameters for room creation */);
        return "redirect:/somelocation";
    }
}

Try to rework your code based on these suggestions and it should work.

Bohuslav Burghardt
  • 33,626
  • 7
  • 114
  • 109
  • @Do we have a factory method that we will have only one ctx instance? I will tried your method and get back in minutes. – JaskeyLam Nov 02 '14 at 12:06
  • I tried both of your suggestion, 1. Use the same ctx, 2. Use roomService() instead of ctx, but neither of it solves the problem!..Does it work in your side? – JaskeyLam Nov 02 '14 at 12:30
  • And I find my RoomService constructor is called several times when I run Tomcat, is it a clue? – JaskeyLam Nov 02 '14 at 12:36
  • It works in my simple project I created to test it. But I don't run it in Tomcat. Btw, I just looked at your updated question. If you are running in Tomcat don't define the static application context like that in your configuration class. The application context will be created automatically. How are you bootstrapping your spring application and registering your config classes? web.xml or using Java config (web application initializer)? Please share that code if possible. It might help. – Bohuslav Burghardt Nov 02 '14 at 12:51
  • I use web.xml and please review my updated question! Thank you very much for your help! – JaskeyLam Nov 02 '14 at 13:35
  • I solve this problem by auto inject a applicationcontext, accoring to the second answer in http://stackoverflow.com/questions/129207/getting-spring-application-context. Would you please help to update your answer and also explain why I have to inject the application context while if I use getInstance from Application.java does help work? And I will accept your answer after this. Thank you! – JaskeyLam Nov 02 '14 at 14:24
  • OK, just one final question before I update it. You injected the application context in your room service? – Bohuslav Burghardt Nov 02 '14 at 15:30
  • Yes, I injected an applicationcontext in my roomService and now it works. But the @bean for my room is needed (at least for my current relation), beacause I need spring create room bean for me since the room need something else to be injected,check this question: http://stackoverflow.com/questions/26698207/spring-injected-bean-null-when-creating-an-object-with-new-how-to-solve-it . So, would you please also explain that when should we use new ApplicationContext and when should not? Since at the first time you seems to agree that `new` should work. Thank you very much! – JaskeyLam Nov 04 '14 at 05:04
  • At first I thought that you were using it in a standalone app and therefore creating new context would be OK. But after your updates I found out that you were running your app in Tomcat and the Spring context was created for you so in this case you should not create the context manually like in your example. It probably didn't work as you expected because you had multiple contexts. Now when you autowire your application context in your RoomService it references the SAME context to which the RoomService bean itself exists in and therefore your bean behaves correctly as a singleton. – Bohuslav Burghardt Nov 05 '14 at 11:33
  • what if this is not a bean that spring controls ,but i need to get ascess to applicationcontext, what should i do? – JaskeyLam Nov 05 '14 at 13:24