Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Bean Wiring

I'm learning the basics of Spring and right now I'm going over Beans/wiring. This question might not make sense, it's just something I'm thinking about after reading/trying out some examples from the book Spring into Action by Craig Walls. Say there is this interface.

public interface CompactDisc {
    void play()
}

And I have two classes that implement this interface.

public class HybridTheory implements CompactDisc { }
public class Meteora implements CompactDisc { }

My configuration class uses component scanning and creates beans from these two classes. Now if I had a test class that has an instance of Compact disc and is wired using autowired

public class myTest {
    @Autowired
    private CompactDisc cd;
}  

there would be a problem correct? My question is, how do you wire it so that it uses one bean over the other? Or is this not a real situation/should I make the property of a specific class, not of the interface? I guess I'm just having difficulty wrapping my head around wiring.

like image 855
user3369427 Avatar asked Feb 11 '23 06:02

user3369427


2 Answers

Whenever you run into situation that you have more than one implementing same interface there are few approaches you can take.

1. Use @Qualifier annotation

If you use classpath scanning, your classes may look like:

@Component
public class HybridTheory implements CompactDisc { }

@Component
public class Meteora implements CompactDisc { }

Each Spring bean gets an id assigned by a container and by default its a class name starting with lower letter. In case of these two components that would be:

  • hybridTheory
  • meteora

You may also want to pick custom name by passing name parameter to @Component annotation, for example: @Component("myFavLinkinParkAlbum").

Then whenever you autowire CompactDisc bean you can pass @Qualifier annotation and tell Spring which exactly bean you have in mind.

@Service
class Player {
    private final CompactDisc compactDisc;

    // note that starting from Spring 4.3 
    // you can omit @Autowired annotation from constructor
    Player(@Qualifier("hybridTheory") CompactDisc compactDisc) {
        this.compactDisc = compactDisc;
    }
}

2. Mark one of your beans as @Primary

If in majority of injection points you inject always the same bean and the other one is used rarely you may consider marking the former with @Primary.

@Component
@Primary
public class HybridTheory implements CompactDisc { }

Whenever Spring finds out that there is more than one bean that matches injection point it will inject the one annotated with @Primary. Note that if there is more than one bean annotated with @Primary and implementing interface used in injection point the problem remains.

3. Use Java configuration

Although @Qualifier and @Primary solve the problem sometimes you may find that to learn which implementation is injected you need to travel a lot through source code. Sometimes the cleaner approach is to drop @Component (or other stereotype annotations) from your classes and declare beans using Java configuration:

public class HybridTheory implements CompactDisc { }

public class Meteora implements CompactDisc { }

@Configuration
class RecordsConfiguration {

    @Bean
    CompactDisc hybridTheory() {
        return new HybridTheory();
    }

    @Bean
    CompactDisc meteora() {
        return new Meteora();
    }

    @Bean
    Player player() {
        return new Player(meteora());
    }
}

Choose the one that fits your use case the best!

like image 103
Maciej Walkowiak Avatar answered Feb 13 '23 21:02

Maciej Walkowiak


A simple and elegant approach is to use byType whenever possible. When that is not possible the byName approach can be used.

One way of using this is to name the beans manually - then the bean names will not differ after you change the classname. Note that naming the beans should only be used if you have multiple implementation of the same type, otherwise naming is not really needed. In the example below, the naming is part of the annotation e.g @Component("hybridTheory"). This means that you can change the classname of HybridTheory to whatever you want and the bean name will still be hybridTheory.

When you inject something byName you can use the @Qualifier annotation to specify which named bean you require. I personally prefer it to @Resource since you can use @Qualifier in constructors (I prefer constructor injection over setter- and field-based injection).

You should NOT inject the concrete types - that is what dependency injection is all about. Someone else (i.e. Spring) will handle the object creation for you ;)

public interface CompactDisc {
    void play();
}

@Component
class CompactDiscPlayer {
    @Autowired
    CompactDiscPlayer(@Qualifier("hybridTheory") final CompactDisc compactDisc) {
        // The bean of type HybridTheory will be used
        compactDisc.play();
    }
}

@Component("hybridTheory")
class HybridTheory implements CompactDisc {
    public void play() {
        System.out.println(getClass().getSimpleName());
    }
}

@Component("meteora")
class Meteora implements CompactDisc {
    public void play() {
        System.out.println(getClass().getSimpleName());
    }
}

The Spring docs describes the various options here.

like image 23
wassgren Avatar answered Feb 13 '23 21:02

wassgren