10

CDI has the feature of Specialization, and I'm looking for that in the Spring world.

Details. In CDI, the @Specializes annotation allows one to change the behaviour of a bean just by overriding it. This is completely transparent to users of that bean, e.g. if we'd have

public class OneBean {
  public String whoAmI() { return "OneBean"; }
}

@Specializes
public class AnotherBean extends OneBean {
  @Override
  public String whoAmI() { return "AnotherBean"; }
}

we could

public class SomewhereElse {
  @Inject
  OneBean oneBean; // we know nothing of AnotherBean here!

  public void guessWhosThere() {
    return oneBean.whoAmI(); // yet it returns "AnotherBean"
  }
}

This gets really useful as soon as OneBean is actually used with and without AnotherBean. For example, if OneBean is in one.jar and AnotherBean is in another.jar, we can change the bean's behaviour just by reconfiguring the classpath.

Question. Does something like Specialization also exist in Spring?

I could only find the @Primary annotation, which however has a different semantics: @Primary does not replace one bean, but only marks one of multiple alternatives as the primary one. Especially, as I understood, I could not build a deep inheritance hierarchy as it's possible with @Specializes.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
fxnn
  • 978
  • 6
  • 19
  • 1
    What's stopping you from changing the signature to `public abstract class OneBean`? Conceptually it isn't as obvious, but if I understand `Specializes` correctly then it seems like you get the same functionality. – Floegipoky Nov 13 '14 at 15:42
  • 1
    Yes, you're right: in the given example, I could just make `OneBean` abstract. However, as it turns out, I wouldn't even need `@Specializes` in this case. The annotation gets really useful as soon as `OneBean` is actually used, for example in `one.jar`, and `AnotherBean` might change its behaviour, for example as soon as `another.jar` (containing `AnotherBean`) is in the classpath. – fxnn Nov 13 '14 at 15:50

3 Answers3

4

Short answer In Spring 4, this is not possible. Period. Still, in 2016, nothing like this is possible with Spring's obsolete dependency injection model.

Pavel Pscheidl
  • 334
  • 3
  • 11
1

Seems like there is no similar annotation in spring, but you can achive it via @Qualifier.

Beans:

@Resource("oneBean")
public class OneBean {
  public String whoAmI() { return "OneBean"; }
}

@Resource("anotherBean")
public class AnotherBean extends OneBean {
  @Override
  public String whoAmI() { return "AnotherBean"; }
}

SomewhereElse:

public class SomewhereElse {
  @Autowired
  @Qualifier("anotherBean")
  OneBean oneBean;

  public void guessWhosThere() {
    return oneBean.whoAmI(); // returns "AnotherBean"
  }
}

Edited.

Also you can develop your own annotation and use it in BeanPostProcessor, look at spring docs here

OR even better to use CustomAutowireConfigurer, see here

Maksym
  • 4,434
  • 4
  • 27
  • 46
  • I don't think this is what OP is looking for. If `YetAnotherBean` comes along, it won't do what he wants. – Floegipoky Nov 13 '14 at 16:08
  • 1
    True. I added a comment in the OPs example: `SomewhereElse` knows *nothing* of `AnotherBean`. With the `@Qualifier` annotation suggested here, we would depend on it (not by type, but by name). – fxnn Nov 13 '14 at 16:16
  • You must modify all injection points by adding a qualifier. Point of specialization is to completely replace one bean with another one without the need to edit your current code or inform other developers to use some kind of special qualifier. –  Jun 13 '17 at 08:12
1

With Spring boot, you could probably get a similar result by leveraging its auto-configure mechanism, e.g. with a bean condition such as @ConditionalOnMissingBean:

public class OneBean {
  public String whoAmI() { return "OneBean"; }
}

@Configuration
public class OneConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public OneBean getBean() { return new OneBean(); }
}

@Component
public class AnotherBean extends OneBean {
  @Override
  public String whoAmI() { return "AnotherBean"; }
}

However, you would have to make sure that all configurations are built accordingly if you don't know for sure which ones will be specialized:

public class OneBean {
  public String whoAmI() { return "OneBean"; }
}

public class AnotherBean extends OneBean {
  @Override
  public String whoAmI() { return "AnotherBean"; }
}

public class YetAnotherBean extends AnotherBean {
  @Override
  public String whoAmI() { return "YetAnotherBean"; }
}

@Configuration
public class OneConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public OneBean getBean() { return new OneBean(); }
}

@Configuration
public class AnotherConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public AnotherBean getBean() { return new AnotherBean(); }
}

@Configuration
public class YetAnotherConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public YetAnotherBean getBean() { return new YetAnotherBean(); }
}
// and so on...