admin管理员组

文章数量:1123412

I have a Spring webapp on Tomcat with two implementations of an interface. I want to be able to decide at configuration time (not necessarily at runtime) which one to instance, and I am using @Profile.

public interface ClienteFarmacia {}

@Component
@Profile("farmaciaDb")
public class ClienteFarmaciaDbImpl implements ClienteFarmacia {}

@Component
@Profile("farmaciaWs")
public class ClienteFarmaciaWsImpl implements ClienteFarmacia {}

If I enable the profile by setting an environment variable (export spring_profiles_active=farmaciaDb) it works as expected.

But as I already have a configuration properties file and I would like to centralize all of it there, I am trying to activate the profile programmatically. I have a @Component that in its @PostConstruct loads that properties, and in it I use the ConfigurableEnvironment. The logic works and is executed before the ClienteFarmacia instance is created, but anyway the instantiation fails because Spring does not find which instance to use.

The configuration class:

@Component
public class Configuracion {
    @Autowired
    private ConfigurableEnvironment env;

    @PostConstruct
    void init() {
       [... Load data]
       switch (farmaciaClientType) {
        case "BD":
            log.debug("Asignado activeProfile farmaciaDb");
            this.env.setActiveProfiles("farmaciaDb");
            break;
        case "WS":
            log.debug("Asignado activeProfile farmaciaWs");
            this.env.setActiveProfiles("farmaciaWs");
            break;
        default:
            log.error("farmacia.tipoConsulta es " + tipoConsultaFarmacia + ", valores válidos BD o WS");
            throw new RuntimeException("Error cargando configuración");
        }
    }

[2025-01-10T14:59:52.145+01:00] [main] DEBUG e.s.d.e.e.p.config.Configuracion.init(123) - Asignado activeProfile farmaciaDb
[2025-01-10T14:59:52.154+01:00] [main] WARN o.s.w.c.s.AnnotationConfigWebApplicationContext.refresh(599) - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'autenticacionController': Unsatisfied dependency expressed through field 'idp'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'idp': Unsatisfied dependency expressed through field 'clienteFarmacia'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'es.ssib.dtic.ereceta.ehdsi.portal.farmacia.ClienteFarmacia' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
[2025-01-10T14:59:52.156+01:00] [main] INFO o.s.o.j.LocalEntityManagerFactoryBean.destroy(651) - Closing JPA EntityManagerFactory for persistence unit 'PortalEhdsiDB'
[2025-01-10T14:59:52.160+01:00] [main] ERROR o.s.web.servlet.DispatcherServlet.initServletBean(534) - Context initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'autenticacionController': Unsatisfied dependency expressed through field 'idp'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'idp': Unsatisfied dependency expressed through field 'clienteFarmacia'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'es.ssib.dtic.ereceta.ehdsi.portal.farmacia.ClienteFarmacia' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:713)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:693)

How can I get what I want?

I have a Spring webapp on Tomcat with two implementations of an interface. I want to be able to decide at configuration time (not necessarily at runtime) which one to instance, and I am using @Profile.

public interface ClienteFarmacia {}

@Component
@Profile("farmaciaDb")
public class ClienteFarmaciaDbImpl implements ClienteFarmacia {}

@Component
@Profile("farmaciaWs")
public class ClienteFarmaciaWsImpl implements ClienteFarmacia {}

If I enable the profile by setting an environment variable (export spring_profiles_active=farmaciaDb) it works as expected.

But as I already have a configuration properties file and I would like to centralize all of it there, I am trying to activate the profile programmatically. I have a @Component that in its @PostConstruct loads that properties, and in it I use the ConfigurableEnvironment. The logic works and is executed before the ClienteFarmacia instance is created, but anyway the instantiation fails because Spring does not find which instance to use.

The configuration class:

@Component
public class Configuracion {
    @Autowired
    private ConfigurableEnvironment env;

