Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

|
//

A business component architecture with Spring 3.0/3.1 – Part 3: Properties

19.1.2012 | 6 minutes of reading time

This is the third post in a series describing a business component architecture using Spring 3.0/3.1 features like Java based configuration, the environment abstraction, bean definition profiles and property sources.

After the general structure and resources I’m gonna talk about properties this time. The subject seems to be an easy one, developers with Spring background will probably point to the PropertyPlaceholderConfigurer and mark it off, but anyway – in the environment described (>100 developers, many departments, applications may use any business component) we have some aspects complicating things.

What are properties?

But let’s step back and take a look at what properties are. Properties are used to source out configuration data from the application, being set later by somebody / something standing outside the application. We have two groups of properties:

  • Properties determining application behaviour, like different modes (category A).
  • Properties configuring resources, database URLs, queue names or the like (category B).

Properties are static and don’t change during runtime. For values changing dynamically we have other concepts (databases, JMX).

Reading properties is infrastructure code and shouldn’t be mixed with business logic. In the context of the business component architecture it means that properties are read in the configuration project and injected into the business component via dependency injection. Let’s take a look at an example.

Let’s say the PartnerService had a flag read-only, a property determining application behaviour.

1public class PartnerServiceImpl implements PartnerService {
2 
3    private boolean readOnly;
4 
5    private JdbcTemplate jdbcTemplate;
6 
7    public PartnerServiceImpl(JdbcTemplate jdbcTemplate, boolean readOnly) {
8        this.jdbcTemplate = jdbcTemplate;
9        this.readOnly = readOnly;
10    }
11 
12    public void savePartner(Partner partner) {
13        if (readOnly) {
14            throw new IllegalStateException(
15                    "Persisting partner not allowed in read-only mode!");
16        }
17        // save Partner
18    }
19 
20    public Partner getPartner(long id) {
21        return this.jdbcTemplate.queryForObject("SELECT ....",
22                new PartnerRowMapper, id);
23    }
24 
25}

Properties and Spring 3.1’s environment abstraction

The flag isn’t read directly in the PartnerServiceImpl, the PartnerConfig is taking over this part. With Spring 3.1’s environment abstraction (check here and here ) it looks like this:

1@Import(HighLevelDataAccessConfig.class)
2@PropertySource("classpath:partner.properties")
3@Configuration
4public class PartnerConfig {
5 
6    @Autowired
7    private Environment environment;
8 
9    @Autowired
10    private HighLevelDataAccessConfig dataAccessConfig;
11 
12    @Bean
13    public PartnerService partnerService() throws Exception {
14        return new PartnerServiceImpl(dataAccessConfig.jdbcTemplate(),
15                environment.getProperty("partner.readonly", boolean.class));
16    }
17 
18}

Here the new elements in detail:

1@PropertySource("classpath:partner.properties")

If you use the annotation PropertySource, all properties in partner.properties are added to the Environment.

1@Autowired
2    private Environment environment;

The Environment exists in every ApplicationContext (from Spring 3.1) and can be injected into @Configuration objects (of course in any other Spring bean as well, but you shouldn’t do that, because it means mixing business code with configuration code!).

1environment.getProperty("partner.readonly", boolean.class)
1public class DatabaseReaderDelegate {
2 
3    private JdbcTemplate jdbcTemplate;
4    private String stage;
5    private String runtime;
6    private String application;
7 
8    private static final String SQL = "SELECT p.value FROM PROPERTYTABLE p WHERE stage = ? AND runtime = ? AND application = ? AND key = ?";
9 
10    public DatabaseReaderDelegate(DataSource dataSource, String stage,
11            String runtime, String application) {
12        jdbcTemplate = new JdbcTemplate(dataSource);
13        this.stage = stage;
14        this.runtime = runtime;
15        this.application = application;
16    }
17 
18    public String getProperty(String property) {
19        String value = null;
20        try {
21            value = jdbcTemplate.queryForObject(SQL, String.class, stage,
22                    runtime, application, property);
23        } catch (EmptyResultDataAccessException e) {
24            try {
25                value = jdbcTemplate.queryForObject(SQL, String.class, stage,
26                        runtime, "default", property);
27            } catch (EmptyResultDataAccessException e2) {
28                // nothing to do
29            }
30        }
31        return value;
32    }
33 
34}
35 
36public class DatabasePropertySource extends
37        PropertySource<DatabaseReaderDelegate> {
38 
39    public DatabasePropertySource(DataSource dataSource, String stage,
40            String runtime, String application) {
41        super("database_propertysource", new DatabaseReaderDelegate(dataSource,
42                stage, runtime, application));
43    }
44 
45    @Override
46    public Object getProperty(String key) {
47        return this.source.getProperty(key);
48    }
49 
50}

This PropertySource needs a DataSource and knowledge about stage, runtime and application. When a property is asked for, it first looks for an entry for application, stage and runtime. If there is one, it is given back. If there is none, it checks if there is an default entry for stage and runtime. If there is none as well, it returns null, which indicates that this PropertySource doesn’t have a value for this property.
The DatabasePropertySource is set on the ApplicationContext with an ApplicationContextInitializer.

