简体   繁体   中英

Firebase initialization inside a spring bean is not firing onDataChange

I am using firebase admin sdk for java. When I created a java console application, this code below works as expected. But if I put the same code, in the initialization of a bean in a springboot application, it never goes inside the onDataChange EventHandler.

I even tried putting a Thread.sleep() with a sufficient delay at the end, to check if this was happening because the thread initializing the bean was exiting. However that did not help.

What is the right way to do this inside a spring bean?

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.database.*; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Repository;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;

@Repository
@EnableAsync
public class MyBean {


    FirebaseDatabase db;


    public MyBean() {

        try {

            File file = new File(
                    getClass().getClassLoader().getResource("myKey.json").getFile()
            );

            FileInputStream fis = new FileInputStream(file);

            FirebaseOptions options = new FirebaseOptions.Builder()
                    .setCredentials(GoogleCredentials.fromStream(fis))
                    .setDatabaseUrl("https://myUrl/")
                    .build();

            FirebaseApp.initializeApp(options);

            db = FirebaseDatabase.getInstance();

            DatabaseReference ref = db
                    .getReference("/a/b/c");


            ref.addValueEventListener(new ValueEventListener() {
            //The code execution never comes in here

                public void onDataChange(DataSnapshot dataSnapshot) {
                    System.out.println("dataSnapshot.exists() :"+ dataSnapshot.exists()); 
                }


                public void onCancelled(DatabaseError error) {
                    System.out.print("Error: " + error.getMessage());
                }
            });

           // Thread.sleep(10000);

        } catch (Exception ex) {
            String err=ex.getMessage();
        }


    }

`

Take a look at the source code for the FirebaseReference.getReference() method:

public DatabaseReference getReference() {
  return new DatabaseReference(ensureRepo(), Path.getEmptyPath());
}

It is creating a new DatabaseReference each time you call it. The way you are doing it right now is you are adding a ValueEventListener to a DatabaseReference that will just get garbage collected since you are not passing it out of this method someway.

What I suggest is declaring your DatabaseReference object as a bean in a @Configuration class:

@Configuration
public class FirebaseConfig {

  // This is just the ApplicationContext, injected through abstraction 
  private final ResourceLoader resourceLoader;

  @Autowired // Constructor injection, recommended above field and setter injections
  public FirebaseConfig(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

  @Bean
  public FirebaseDatabase firebaseDatabase() throws IOException {
    Resource resource = resourceLoader.getResource("classpath:myKey.json");
    InputStream inputStream = resource.getInputStream();
    FirebaseOptions firebaseOptions = new FirebaseOptions.Builder()
            .setCredentials(GoogleCredentials.fromStream(inputStream))
            .setDatabaseUrl("url")
            .build();
    FirebaseApp.initializeApp(firebaseOptions);
    return FirebaseDatabase.getInstance();
  }

  @Bean
  public DatabaseReference databaseReference(FirebaseDatabase firebaseDatabase) {
    DatabaseReference ref = firebaseDatabase.getReference();
    ref.addValueEventListener(new ValueEventListener() {

      public void onDataChange(DataSnapshot dataSnapshot) {
        System.out.println("dataSnapshot.exists() :" + dataSnapshot.exists());
      }


      public void onCancelled(DatabaseError error) {
        System.out.print("Error: " + error.getMessage());
      }
    });

    return ref;
  }
}

Now, throughout your Spring Application you should autowire this DatabaseReference bean.

An even more elegant solution would be to separate the ValueEventListener into it's own bean, and autowire that into the DatabaseReference bean method:

  @Bean
  public DatabaseReference databaseReference(
    FirebaseDatabase firebaseDatabase, 
    // List injection will be populated with all ValueEventListener beans in the ApplicationContext
    List<ValueEventListener> valueEventListeners 
  ) {
    DatabaseReference ref = firebaseDatabase.getReference();
    valueEventListeners.forEach(ref::addValueEventListener);
    return ref;
  }

And in a separate class:

@Component
public class SystemOutListener implements ValueEventListener {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    System.out.println("dataSnapshot.exists() :"+ snapshot.exists());
  }

  @Override
  public void onCancelled(DatabaseError error) {
    System.out.print("Error: " + error.getMessage());
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM