Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Managing Environment-Specific Beans with Spring's @Profile Annotation

Tech May 12 2

Spring's @Profile annotation provides a mechanism to conditionally register beans or configuration classes based on the active runtime environment. This enables flexible configuration managemnet across different deployment stages.

Basic Usage

On Configuration Classes

@Configuration
@Profile("development")
public class DevelopmentConfig {

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://localhost:3306/dev_db")
            .username("dev_user")
            .password("dev_pass")
            .build();
    }
}

@Configuration
@Profile("production")
public class ProductionConfig {

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://prod-cluster:3306/prod_db")
            .username("prod_user")
            .password("prod_pass")
            .build();
    }
}

On Bean Methods

@Configuration
public class AppConfiguration {

    @Bean
    @Profile("development")
    public MessageService fakeMessageService() {
        return new FakeMessageService();
    }

    @Bean
    @Profile("production")
    public MessageService realMessageService() {
        return new RealMessageService();
    }
}

On Component Classes

@Service
@Profile("development")
public class DevelopmentUserService implements UserService {
    // Development-specific implementation
}

@Service
@Profile("production")
public class ProductionUserService implements UserService {
    // Production-specific implementation
}

Activating Profiles

Using Properties Files

# application.yml
spring:
  profiles:
    active: development
    group:
      development: dev-database, dev-queue
      production: prod-database, prod-queue

Using Command Line

java -jar app.jar --spring.profiles.active=production

Programmatically

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setAdditionalProfiles("development");
        app.run(args);
    }
}

Implementation Overview

Core Interfaces

public interface Environment extends PropertyResolver {
    String[] getActiveProfiles();
    String[] getDefaultProfiles();
    boolean acceptsProfiles(Profiles profiles);
}

public interface ConfigurableEnvironment extends Environment {
    void setActiveProfiles(String... profiles);
    void addActiveProfile(String profile);
    void setDefaultProfiles(String... profiles);
}

ProfileCondition

public class ProfileCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attributes = 
            metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attributes != null) {
            String[] profiles = (String[]) attributes.getFirst("value");
            return context.getEnvironment()
                .acceptsProfiles(Profiles.of(profiles));
        }
        return true;
    }
}

Annotation Definition

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
    String[] value();
}

Advanced Techniques

Combining Profiles

@Configuration
public class CompositeProfileConfig {

    @Bean
    @Profile({"development", "testing"})
    public DataSource testingDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }

    @Bean
    @Profile("!development & !testing")
    public DataSource productionDataSource() {
        return new DriverManagerDataSource();
    }
}

Using Profile Expressions

@Configuration
public class ExpressionProfileConfig {

    @Bean
    @Profile("development | testing")
    public SecurityConfig devTestSecurity() {
        return new LaxSecurityConfig();
    }

    @Bean
    @Profile("production & !us")
    public SecurityConfig nonUsSecurity() {
        return new RegionalSecurityConfig();
    }
}

Default Profile

@Configuration
public class DefaultProfileConfig {

    @Bean
    @Profile("default")
    public DataSource fallbackDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

Practical Examples

Multi-Environment Logging

@Configuration
public class EnvironmentLoggingConfig {

    @Bean
    @Profile("development")
    public LoggingService verboseLogger() {
        return new VerboseLoggingService();
    }

    @Bean
    @Profile("production")
    public LoggingService conciseLogger() {
        return new ConciseLoggingService();
    }

    @Bean
    @Profile("development")
    public CacheConfig localCache() {
        return new LocalCacheConfig();
    }

    @Bean
    @Profile("production")
    public CacheConfig clusteredCache() {
        return new ClusteredCacheConfig();
    }
}

Testing with Profiles

@SpringBootTest
@ActiveProfiles("testing")
public class UserServiceTest {

    @Autowired
    private UserService service;

    @Test
    public void testWithTestingProfile() {
        // Uses testing environment configuration
    }
}

Conditional Configuration

@Configuration
public class ConditionalServiceConfig {

    @Bean
    @Profile("development")
    @ConditionalOnProperty(name = "features.debug", havingValue = "true")
    public DebugService debugService() {
        return new DebugServiceImpl();
    }

    @Bean
    @Profile("production")
    @ConditionalOnClass(name = "com.amazonaws.services.s3.AmazonS3")
    public StorageService cloudStorageService() {
        return new S3StorageService();
    }
}

The profile resolution process operates as follows:

  1. The Spring container initializes the Environment at startup.
  2. Active profiles are determined from properties files, command-line arguments, or programmatic configuration.
  3. When processing @Configuration classes and bean definitions, the ProfileCondition evaluates @Profile annotations.
  4. Beans are created only if their associated profile matches the currently active set.
  5. The profile state remains constant throughout the application's lifecycle.

Best practices include:

  • Define meaningful and consistent profile names.
  • Keep profile granularity appropriate for your deployment needs.
  • Avoid overusing profiles; prefer property-based configuration where possible.
  • Provide sensible default configurations.
  • Document profile usage and expected behavior.

Leveraging @Profile effectively enables robust, environment-aware conifguration, enhancing application maintainability and simplifying deployment workflows.

Tags: spring

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.