Installers
Examples: creating database schema, installing default or test data…
An @Installer
annotation marks a class as an installer and allows you to specify the important installer attributes.
attribute | description |
---|---|
name |
Optional (unique) name of the installer. If not specified, the fully qualified class name of the installer class will be used. |
description |
Descriptive text of what will be installed. |
phase |
When this installer should run during the context bootstrap. See bootstrap phases. |
runCondition |
Condition when this installer should run. See run conditions. |
version |
Version number of the installer.
This value should be incremented to enforce a new run of the same installer.
Only relevant if the run condition is |
An @InstallerMethod
annotation marks a method within an @Installer
marked class as a method that should be executed when the installer is executed.
Using the required attribute, it is possible to define the method’s parameters as optional, which means they will not be autowired if they are not found.
Installer detection
By default installers are detected from the classpath by scanning the installers package relative to the package of the AcrossModule
.
Changing the packages to scan for installers can be done by overriding the getInstallerScanPackages()
method on AcrossModule
.
If you manually want to specify a list of installers, you can do so by overriding the getInstallers() method.
Though still possible for compatibility reasons, installers should be specified with their Class .
|
Installer tracking and synchronization
The execution of installers is tracked using the AcrossInstallerRepository
.
This allows for installers to execute only if they have not yet been executed (see run conditions).
In addition, installer execution is synchronized for multiple applications connecting on the same database.
This is done through the DistributedLockRepository
that is created in the core schema.
A lock will be taken on the database as soon as a single installer wishes to execute, and will be released once that installer has finished executing.
The lock owner will contain the hostname of the computer running the application, as well as the displayName
of the AcrossContext
.
Because of tracking and synchronization, a datasource is required in order to execute installers.
The AcrossInstallerRepository can be wired as a bean in installers.
This can be helpful in migration trajectories for example to rename installers.
|
Bootstrap phases
Installers are executed during a specific phase of the context bootstrap. This phase should be specified for every installer. The phase will determine which beans are available in the installer.
The following bootstrap phases exist:
- BeforeContextBootstrap
-
The installer is executed before any of the modules in the AcrossContext are bootstrapped. Only beans from the parent
ApplicationContext
or the installerApplicationContext
are available. - BeforeModuleBootstrap
-
The installer is executed before the module that owns it bootstraps, but after all previous modules have bootstrapped. All beans from previous modules will also be available to the installer.
- AfterModuleBootstrap
-
The installer is executed after the module that owns it has bootstrapped, but before the next module bootstraps. All beans from the current module are available.
- AfterContextBootstrap
-
The installer is executed after all modules have bootstrapped, but before the
AcrossContextBootstrappedEvent
is published. All beans from all modules are available.
Run conditions
The following run conditions are supported for an installer:
- AlwaysRun
-
The installer will always be executed, everytime the module bootstraps.
- VersionDifferent
-
The installer will only execute if the version attribute is higher than the version previously executed.
In addition to the standard run conditions, you can use @Conditional
annotations on installer classes.
This includes for example the use of @ConditionalOnAcrossModule
to execute installers if certain modules are active.
Using AlwaysRun installers will always cause the bootstrap lock to be taken. This might cause a slowdown when starting multiple applications on the same datasource simultaneously. |
Installer group
In addition to the standard @Installer
attributes, an @InstallerGroup
annotation can be specified.
This allows grouping types of installers together (for example schema) and overruling their execution using InstallerSettings
.
Installer execution order
If all conditions are fulfilled, installers will be executed in a predetermined order:
-
in bootstrap phase order
-
in installer order (according to
@Order
annotations on the installer class)
Installers without an @Order annotation behave as if they have an order index of 0.
In that case the installer registration order will be used as a fallback, with installers specified through getInstallers() having a higher precedence.
|
Datasources and installers
An AcrossContext
requires at least one datasource if modules need to run installers.
Certain core features like the DistributedLockRepository
also require the core schema to be installed and will require a valid datasource to be configured.
The main datasource is available for all modules as a bean named acrossDataSource
.
Optionally a separate installer datasource can be configured that will be available as acrossInstallerDataSource
.
If no separate installer datasource is specified, the default across datasource will be used.
A valid default datasource is always required for installers to run. It is not possible to configure only an installer datasource as the distributed locking mechanism uses the default datasource. |
The installer datasource is the default datasource used for all AcrossLiquibaseInstaller
instances.
Installer ApplicationContext
If installers need to be run for a module, a specific ApplicationContext
is created in which the installers will be wired as beans.
This ApplicationContext
can exist before the actual module ApplicationContext
does.
However, all beans from the parent Across context and the module context - when created - are available in installers.
Installer contexts are temporary, once the Across context has bootstrapped they are closed.
Configuration and other annotated classes can be added to the installer context by using ApplicationContextConfigurer
implementations, either on the AcrossContext
or on an AcrossModule
.
By default, the package installers.config relative to the module package will be scanned for beans to be added to the installer ApplicationContext
.
@Configuration
class Config implements AcrossContextConfigurer
{
/**
* Installer tracking will be done on this datasource.
*/
@Bean
public EmbeddedDatabase acrossDataSource() {
return new EmbeddedDatabaseBuilder()
.setType( EmbeddedDatabaseType.HSQL )
.setName( "core" )
.build();
}
@Bean
public EmbeddedDatabase moduleDataSource() {
return new EmbeddedDatabaseBuilder()
.setType( EmbeddedDatabaseType.HSQL )
.setName( "data" )
.build();
}
@Override
public void configure( AcrossContext context ) {
ProvidedBeansMap beans = new ProvidedBeansMap();
beans.put( AcrossContext.DATASOURCE, new PrimarySingletonBean( moduleDataSource() ) );
beans.put( AcrossContext.INSTALLER_DATASOURCE, moduleDataSource() );
context.addApplicationContextConfigurer( new ProvidedBeansConfigurer( beans ),
ConfigurerScope.MODULES_ONLY );
context.addInstallerContextConfigurer( new ProvidedBeansConfigurer( beans ) );
}
}
The installer context has no web support as it is a direct implementation of AcrossApplicationContext but does not implement WebApplicationContext .
|
Installers are registered as bean definitions in the installer ApplicationContext
.
You can use any Spring @Conditional
annotations to suppress installer execution, even if the run conditions are fulfilled.
When registering bean definitions to the installer context, a good practice is to demarcate beans as @Lazy .
In that case they will never get created if the installer conditionals fail.
|
AcrossLiquibaseInstaller
Across core comes with an AcrossLiquibaseInstaller
class.
This is an abstract base class for executing liquibase XML resources.
Simply extending the base class and annotating as installer will execute an XML resource in the same package as the installer class against the installer datasource.
package my.package;
@Installer(description = "Liquibase installer", runCondition = InstallerRunCondition.AlwaysRun)
public class LiquibaseInstaller extends AcrossLiquibaseInstaller
{
// Will execute the resource file 'my/package/LiquibaseInstaller.xml'
// As liquibase has its own locking mechanism, we can safely always run (even though you should avoid this when possible)
}
SchemaConfiguration
An AcrossLiquibaseInstaller
will use a SchemaConfiguration
bean to configure its default schema and pass configuration properties.
A SchemaConfiguration
for the current module will be looked up first, if none is found a default bean will be used if it exists.
The default SchemaConfiguration
is a bean without any @Module
annotations.
The presence of a @Module
annotation marks that SchemaConfiguration
as applying only for that particular module.
If no default or module SchemaConfiguration
is found, the installer will continue without setting a default schema.
Adding SchemaConfiguration beans to a context directly via a ProvidedBeansConfigurer erases the visibility of bean declaration annotations.
|
SchemaConfiguration
beanspackage my.package;
@EnableAcrossContext
@Configuration
public class SchemaConfig
{
/
* AcrossLiquibaseInstaller in the QuotationModule will automatically pick up this SchemaConfiguration.
* Liquibase scripts will run with 'MY_QUOTATIONS' as their default schema.
*/
@Bean
@Module( "QuotationModule" )
public SchemaConfiguration userSchemaConfiguration() {
return new SchemaConfiguration( "MY_QUOTATIONS" );
}
/
* This is the default SchemaConfiguration.
* AcrossLiquibaseInstaller in other module will pick up this SchemaConfiguration.
*/
@Bean
public SchemaConfiguration defaultSchemaConfiguration() {
return new SchemaConfiguration( "MY_SCHEMA" );
}
}
Alternatively the SchemaConfiguration
bean schema can be ignored and a default schema fixed on the AcrossLiquibaseInstaller
.
This is done in the installer implementation through the setDefaultSchema()
method.
AcrossLiquibaseInstaller#setDefaultSchema
package my.package;
/**
* Liquibase script of this installer will run with 'IM_SPECIAL' as default schema.
* Regardless of any defined SchemaConfiguration.
*/
@Installer(description = "Liquibase installer", runCondition = InstallerRunCondition.AlwaysRun)
public class SpecialSchemaInstaller extends AcrossLiquibaseInstaller
{
public SpecialSchemaInstaller() {
setDefaultSchema( "IM_SPECIAL" );
}
}
InstallerSettings
For advanced configuration, both AcrossContext
and AcrossModule
allow InstallerSettings
to be set.
InstallerSettings
can be used to set the action (eg. force executed, skip) to be performed for one or more installers or installer groups.
InstallerSettings
accepts an InstallerActionResolver
for determining install action at runtime.
Alternatively an installer can implement InstallerActionResolver
that will be used in a second stage only if the original action is EXECUTE
.
Please refer to the javadoc for more information.