Dean Gurvitz
Dean Gurvitz

Reputation: 1072

Why doesn't Spring's @Component annotation use the full class name?

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.

Upvotes: 1

Views: 3689

Answers (3)

the hand of NOD
the hand of NOD

Reputation: 1769

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

Upvotes: 0

Makoto
Makoto

Reputation: 106430

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 {}

Upvotes: 2

User9123
User9123

Reputation: 1733

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

Upvotes: 2

Related Questions