1public class CustomApplicationContextInitializer implements
2        ApplicationContextInitializer<ConfigurableApplicationContext> {
3    public void initialize(ConfigurableApplicationContext ctx) {
4        String stage = System.getProperty("de.codecentric.stage");
5        String runtime = System.getProperty("de.codecentric.runtime");
6        String application = System.getProperty("de.codecentric.application");
7        String dbURL = System.getProperty("de.codecentric.db.url");
8        String dbUser = System.getProperty("de.codecentric.db.user");
9        String dbPassword = System.getProperty("de.codecentric.db.password");
10        ctx.getEnvironment().setActiveProfiles(runtime);
11        BasicDataSource dataSource = new BasicDataSource();
12        dataSource.setUrl(dbURL);
13        dataSource.setUsername(dbUser);
14        dataSource.setPassword(dbPassword);
15        DatabasePropertySource databasePropertySource = new DatabasePropertySource(
16                dataSource, stage, runtime, application);
17        ctx.getEnvironment().getPropertySources()
18                .addFirst(databasePropertySource);
19    }
20}

Beside reading the JVM properties and initializing the DataSource two important things are happening here:

1ctx.getEnvironment().setActiveProfiles(runtime);

Here we set the value read in for runtime as the active profile. Doing that the correct resource definitions are used (take a look at the second blog post for more on that).

1ctx.getEnvironment().getPropertySources()
2                .addFirst(databasePropertySource);

Here we set our own DatabasePropertySource as first PropertySource to be checked by Spring. Only if the DatabasePropertySource doesn’t have a value for a key other PropertySources are being asked. The default properties files added to the jar of the component are belonging to those PropertySources.
In a web application an ApplicationContextInitializer can be used with a ServletContext parameter:

1<context-param>
2    <param-name>contextInitializerClasses</param-name>
3    <param-value>de.codecentric.CustomApplicationContextInitializer</param-value>
4</context-param>

Of course there is a lot of optimization potential in these sources, a caching is missing, the value for runtime can be determined somehow cleverly without JVM property, the application is impractical as a JVM property, because you might wanna run more than one application in a JVM, the DataSource could be retrieved via JNDI with a fallback to JVM properties and so on. It’s most important, that the concept is clear.

Conclusion

Reading properties is infrastructure code and therefore separated from business logic by using Spring’s Environment to read properties in @Configuration classes and setting them via Dependency Injection on business components.
By using our own DatabasePropertySource we get a simple build and deployment process without complex replacements. It’s easy to include a process that makes revisions of properties whenever they are changed. A developer of an application normally does not have to set properties because there are reasonable defaults. Anyway he may overwrite whatever property he wants to change.
If we create the web.xml including the definition of the ApplicationContextInitializer with a Maven archetype, the concept works out-of-the-box.

Completion of the example

In the preceding blog post I presented the low level data access configurations omitting the properties. That’s how they look like with properties:

1@Profile("websphere")
2@Configuration
3public class JndiDataAccessConfig implements LowLevelDataAccessConfig {
4 
5    @Autowired
6    private Environment env;
7 
8    @Bean
9    public DataSource dataSource() throws Exception {
10        InitialContext initialContext = new InitialContext();
11        return (DataSource) initialContext.lookup(env
12                .getProperty("infrastructure.db.jndi"));
13    }
14 
15    @Bean
16    public PlatformTransactionManager transactionManager() {
17        return new WebSphereUowTransactionManager();
18    }
19 
20}
21 
22@Profile("standalone")
23@Configuration
24public class StandaloneDataAccessConfig implements LowLevelDataAccessConfig {
25 
26    @Autowired
27    private Environment env;
28 
29    @Bean
30    public DataSource dataSource() {
31        BasicDataSource dataSource = new BasicDataSource();
32        dataSource.setUrl(env.getProperty("infrastructure.db.url"));
33        dataSource.setUsername(env.getProperty("infrastructure.db.user"));
34        dataSource.setPassword(env.getProperty("infrastructure.db.password"));
35        return dataSource;
36    }
37 
38    @Bean
39    public PlatformTransactionManager transactionManager() {
40        return new DataSourceTransactionManager(dataSource());
41    }
42 
43}

Since it’s impossible to have reasonable defaults for these properties that we could add to a properties file inside the jar, we don’t specify such a file. The properties have to be in the database or added by another PropertySource.

What do we need to do for configuring a web application offering services from CashingService?
The web application is created with a Maven archetype that creates a web.xml already containing the ApplicationContextInitializer for the DatabasePropertySource.
There are five properties relevant for the application:

  • partner.readonly -> partner.properties contains the default false, sufficient in this case.
  • infrastructure.db.jndi -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
  • infrastructure.db.user -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
  • infrastructure.db.url -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.
  • infrastructure.db.password -> the database contains defaults for all stages and relevant runtimes, sufficient in this case.

The developer can include the CashingService via CashingConfig without caring about the properties.
And if he wants, he can overwrite every property by adding a database entry.

|

share post

Likes

0

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.