Externalising configuration, switching beans per environment, and the expression language — three loose threads from earlier lessons, tied together.
@Value reads them; what @Profile does,
where it goes, and how profiles are activated; and — the recurring one — the difference between
${...} and #{...}.
Hard-coding values is brittle. Spring reads configuration from an Environment
abstraction populated by property sources (files, system properties, environment variables). You add a
file as a source with @PropertySource:
@Configuration
@PropertySource("classpath:app.properties") // adds a source to the Environment
public class AppConfig {
@Value("${app.timeout:30}") // inject property; 30 is the default if missing
private int timeout;
@Autowired Environment env; // or read programmatically
// env.getProperty("app.timeout")
}
@Value("${key}") injects a property; ${key:default} supplies a fallback if the key is absent.${...} placeholders are resolved by PropertySourcesPlaceholderConfigurer — a BeanFactoryPostProcessor (remember Lesson 6's family) that runs before beans are created.@PropertySource loads .properties, not YAML. (Spring Boot's application.yml is loaded by Boot itself — Lesson 15, not by @PropertySource.)@ConfigurationProperties (Boot — Lesson 15).A profile is a named group of beans that's only active in certain environments.
@Profile makes a bean conditional on the active profile(s):
@Bean
@Profile("dev") // only when 'dev' is active
public DataSource devDataSource() { return ...; }
@Bean
@Profile("!prod") // active when 'prod' is NOT active
public Mailer fakeMailer() { return ...; }
| Aspect | Detail |
|---|---|
| Can annotate | @Configuration class, @Bean method, @Component class, or another annotation (meta) — not a field |
| Expressions | !prod (not), {"p1","p2"} (either / OR), "p1 & p2" (both) |
| Activate via | spring.profiles.active property · SPRING_PROFILES_ACTIVE env var · -Dspring.profiles.active=… · env.setActiveProfiles(…) · @ActiveProfiles in tests |
| Multiple active | Yes — several profiles can be active at once |
@Profile is always registered, whatever profiles are active.
A bean marked @Profile("default") is active only when no other profile is set.
Those are different things — the exam checks you know it.
You've met ${...} and #{...} separately. Here they are side by side — this table is
the single highest-value thing on the page:
| Syntax | Is a… | Does | Example |
|---|---|---|---|
${...} | Property placeholder | Looks up a property in the Environment | @Value("${app.timeout}") |
#{...} | SpEL expression | Evaluates code: beans, methods, operators, literals | @Value("#{2 * 60}") |
#name | SpEL variable ref | References a variable in the eval context (#root, #this, …) | #{#root.name} |
SpEL #{...} can reference other beans, call methods, use operators, and read system properties:
@Value("#{systemProperties['user.timezone']}") String tz; // system property via SpEL
@Value("#{pricingService.vatRate * 100}") double vatPct; // reference another bean
@Value("#{T(java.lang.Math).PI}") double pi; // static method/field
${...} resolves a property; #{...} runs an expression. SpEL does
not resolve ${...} placeholders itself — that's the placeholder configurer's job.
A question hinges on this almost every exam.
Read "Bean Definition Profiles" and "Environment Abstraction", then skim the SpEL intro — you only need the annotation-based (@Value) usage.
The ${...} vs #{...} question and the "default profile" trap are the two to nail. Options shuffle on every load.
$/# reference questions, @PropertySource, and the
@Profile combination/target questions. Then re-run the Container Drill.
${}/#{} split still feels slippery, ask me for five more examples of each.
Say "continue" for Lesson 6 — the Bean Lifecycle, BeanPostProcessors & proxies (where a lot of
earlier threads finally connect).