0

I've been modifying my class to avoid new operator on StudentService ( let Spring's container manages it) there in my controller. I need Student student; field there gets injected with StudentService but rather than getting the student injected it gets injected with an exception saying : No qualifying bean of type [com.Student] is defined: expected single matching bean but found 2: studentService,ss

I want this program to print

NAME : bravo

Student :

package com;
public interface Student {
    public String getN();
}

StudentService :

package com;
import org.springframework.stereotype.Service;
@Service
public class StudentService implements Student{
String name;
    @Override
    public String getN() {
      return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Controller

 package com;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TheController {

    @Autowired
    private ApplicationContext appContext;

    @Autowired
    Student student; 

    @RequestMapping("/")
    public String aMethod() {

        StudentService ss = (StudentService) appContext.getBean("ss");
        ss.setName("bravo");

        System.out.println("NAME : " + student.getN());
        // while "NAME : " + ss.getN() will work, of course
        return "";
    }
}

dispatcher-servlet

...
 <context:component-scan base-package="com" /> 

 ...
  <bean id="ss" class="com.StudentService" />

...

EDIT : If I do this :

@Service ("st")

public class StudentService implements Student{

and this (in controller)

@Autowired
@Qualifier("st")
Student student; // tipenya interface

no exceptions, but it produces

NAME :null

If I get rid of the @Service and load StudentService as <bean class="com.StudentService".. > and load it manually through application context then it will work.

I mean how is that @Service can't be autowired

  • Possible duplicate of [spring autowiring with unique beans: Spring expected single matching bean but found 2](http://stackoverflow.com/questions/8414287/spring-autowiring-with-unique-beans-spring-expected-single-matching-bean-but-fo) – blacktide Jun 09 '16 at 21:24
  • HI how do I qualify it ? they are `StudentService` for class and `ss` for xml – Plain_Dude_Sleeping_Alone Jun 09 '16 at 21:27
  • You either do xml based config or you do java based config. `@Service` makes it a bean – zapl Jun 09 '16 at 21:28
  • It will work if the service bean is declared via xml, what actually i need to find that if this `@Autowired` field is aware of StudentService by just annotate it with plain `@Autowired` since `StudentService` is annotated with `@Service`, and for qualifying bean part, I've no idea how to qualify them since one is `class` while another one is `xml` – Plain_Dude_Sleeping_Alone Jun 09 '16 at 21:36
  • Please add the configuration file, probably it is a component scan problem – reos Jun 09 '16 at 22:21
  • Hi, the configurations is fine. Everything is fine except for it can't be wired. – Plain_Dude_Sleeping_Alone Jun 09 '16 at 22:28

2 Answers2

3

Spring knows more than 2 ways to declare "beans" (objects it can use to inject). One is xml:

<bean id="ss" class="com.StudentService" />

This will cause spring to create a bean named "ss" by doing new com.StudentService(...). It should even try to figure out constructor arguments by finding other beans that match. The resulting object is stored in your application context for future use. E.g. when some place requests to get a bean of type StudentService or with one named "ss".

@Service

As well as @Component and some others will cause spring to treat the class this is attached to as bean. The name will be derived from the class name: it's simply lower-cased. It will also new an instance of that type and take care for all dependencies. Both XML and @-annotation configured beans will be available in the application context.

In your case, you tell spring in two different ways to do the same: that you want to have com.StudentService available as bean, once named explicitly "ss" via xml, once implicitly named "studentService" in java.

Now when you request in your controller a bean that matches the type Student like so

@Autowired
Student student; 

it has 2 equally good candidates and can't decide which one to inject. This is your current exception.

To solve the problem, you can either declare only 1 bean (preferably) or you qualify which one you mean by providing the name. For example

@Autowired
@Qualifier("ss")
Student student;

That's almost exactly what you do later with

StudentService ss = (StudentService) appContext.getBean("ss");

When you combine both like

@Autowired
@Qualifier("st")
Student student;

...

StudentService ss = (StudentService) appContext.getBean("ss");
ss.setName("bravo");

System.out.println("NAME : " + student.getN());

What happens is that you now have 2 independent beans / instances of StudentService, which are both "singletons" (wrt their bean-name). You set the name of one, and read the name of the other. The expected result is that the name of the "st" bean is still null.

zapl
  • 63,179
  • 10
  • 123
  • 154
  • Basically you want to say that `StudentService ss = (StudentService) appContext.getBean("ss");` is basically spawning a new bean since ( `` is enough ? Because I've just solved it by doing `deep understanding` on reading your answer :D – Plain_Dude_Sleeping_Alone Jun 09 '16 at 22:44
  • Thank you *so-very-extremely* much @zapl – Plain_Dude_Sleeping_Alone Jun 09 '16 at 22:49
  • @Hey-men-whatsup `.getBean("ss")` does not spawn a new instance. It gives you the one that is constructed once spring reads the xml during application initialization. Just like `getBean("st")` gives you the instances that is constructed during the phase in which it scans your classes for annotations. If you want a new `Student` each time your controller handles `aMethod` you have to do something different. (a scoped proxy for example: http://stackoverflow.com/questions/20435448/spring-mvc-scoped-controller-using-class-based-proxy-there-is-already-scopedt) – zapl Jun 09 '16 at 22:50
  • Thank you again, Ah I see your point since bean is singleton, btw the work around is to just remove these two lines `` & `StudentService ss = (StudentService) appContext.getBean("ss");` , I managed it by reading your answer Intimately (*not that kind of intimate*). Thank you very much! – Plain_Dude_Sleeping_Alone Jun 09 '16 at 23:27
1

Let's look at aMethod

@RequestMapping("/")
public String aMethod() {

    StudentService ss = (StudentService) appContext.getBean("ss");
    ss.setName("bravo");

    System.out.println("NAME : " + student.getN());
    // while "NAME : " + ss.getN() will work, of course
    return "";
}

In this method you're creating a StudentService object called ss. Just below, you call setName interface method, which sets the StudentService name to bravo. However, when you call the student.getN(), you're retrieving the name of a different object.

To fix this, you could retrieve a Student object managed by the StudentService:

@Service
public class StudentService {

    @Autowired
    Student student;

    public Student getStudent() {
        return student;
    }

    public void setName() {
        this.student.setName(Alfred);
    }
}

But for the sake of idiosyncrasy, I recommend decoupling the two and just use your Student's setName() without the use of a service.

AlexOlsen
  • 187
  • 2
  • 10