    @PostConstruct
    void init() {
       [... Load data]
       switch (farmaciaClientType) {
        case "BD":
            log.debug("Asignado activeProfile farmaciaDb");
            this.env.setActiveProfiles("farmaciaDb");
            break;
        case "WS":
            log.debug("Asignado activeProfile farmaciaWs");
            this.env.setActiveProfiles("farmaciaWs");
            break;
        default:
            log.error("farmacia.tipoConsulta es " + tipoConsultaFarmacia + ", valores válidos BD o WS");
            throw new RuntimeException("Error cargando configuración");
        }
    }

[2025-01-10T14:59:52.145+01:00] [main] DEBUG e.s.d.e.e.p.config.Configuracion.init(123) - Asignado activeProfile farmaciaDb
[2025-01-10T14:59:52.154+01:00] [main] WARN o.s.w.c.s.AnnotationConfigWebApplicationContext.refresh(599) - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'autenticacionController': Unsatisfied dependency expressed through field 'idp'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'idp': Unsatisfied dependency expressed through field 'clienteFarmacia'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'es.ssib.dtic.ereceta.ehdsi.portal.farmacia.ClienteFarmacia' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
[2025-01-10T14:59:52.156+01:00] [main] INFO o.s.o.j.LocalEntityManagerFactoryBean.destroy(651) - Closing JPA EntityManagerFactory for persistence unit 'PortalEhdsiDB'
[2025-01-10T14:59:52.160+01:00] [main] ERROR o.s.web.servlet.DispatcherServlet.initServletBean(534) - Context initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'autenticacionController': Unsatisfied dependency expressed through field 'idp'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'idp': Unsatisfied dependency expressed through field 'clienteFarmacia'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'es.ssib.dtic.ereceta.ehdsi.portal.farmacia.ClienteFarmacia' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:713)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:693)

How can I get what I want?

Share Improve this question asked 12 hours ago SJuan76SJuan76 24.8k6 gold badges51 silver badges92 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

Can you try the following solution?

  • Instead of setting the profile within a @PostConstruct method, you should configure the profiles early in the application's startup process. The most common approach is to configure the active profile programmatically before the Spring context refreshes.
  • You can use an ApplicationContextInitializer to set the active profiles before the Spring context is initialized.

Here's how you can implement it:

Create an ApplicationContextInitializer Implementation

This initializer allows you to set the active profiles before Spring starts scanning for beans.

public class ProfileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        String farmaciaClientType = System.getenv("FARMACIA_CLIENT_TYPE"); // Or load it from a config file

        switch (farmaciaClientType) {
            case "BD":
                applicationContext.getEnvironment().setActiveProfiles("farmaciaDb");
                break;
            case "WS":
                applicationContext.getEnvironment().setActiveProfiles("farmaciaWs");
                break;
            default:
                throw new RuntimeException("Invalid farmacia client type");
        }
    }
}

Register the Initializer in web.xml (or Java Config)

If you're using web.xml, you can register the initializer like this:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>com.example.ProfileApplicationContextInitializer</param-value>
</context-param>

Alternatively, if you're using a Spring Boot application or Java-based configuration, you can add the initializer to your application class:

@SpringBootApplication
public class MyApplication {
    
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);
        app.addInitializers(new ProfileApplicationContextInitializer());
        app.run(args);
    }
}

Profile-Scoped Beans

Make sure that the beans (ClienteFarmaciaDbImpl and ClienteFarmaciaWsImpl) are defined with the correct profiles:

@Component
@Profile("farmaciaDb")
public class ClienteFarmaciaDbImpl implements ClienteFarmacia {}

@Component
@Profile("farmaciaWs")
public class ClienteFarmaciaWsImpl implements ClienteFarmacia {}

I think you're failing to take advantage of some of the tools Spring Boot offers to manage some of this stuff for you. @Configuration and @AutoConfiguration classes are designed to allow you to conditionally create different beans to establish the logic you want based on your environment to. In your case you could create something like:

@AutoConfiguration
public class ClienteFarmaciaAutoConfiguration {
    @Bean
    @Profile("farmaciaDb")
    public ClienteFarmacia clienteFarmacia() {
        return new ClienteFarmaciaDbImpl();
    }

    @Bean
    @Profile("farmaciaWs")
    public ClienteFarmacia clienteFarmacia() {
        return new ClienteFarmaciaWsImpl();
    } 
}

It would also be a best practice to mark one of these as @Primary incase both profiles are ever set.

If you can, its likely better to use ConfigurationProperties to control this behavior rather than using profiles. To do so, you can replace the @Profile annotation with @ConditionalOnProperty and the appropriate values.

Lastly, note that you will need to ensure your project has done all of the other setup needed on your main class to use Spring Boots Configurations. There are plenty of online articles on how to do so, its one of the core basic concepts Spring Boot offers

本文标签: tomcatProblem activating Spring profile through ConfigurableEnvirnomentStack Overflow