1

I want to read data in ApplicationListener, but my object is not initialized. Below is my code:

AppContextListener.java

@Component
public class AppContextListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {       
        AppContext.getInstance();
    }
}

AppContext.java

public class AppContext {    
    private static AppContext instance;

    @Autowired
    MyElasticsearchRepository repository;

    public AppContext(){
        InitData();
    }

    public static synchronized AppContext getInstance() {
        if (instance == null) {
            instance = new AppContext();
        }
        return instance;
    }   

    private void InitData(){       
        List<MyEntity> dataList = repository.findAllEntities();//repository is null here
        //.......
    }   
}

MyElasticsearchRepository.java

 public interface MyElasticsearchRepository extends ElasticsearchRepository<MyEntity,String>
 { }

problem

As you can see in my code, at InitData(), repository is null. I don't know why @Autowired MyElasticsearchRepository repository; does not worked here.

Please show me how to fix this. Thank you very much.

amdixon
  • 3,814
  • 8
  • 25
  • 34
MichaelP
  • 2,761
  • 5
  • 31
  • 36
  • First off all I would strongly suggest doing it differently as I would consider this an anti-pattern instead of good design. You are trying to use the auto wired field from within the constructor (you are calling a method) but auto wiring can only happen after an instance is created, so no inject has happend at that point. Also due to the use of an anti pattern you are constructing instances of the bean yourself (`new AppContext()`) which leads to non spring managed instance so auto wiring will also never happen. – M. Deinum May 26 '15 at 10:06

4 Answers4

1

@Autowired will only work if bean is marked with Stereotype annotation (What's the difference between @Component, @Repository & @Service annotations in Spring?) or you explicitly define it in spring configuration.

AppContextListener.java

@Component // AFAIR not needed. Spring will create this bean when it will see that class implements `ApplicationListener` interface.
public class AppContextListener implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private AppContext appContext;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {       
        appContext.initData();
    }
}

AppContext.java

@Component
public class AppContext {    

    @Autowired
    MyElasticsearchRepository repository;

    public void initData(){       
        List<MyEntity> dataList = repository.findAllEntities();//repository is null here
        //.......
    }   
}
Aliaksei Yatsau
  • 749
  • 7
  • 12
  • in my controller, with such declaring repository variable, i see that repository is instance of SimpleElasticsearchRepository ( in package org.springframework.data.elasticsearch.repository.support). But i dont know why i use it in app listener, it does not work. – MichaelP May 26 '15 at 09:47
  • Why do u need `ApplicationListener`? – Aliaksei Yatsau May 26 '15 at 09:57
  • i want to init some data at very first moment when my web service start. So i choose ApplicationListener event for this. If you can suggest me alternative way, i am very appreciated. – MichaelP May 26 '15 at 10:00
1

There are a couple of things wrong with your code.

First you are using the singleton pattern which I would say is an anti-pattern especially when combined with auto wiring.

Second in your getInstance() method you are creating a new instance of AppContext yourself. This instance isn't managed by Spring so @Autowired is pretty much useless here, Spring is only able to inject dependencies into beans it knows about.

Instead make your AppContext a component (or service what ever you like). Remove the getInstance method and use constructor injection instead.

@Component
public class AppContext {    

    private final MyElasticsearchRepository repository;

    @Autowired
    public AppContext(MyElasticsearchRepository repository){
        this.repository=repository;
    }
    ...
}

Thirdly you are trying to use the @Autowired instance from the constructor (you are doing method call which expects it to be there), however auto wiring can only be done on an instance of a bean. So at that moment the auto wiring hasn't taken place and your variable will always be null. Instead of calling the method from the constructor either, use constructor inject or annotate the InitData method with @PostConstruct.

@PostConstruct
private void InitData(){       
    List<MyEntity> dataList = repository.findAllEntities();
    ...        
}

Now that your AppContext is a component it will be detect by spring and you can simply inject it into your ApplicationListener.

@Component
public class AppContextListener implements ApplicationListener<ContextRefreshedEvent> {

    private final AppContext appContext;

    @Autowired
    public AppContextListener(AppContext appContext) {
        this.appContext=appContext;
    }
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {       
        // Do your thing with appContext
    }
}

Note: I prefer constructor injection for required fields and setter injection for optional fields. You should avoid field injection (i.e. @Autowired on instance fields) as that is considered a bad practice. See here why field injection is evil and should be avoided.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • I used singleton pattern for my AppContext because AppContext will contain some more data that will be accessed in many places. If you move it into AppContextListener as a private variable, so how can i access AppContext in other places? – MichaelP May 26 '15 at 10:20
  • Just inject it., that is the whole point of dependency injection... Instead of hacking everything together with static get instance methods use dependency injection. – M. Deinum May 26 '15 at 10:21
  • Why does `Singelton` scope in Spring anti-pattern? – Aliaksei Yatsau Jan 28 '18 at 12:48
0

@Autowired will work only after AppContext object constructed. Since you try to access @Autowired element inside constructor, it doesn't exist.

rharishan
  • 970
  • 7
  • 11
  • is there any event that i can access @Autowired element? I need find an event that i can read some data before controller receive requests from clients – MichaelP May 26 '15 at 10:07
  • You can initialize the object without @Autowire it or try annotate your AppContext class constructor with Autowired. – rharishan May 26 '15 at 10:11
0

Can't you just do this?

    @Component
    public class AppContextListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {       
            ApplicantContext context = event.getApplicationContext();
            MyElasticsearchRepository repository = context.getBean(MyElasticSearchRepository.class);
            //do stuff
        }
    }

http://docs.spring.io/autorepo/docs/spring/4.1.4.RELEASE/javadoc-api/org/springframework/context/event/ContextRefreshedEvent.html

Alan Hay
  • 22,665
  • 4
  • 56
  • 110