0

I've worked with legacy Spring in the past. We defined our beans via xml configuration and manually wired them. My team is finally making a concerted effort to update to annotations and use Spring Boot instead of the 'traditional' approach with Spring MVC.

With that said, I cannot figure out how the heck I retrieve a bean in Boot. In legacy, Spring would either use constructor/setter injection (depending on our configuration), or we could directly call a bean with context.getBean("myBeanID"); However, it does not appear to be the case anymore.

I put together a small test case to try and get this working in the below code:

package com.ots;


import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
public class GriefUIApplication{

    public static void main(String[] args) {
        SpringApplication.run(GriefUIApplication.class, args);

        SessionFactory factory = new Configuration().configure("hibernate.cfg.xml")
                .addAnnotatedClass(GriefRecord.class).addAnnotatedClass(RecordId.class)
                .addAnnotatedClass(CrossReferenceRecord.class)
                .buildSessionFactory();

        Statics.setSessionFactory(factory);

        TennisCoach tCoach = new TennisCoach();
        tCoach.getCoach();

    }

}



interface Coach{
    public String workout();
}


@Service
class TennisCoach implements Coach{

    private Coach soccerCoach;
    @Autowired
    private ApplicationContext context;


    public TennisCoach() {

    }

    public Coach getCoach() {

        System.out.println(context + " IS THE VALUE OF THE CONTEXT OBJECT");

        soccerCoach = (SoccerCoach)context.getBean("soccerCoach");

        System.out.println(soccerCoach.getClass());

        return soccerCoach;

    }

    @Override
    public String workout() {
        String practice = "practice your backhand!";
        System.out.println(practice);
        return practice;
    }


}

@Service
class SoccerCoach implements Coach{

    public SoccerCoach() {

    }

    @Override
    public String workout() {
        String practice = "practice your footwork!";
        System.out.println(practice);
        return practice;
    }

}

@RestController
class MyController{

    @GetMapping("/")
    public String sayHello() {

        return "Time on server is: " + new java.util.Date();
    }

}

I tried autowiring the ApplicationContext object into the TennisCoach class. When I run this, that object is null.

How do we retrieve beans in Boot?

Matthew
  • 817
  • 1
  • 13
  • 39

2 Answers2

2

The most common approach is to use the @Autowired annotation. Also, because you have two different implementations of the Coach interface, you should use the @Qualifier annotation to tell Spring which interface implementation to inject.

Some tutorials about these two annotations, to get you started:

For your example, to inject the beans into your controller, you should do:

@RestController
class MyController {

    @Autowired
    @Qualifier("soccerCoach")
    private Coach coach;

    @GetMapping("/")
    public String sayHello() {
        // this should invoke the workout() from SoccerCoach implementation
        System.out.println(coach.workout());

        return "Time on server is: " + new java.util.Date();
    }

}

Or, to inject the soccerCoach into the tennisCoach as you intended, using constructor injection , the code would become:

@Service
class TennisCoach implements Coach {

    private Coach soccerCoach;

    @Autowired
    public TennisCoach(@Qualifier("soccerCoach") Coach soccerCoach) {
        this.soccerCoach = soccerCoach;
    }

    public Coach getCoach() {
        System.out.println(soccerCoach.getClass());

        return soccerCoach;
    }

}

If you really need to use the ApplicationContext to retrieve some bean, it's not advisable to do so in your class that has the main function. Just create another bean (you could use the @Componenent annotation). Here's an example:

@Component
public class AnyComponent {

    @Autowired
    private ApplicationContext applicationContext;

    public void invokeCoach() {
        System.out.println(applicationContext.getBean("tennisCoach"));
        System.out.println(applicationContext.getBean(SoccerCoach.class));
    }

}

And inject this bean in your application flow, could be in the controller, service, or repository:

@RestController
class MyController {

    @Autowired
    private AnyComponent anyComponent;

    @GetMapping("/")
    public String sayHello() {
        anyComponent.invokeCoach();

        return "Time on server is: " + new java.util.Date();
    }

}

Note: Injecting beans through annotation is something specific to Spring, in general, not Spring Boot in particular.

From https://www.baeldung.com/spring-autowire:

Starting with Spring 2.5, the framework introduced a new style of Dependency Injection driven by @Autowired Annotations. This annotation allows Spring to resolve and inject collaborating beans into your bean.

Alexandru Somai
  • 1,395
  • 1
  • 7
  • 16
  • How would I do it without field injection? From what I've read, that is generally considered bad practice. How would I do it with constructor injection? – Matthew Feb 20 '20 at 15:55
  • I've updated my answer to do that with constructor injection. Personally I like the field injection, but I think it's just a matter of preference. Also the `soccerCoach` could be final: `private final Coach soccerCoach;` – Alexandru Somai Feb 20 '20 at 16:00
  • I understand, but both answers here are slightly missing the point. I do not understand _how_ to retrieve a bean. If you look at my code, in the main method, I am trying to instantiate a Tennis coach view the `new Coach()` process. However, I get a null pointer exception. Even after altering my code to be like your above, I am still getting a null pointer exception. How do I retrieve a bean from the Spring container? – Matthew Feb 20 '20 at 16:05
  • It should be as you've tried, with ApplicationContext. In your main app, just do `context.getBean(TennisCoach.class)`. More details: https://stackoverflow.com/questions/34088780/how-to-get-bean-using-application-context-in-spring-boot. – Alexandru Somai Feb 20 '20 at 16:10
  • I've looked at that page and tried autowiring an applicationcontext. It just gets returned as null, doesn't work. – Matthew Feb 20 '20 at 16:21
  • Thanks for accepting my answer. Just to clarify things a bit, I've updated my example to include the `ApplicationContext`. It's not advisable to use it in the `main` function. Just create another bean where you inject it. – Alexandru Somai Feb 21 '20 at 07:18
1

Inject the required beans directly. No need of ApplicationContext in Spring boot.

@Service
class TennisCoach {

    private SoccerCoach soccerCoach;

    @Autowired
    public TennisCoach(SoccerCoach soccerCoach) {
      this.soccerCoach = soccerCoach;
    }

}
kann
  • 687
  • 10
  • 22
  • If your field is final and there is only one constructor, there is no need even for the `@Autowired` annotation. I typically use Lombok's `@RequiredArgsConstructor` to eliminate boilerplate. – adarshr Feb 20 '20 at 15:49
  • How do I retrieve this bean though? `TennisCoach tCoach = new Coach();`? When I attempt to instantiate the object, It says I'm missing an argument. – Matthew Feb 20 '20 at 15:52
  • No need to do that. The Spring container will take care of object instantiation. – Alexandru Somai Feb 20 '20 at 15:56
  • Ok, but _how_ do I retrieve the bean if I do not do `new Coach()`? In my main method I am trying to retrieve the bean, and I cannot figure out how to do so. – Matthew Feb 20 '20 at 15:59