1

I've recently came across a behavior in Spring framework which left me very puzzled: if two classes have the same simple name (but are from different packages), and both are marked with the @Component annoation, then any attempt to mark a variable of the type of one of them with @Autowired will result in an exception, since there are 2 different Beans declared with the desired name. Simply put - the @Component annotation uses the Class' simple name instead of its full name (this is also mentioned in this question).

Is there a reason why Spring works this way? From what I understood, the whole point of dependency injection is that you can receive an object of the appropriate type without knowing how to create it, and so forcing the receiver of the dependency to know the source of the dependency through annotations such as @Qualifier even though there is only 1 truly relevant option really confuses me.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Dean Gurvitz
  • 854
  • 1
  • 10
  • 24
  • 1
    By default Spring create bean based its simple name. You can declare bean as @Component("NoSimpleNameBean") – User9123 Mar 03 '20 at 19:33
  • 1
    You must use @Qualifier if two beans have same type (or implementing and injecting by same interface). In your example is unnesessary – User9123 Mar 03 '20 at 19:34
  • @User9123 if I have 2 different types with the same simple name, unfortunately I would still need to use @ Qualifier. That's the part that confused me – Dean Gurvitz Mar 03 '20 at 19:37
  • Nope, you can create first component as "@Component", second - as "@Component("ImNoFirstComponent")" and injecting their without @Qualifier (only using their type) – User9123 Mar 03 '20 at 19:45
  • @User9123 I'm pretty sure that would only work if you inject variables of the type that is marked with "@Component" (with no brackets), but would result in an incompatible bean error with the other type – Dean Gurvitz Mar 03 '20 at 19:47
  • Are the classes with the same simple name yours? )) – aviad Mar 03 '20 at 19:51
  • You can implement your own `BeanNameGenerator` or extend the default one to do what you what. You can then hook it up to the `@ComponentScan` and have your desired result. – M. Deinum Mar 04 '20 at 06:59
  • But the question was if there is any reason for the simple names? Why not using full qualified names for the beans? Maybe it's because you can define Beans in different ways. Not only with the `@Component` annotation but also with the `@Bean` annotation on method level in a Config class. What would be the full qualified name for beans defined in the same Config class in different methods? – the hand of NOD Mar 04 '20 at 07:11

3 Answers3

2

It works fine. First component:

package com.example.demo.component1;

import org.springframework.stereotype.Component;

@Component
public class SimpleComponent {
    public String action() {
        return "imSimpleComponent";
    }
}

Second component:

package com.example.demo.component2;

import org.springframework.stereotype.Component;

@Component("SimpleComponent2")
public class SimpleComponent {
    public String action() {
        return "imSimpleComponent2";
    }
}

Controller:

package com.example.demo.controller;

import com.example.demo.component1.SimpleComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ResourceController {

    @Autowired
    private SimpleComponent simpleComponent;

    @Autowired
    private com.example.demo.component2.SimpleComponent simpleComponent2;

    @RequestMapping("/home")
    public String hello() {
        return simpleComponent.action() + "_" + simpleComponent2.action();
    }
}

http://localhost:8080/home return:

imSimpleComponent_imSimpleComponent2

User9123
  • 1,643
  • 1
  • 8
  • 21
  • You're correct. Even though your answer doesn't directly answer my question, this is by far the simplest solution to the problem I raised here and so I accepted it. – Dean Gurvitz Mar 04 '20 at 07:15
2

Not knowing how a bean is created is not the same as knowing where to find the bean.

As you've observed, if you have two @Components which have the same simple name, and you attempt to @Autowire them in, Spring won't know which one to bring in. This is because the names generated by Spring are non-qualified; the default generator will only ever use the simple name of the class.

While there's a nifty @ComponentScan-oriented fix to this, I prefer that there be no ambiguity when I'm looking at components or beans, so I would espouse explicitly naming your beans so that there can be no ambiguity going forward.

// Assuming these are in different packages
@Component("foo")
public class MyComponent {}

