Spring Qualifier and property placeholder
Asked Answered
T

6

28

Does anyone know if I should be able to use property placeholder as an expression in a Qualifier? I can't seem to get this working.

I am using spring 3.0.4.

@Controller
public class MyController {
   @Autowired
   @Qualifier("${service.class}")
   Service service;
}

@Service
@Qualifier("ServiceA")
ServiceA implements Service {
   public void print() {
       System.out.println("printing ServiceA.print()");
   } 
}

@Service
@Qualifier("ServiceB")
ServiceB implements Service {
   public void print() {
      System.out.println("printing ServiceB.print()");
   } 
}

XML:

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="file:/etc/config.properties"/>
</bean>

config.properties:

config.properties
service.class=serviceB
Tananarive answered 18/10, 2011 at 19:45 Comment(3)
what would be the point of doing this in a properties file and with @Qualifier when you could just choose how to wire the bean up in the XML instead?Mastership
Have you looked at spring-3 profile beans? It seems like that might be what you're looking for. It does seem like you should be able to do what you're asking, since spring-3 has @Value annotations that support resolving properties. But I just don't know for sure.Redskin
@matt Just Wanted to minimize the use of XML. And property file because different deployments will use different implementations.Tananarive
M
41

This works. You can leave off the service names if you just use the default spring bean name. serviceA vs ServiceA, etc.

@Controller
class MyController {
@Autowired(required=false)
@Qualifier("Service")
Service service;

public static void main(String[] args) {
   ApplicationContext context = new ClassPathXmlApplicationContext("app-ctx.xml", MyController.class);
   for(String s:context.getBeanDefinitionNames()){
       System.out.println(s);
       for(String t:context.getAliases(s)){
           System.out.println("\t" + t);
       }
   }
   context.getBean(MyController.class).service.print();
  }
}

public interface Service {
    void print();
}

@Service(value="ServiceA")
public class ServiceA implements example.Service {
    public void print() {
        System.out.println("printing ServiceA.print()");
    } 
}

@Service(value="ServiceB")
public class ServiceB implements example.Service {
    public void print() {
        System.out.println("printing ServiceB.print()");
    } 
}

XML:

<beans>
    <alias name="${service.class}" alias="Service"/>
    <context:property-placeholder location="example/app.properties"/>
    <context:component-scan base-package="example"/>
<beans>

Props:

service.class=ServiceB
Meridional answered 3/10, 2012 at 19:48 Comment(6)
Dunno why this has not been accepted. It is definetly the neatest way to do what the OP wanted. +1Nashoma
Is there any reason why you added required="false" on the @Autowired annotation?Merits
I just tried it and did not use required="false". Works as well. It's actually quite neat. The only thing I was missing was: <alias name="${service.class}" alias="Service"/> and that did the trick. Thanks!Paperweight
I wonder if there is a way to do this without the xml, just annotations?Thill
This works without XML: #27107633Hustler
Working solution without XML: https://mcmap.net/q/450417/-spring-qualifier-and-property-placeholderHustler
H
22

This solution works without XML and with properties file.

Yours classes improved:

MyController.java:

@Controller
public class MyController {
    @Autowired
    public MyController(@Qualifier("MyServiceAlias") MyService myService) {
        myService.print();
    }
}

ServiceA.java:

@Service("serviceA")
public class ServiceA implements MyService {
    @Override
    public void print() {
        System.out.println("printing ServiceA.print()");
    }
}

ServiceB.java:

@Service("serviceB")
public class ServiceB implements MyService {
    @Override
    public void print() {
        System.out.println("printing ServiceB.print()");
    }
}

application.properties (here you can change which class will be loaded):

service.class=serviceA

And important configuration file AppConfig.java:

@Configuration
public class AppConfig {

    @Autowired
    private ApplicationContext context;

    @Bean
    public MyService MyServiceAlias(@Value("${service.class}") String qualifier) {
        return (MyService) context.getBean(qualifier);
    }
}

Additional explanations:

  • Use @Qualifier only for field which will be autowired. For services, to specify bean name, use @Service.
  • If you want standard bean name you don't need to use @Service with specyify name. For example, standard bean name for ServiceA is serviceA (not ServiceA - see big first letter), so @Service("serviceA") redundant (@Service is enough).
  • I based AppConfig on this answer: Spring Bean Alias in JavaConfig.
  • This solution is better than this Spring Qualifier and property placeholder, because you don't need XML.
  • Tested on Spring Boot 1.5.7.
Hustler answered 2/11, 2017 at 11:23 Comment(0)
R
4

I would venture to guess the answer is no, just based on the write ups in a few javadoc pages. For example, see the docs for @Value:

http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Value.html

Notice they make special mention of using expressions in the annotation. For comparison, the docs for @Qualifier:

http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Qualifier.html

Which make no mention of expressions. Obviously not a definitive answer (but spring is generally very good on documentation). Also, if expressions were supported in the @Qualifier annotation I would expect they work the same way as the @Value annotation (just based on spring being a very consistent framework).

Spring 3.1 has the new profile bean feature, which seems like it can accomplish something like what you're trying to do. Here's a write up for that:

http://blog.springsource.com/2011/02/14/spring-3-1-m1-introducing-profile/

Redskin answered 18/10, 2011 at 20:23 Comment(0)
C
2

As a workarround, you can set the desired Spring service implementation based on its name in your config.properties.

@Controller
public class MyController {
  //add a String which will hold the name of the service to implement
  @Value("${service.class}")
  private String serviceToImplement;

  Service service;

  // now autowire spring service bean based on int name using setter
  @Autowired
  public void setService(ApplicationContext context) {
    service = (Service) context.getBean(serviceToImplement);
   }
}

@Service
 @Qualifier("ServiceA")
 ServiceA implements Service {
  public void print() {
   System.out.println("printing ServiceA.print()");
  } 
}

 @Service
 @Qualifier("ServiceB")
 ServiceB implements Service {
   public void print() {
    System.out.println("printing ServiceB.print()");
   } 
}

config.properties

service.class=serviceB
Cheque answered 23/3, 2020 at 18:16 Comment(0)
A
0

Just use @ConditionalOnProperty. If you don't need to use both of those services in one context, then your code could be something like:

@Service
@ConditionalOnProperty(value="storage.type", havingValue = "serviceA")
ServiceA implements Service {
   public void print() {
       System.out.println("printing ServiceA.print()");
   } 
}

@Service
@ConditionalOnProperty(value="service.class", havingValue = "serviceB")
ServiceB implements Service {
   public void print() {
      System.out.println("printing ServiceB.print()");
   } 
}

@Controller
public class MyController {
   @Autowired
   Service service;
}
Acetylide answered 23/8, 2023 at 7:19 Comment(0)
T
-5

Maybe give this a whirl:

@Controller
public class MyController {

   private String serviceId;

   @Value("${serviceId}")
   public void setServiceId(String serviceId) {
      this.serviceId = serviceId;
   }

   @Autowired
   @Qualifier(serviceId)
   Service service;
}
Tichon answered 18/10, 2011 at 20:51 Comment(1)
@Aaron docs.oracle.com/javase/1.5.0/docs/guide/language/… Annotations consist of an at-sign (@) followed by an annotation type and a parenthesized list of element-value pairs. The values must be compile-time constants So the workaround with an instance String will not work course of a compile error ....Stevestevedore

© 2022 - 2024 — McMap. All rights reserved.