@Component("bar")
public class MyComponent {}
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • 1
    Yeah but Spring _could_ still be checking the full type when autowiring. I guess it doesn't, til. – daniu Mar 03 '20 at 20:11
  • Doesn't the need to know where to find your beans still create some form of unnecessary coupling? Regardless, the solution is still useful – Dean Gurvitz Mar 03 '20 at 20:12
  • @daniu: There's a good reason why it doesn't - this case is incredibly rare. It's incredibly bad practice (or at least ***really*** bad juju) to create two classes with the same name in different packages. The 99% case is to use the simple name as opposed to going any fancier than looking into the qualified name, and if you *need* fancier, you can reach for it instead. – Makoto Mar 03 '20 at 20:13
  • @DeanGurvitz: You can create POJOs and services and objects without registering them as a part of the component scan. The component scan is you telling Spring *where* your beans live. Spring takes over from there by controlling the lifecycle of the bean depending on how you wire it up. – Makoto Mar 03 '20 at 20:17
  • @Makoto: I don't think that this case is incredibly rare. When my springboot-application consists of several external dependencies I do not have control over the names and therefore it could be (and I had this case several times) that the external dependency uses the same name as I do but naturally in a different package. So I think that the only reason why spring does not use full qualified names is just because they thought that it would be more comfortable for the user of their fw. – the hand of NOD Mar 04 '20 at 07:06
  • @thehandofNOD: Is it the case that the external library *also* registers beans in the Spring context? If not, then it's really a non-issue unless there's something super, super broken going on - and I stress that this only becomes an issue when two similarly named classes are registered in the Spring context. Otherwise, we'd be having this issue with `Date` all the time (and I'll leave it to your imagination to figure out *which* `Date` class would be giving issues). – Makoto Mar 04 '20 at 07:12
  • @Makoto: Yes it does register that bean into the spring context. I know hat there would be no problem if it doesn't :-D that's why I mentioned the external libraries. I just wanted to point out that there are scenarios in which such a thing could happen without having "bad juju" ;-) – the hand of NOD Mar 04 '20 at 07:22
  • @thehandofNOD: Mm...I'd still call that pretty bad juju. Somehow you and this external library elected to create the same bean with the same name, and instead of just...qualifying your own beans and/or filing a bug report requesting that the maintainer of the 3rd party library do the same, you're trying to compromise. That's bad juju in my head. – Makoto Mar 04 '20 at 07:24
  • @Makoto: Could be that it's bad juju. Unfortunately I do not remember the exact name of the bean and which library had the same name but just for example what if I have a project in which I want to handle a `Client`? I therefore would define a type called `Client`. Unfortunately the idea of naming such types `Client` is pretty far spred :-) – the hand of NOD Mar 04 '20 at 07:29
0

Why doesn't Spring's @Component annotation use the full class name? Is there a reason why Spring works this way?

Spring-Beans can be defined in different ways: With the @Component annotation like you did and for example with the @Bean annotation in a "config" class. In such a "config" class you can have several methods which are annotated with @Bean and each of the beans has the methodname as simple name. The full qualified name of the class would not be possible in such a case.

See the example how you can create Spring Beans in your application:

/**
 * Beans which are created from this class use by default the name of the method
 * because it's the name which makes most sense for these Beans
*/ 
@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
    @Bean
    public ClientService clientService() {
        return new ClientServiceImpl();
    }
}
/**
 * The created bean uses the simple type name as bean name.
*/
@Component
public class CustomService {
    ....
}

But you can also override this behaviour with a custom implementation of BeanNamingStrategy.

For more details about naming beans and overriding the naming strategy you can read: https://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch02s02.html

the hand of NOD
  • 1,639
  • 1
  • 18
  • 30
  • And I still think that this is the correct answer of the question? If it's not just a downvote would be not enough wouldn't it? – the hand of NOD Mar 04 '20 at 07:30
  • I'm not sure who downvoted (someone also downvoted User9123's answer which is a fair answer). Regardless, I'm not sure I completely understand your answer yet, I'll try to read more – Dean Gurvitz Mar 04 '20 at 11:14
  • @DeanGurvitz: Did you get it or is shall I update my answer? I tried to explain that there are different ways for creating beans and that's the reason why they do not use the full qualified class name as default, because some beans are created by methods and therefore the methodname is used as "simple name". – the hand of NOD Mar 04 '20 at 13:25
  • I think the main point which I realised through User9123's answer and can also be concluded from yours is that if there is only 1 bean of the required type, then the name of the bean is irrelevant, and the requirement that bean names are unique is a separate requirement – Dean Gurvitz Mar 04 '20 at 13:41
  • 1
    Ok. I added an example to make it more clear for others too. But "the name of the bean is irrelevant" is not fully true, because it still should be as most meaningful as possible :-) – the hand of NOD Mar 04 '20 at 14:45