Spring Framework 141 280 1 70
Spring Framework 141 280 1 70
Kotlin
As with @Autowired, @Inject can also be used with java.util.Optional or @Nullable. This
is even more applicable here, since @Inject does not have a required attribute. The
following pair of examples show how to use @Inject and @Nullable:
@Inject
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
// ...
}
}
1
Java
public class SimpleMovieLister {
@Inject
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
// ...
}
}
Kotlin
class SimpleMovieLister {
@Inject
var movieFinder: MovieFinder? = null
}
Java
import jakarta.inject.Inject; import jakarta.inject.Named;
// ...
}
2
import jakarta.inject.Inject; import jakarta.inject.Named;
import jakarta.inject.Inject import jakarta.inject.Named
@Named
public class SimpleMovieLister @ManagedBean("movieListener")
@Named("movieListener")// { could be used as well class SimpleM
private MovieFinder movieFinder; @Inject
public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder;
@Inject
} lateinit var movieFinder: MovieFinder
// ...
// ...
} }
Kotlin
It is very common to use @Component without specifying a name for the component.
@Named can be used in a similar fashion, as the following example shows:
Jav
Kotlin
import jakarta.inject.Inject import jakarta.inject.Named
@Named
class SimpleMovieLister {
@Inject
lateinit var movieFinder: MovieFinder
// ...
}
3
When you use @Named or @ManagedBean, you can use component scanning in the
exact same way as when you use Spring annotations, as the following example shows:
Java
@Configuration
@ComponentScan(basePackages = "org.example") public class AppConfig{
// ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"]) class AppConfig{
// ...
}
When you work with standard annotations, you should know that some significant
features are not available, as the following table shows:
4
Spring jakarta.inject.* jakarta.inject
restrictions / comments
@Lazy - no equivalent
6
• Bean Definition Profiles
• PropertySource Abstraction
• Using @PropertySource
The @Bean annotation is used to indicate that a method instantiates, configures, and
initializes a new object to be managed by the Spring IoC container. For those familiar
with Spring’s <beans/> XML configuration, the @Bean annotation plays the same role
as the <bean/> element. You can use @Bean
-annotated methods with any Spring @Component. However, they are most
often used with
@Configuration beans.
Annotating a class with @Configuration indicates that its primary purpose is as a source
of bean definitions. Furthermore, @Configuration classes let inter-bean dependencies be
defined by calling other @Bean methods in the same class. The simplest possible
@Configuration class reads as follows:
Java
@Configuration
public class AppConfig {
@Bean
public MyService myService() { return new MyServiceImpl();
}
}
Kotlin
@Configuration class AppConfig {
@Bean
fun myService(): MyService { return MyServiceImpl()
}
}
The preceding AppConfig class is equivalent to the following Spring <beans/> XML:
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
7
Full @Configuration vs “lite”
@Bean mode?
When @Bean methods are declared within classes that are not annotated with
@Configuration, they are referred to as being processed in a “lite” mode. Bean
methods declared in a @Component or even in a plain old class are considered to
be “lite”, with a different primary purpose of the containing class and a @Bean
method being a sort of bonus there. For example, service components may
expose management views to the container through an additional @Bean
method on each applicable component class. In such scenarios, @Bean methods
are a general-purpose factory method mechanism.
The @Bean and @Configuration annotations are discussed in depth in the following
sections. First, however, we cover the various ways of creating a spring container by
using Java-based configuration.
When @Configuration classes are provided as input, the @Configuration class itself is
registered as a bean definition and all declared @Bean methods within the class are
also registered as bean definitions.
When @Component and JSR-330 classes are provided, they are registered as bean
definitions, and it is assumed that DI metadata such as @Autowired or @Inject are used
within those classes where necessary.
Simple Construction
In much the same way that Spring XML files are used as input when instantiating a
ClassPathXmlApplicationContext, you can use @Configuration classes as input when
8
instantiating an AnnotationConfigApplicationContext. This allows for completely XML-
free usage of the Spring
9
container, as the following example shows:
Java
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService my
myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java) val myService = ctx.getBean<
myService.doStuff()
}
Java
public static void main(String[] args) { ApplicationContext ctx = new
AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class)
MyService myService = ctx.getBean(MyService.class); myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java,
val myService = ctx.getBean<MyService>() myService.doStuff()
}
10
Building the Container Programmatically by Using register(Class<?>…)
Java
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(
ctx.refresh();
MyService myService = ctx.getBean(MyService.class); myService.doStuff();
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext() ctx.register(AppConfig::class.java, OtherConfig::cla
ctx.refresh()
val myService = ctx.getBean<MyService>() myService.doStuff()
}
To enable component scanning, you can annotate your @Configuration class as follows:
Java
@Configuration
@ComponentScan(basePackages = "com.acme") ①
public class AppConfig{
// ...
}
11
Kotlin
@Configuration
@ComponentScan(basePackages = ["com.acme"]) ①
class AppConfig{
// ...
}
<beans>
<context:component-scan base-package="com.acme"/>
</beans>
In the preceding example, the com.acme package is scanned to look for any
@Component-annotated classes, and those classes are registered as Spring bean
definitions within the container. AnnotationConfigApplicationContext exposes the
scan(String…) method to allow for the same component-scanning functionality, as the
following example shows:
Java
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("co
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}
Kotlin
fun main() {
val ctx = AnnotationConfigApplicationContext() ctx.scan("com.acme")
ctx.refresh()
val myService = ctx.getBean<MyService>()
}
12
Support for Web Applications with AnnotationConfigWebApplicationContext
<web-app>
<!-- Configure ContextLoaderListener to use
AnnotationConfigWebApplicationContext instead of the default
XmlWebApplicationContext -->
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<!-- Again, config locations must consist of one or more comma- or
space- delimited
and fully-qualified @Configuration classes -->
13
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.acme.web.MvcConfig</param-value>
</init-param>
</servlet>
<!-- map all requests for /app/* to the dispatcher servlet -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
For programmatic use cases, a GenericWebApplicationContext can be used as an alternativetoAnnota
</web-app>
@Bean is a method-level annotation and a direct analog of the XML <bean/> element.
The annotation supports some of the attributes offered by <bean/>, such as:
• init-method
• destroy-method
• autowiring
• name.
Declaring a Bean
To declare a bean, you can annotate a method with the @Bean annotation. You use this
method to register a bean definition within an ApplicationContext of the type specified as
the method’s return value. By default, the bean name is the same as the method
name. The following example shows a @Bean method declaration:
Java
@Configuration
public class AppConfig {
@Bean
public TransferServiceImpl transferService() { return new TransferServiceImpl();
}
}
14
Kotlin
@Configuration class AppConfig {
@Bean
fun transferService() = TransferServiceImpl()
}
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
You can also use default methods to define beans. This allows composition of bean
configurations by implementing interfaces with bean definitions on default methods.
Java
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() { return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
You can also declare your @Bean method with an interface (or base class) return
type, as the following example shows:
15
Java
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() { return new TransferServiceImpl();
}
}
Kotlin
@Configuration class AppConfig {
@Bean
fun transferService(): TransferService { return TransferServiceImpl()
}
}
However, this limits the visibility for advance type prediction to the specified interface
type (TransferService). Then, with the full type (TransferServiceImpl) known to the
container only once the affected singleton bean has been instantiated. Non-lazy
singleton beans get instantiated according to their declaration order, so you may see
different type matching results depending on when another component tries to match
by a non-declared type (such as @Autowired TransferServiceImpl, which resolves only
once the transferService bean has been instantiated).
Bean Dependencies
16
Java
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(AccountRepository accountRepository) { return new Transfe
}
}
Kotlin
@Configuration class AppConfig {
@Bean
fun transferService(accountRepository: AccountRepository): TransferService { return TransferServi
}
}
Any classes defined with the @Bean annotation support the regular lifecycle
callbacks and can use the @PostConstruct and @PreDestroy annotations from JSR-250. See
JSR-250 annotations for further details.
The regular Spring lifecycle callbacks are fully supported as well. If a bean
implements
InitializingBean, DisposableBean, or Lifecycle, their respective methods are called by the
container.
17
Java
public class BeanOne {
@Configuration
public class AppConfig {
18
Kotlin
class BeanOne {
fun init() {
// initialization logic
}
}
class BeanTwo {
fun cleanup() {
// destruction logic
}
}
19
By default, beans defined with Java configuration that have a public close
or shutdown method are automatically enlisted with a destruction
callback. If you have a public close or shutdown method and you do
not wish for it to be called when the container shuts down, you can add
@Bean(destroyMethod="") to your bean definition to disable the default
(inferred) mode.
You may want to do that by default for a resource that you acquire with
JNDI, as its lifecycle is managed outside the application. In particular,
make sure to always do it for a DataSource, as it is known to be
problematic on Jakarta EE application servers.
Java
@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException { return (DataSource) jndiTe
}
Kotlin
@Bean(destroyMethod = "")
fun dataSource(): DataSource {
return jndiTemplate.lookup("MyDS") as DataSource
}
Also, with @Bean methods, you typically use programmatic JNDI lookups,
either by using Spring’s JndiTemplate or JndiLocatorDelegate helpers or
straight JNDI InitialContext usage but not the JndiObjectFactoryBean variant
(which would force you to declare the return type as the FactoryBean type
instead of the actual target type, making it harder to use for cross-
reference calls in other @Bean methods that intend to refer to the
provided resource here).
In the case of BeanOne from the example above the preceding note, it would be
equally valid to call the init() method directly during construction, as the following
example shows:
20
Java
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
BeanOne beanOne = new BeanOne(); beanOne.init();
return beanOne;
}
// ...
}
Kotlin
@Configuration class AppConfig {
@Bean
fun beanOne() = BeanOne().apply { init()
}
// ...
}
When you work directly in Java, you can do anything you like with your
objects and do not always need to rely on the container lifecycle.
Spring includes the @Scope annotation so that you can specify the scope of a bean.
You can specify that your beans defined with the @Bean annotation should have a
specific scope. You can use any of the standard scopes specified in the Bean Scopes
section.
The default scope is singleton, but you can override this with the @Scope
annotation, as the following example shows:
21
Java
@Configuration
public class MyConfiguration {
@Bean @Scope("prototype")
public Encryptor encryptor() {
// ...
}
}
Kotlin
@Configuration
class MyConfiguration {
@Bean @Scope("prototype")
fun encryptor(): Encryptor {
// ...
}
}
If you port the scoped proxy example from the XML reference documentation (see
scoped proxies) to our @Bean using Java, it resembles the following:
22
Java
// an HTTP Session-scoped bean exposed as a proxy @Bean
@SessionScope
public UserPreferences userPreferences() { return new UserPreferences();
}
@Bean
public Service userService() {
UserService service = new SimpleUserService();
// a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences());
}
Kotlin
// an HTTP Session-scoped bean exposed as a proxy @Bean
@SessionScope
fun userPreferences() = UserPreferences()
@Bean
fun userService(): Service {
return SimpleUserService().apply {
// a reference to the proxied userPreferences bean setUserPreferences(userPreferences())
}
}
By default, configuration classes use a @Bean method’s name as the name of the
resulting bean. This functionality can be overridden, however, with the name attribute,
as the following example shows:
Java
@Configuration
public class AppConfig {
23
Kotlin
@Configuration class AppConfig {
@Bean("myThing")
fun thing() = Thing()
}
Bean Aliasing
Java
@Configuration
public class AppConfig {
Kotlin
@Configuration class AppConfig {
Bean Description
To add a description to a @Bean, you can use the @Description annotation, as the
following example shows:
24
Java
@Configuration
public class AppConfig {
@Bean
@Description("Provides a basic example of a bean") public Thing thing() {
return new Thing();
}
}
Kotlin
@Configuration class AppConfig {
@Bean
@Description("Provides a basic example of a bean") fun thing() = Thing()
}
Java
@Configuration
public class AppConfig {
@Bean
public BeanOne beanOne() {
return new BeanOne(beanTwo());
}
@Bean
public BeanTwo beanTwo() { return new BeanTwo();
}
}
25
Kotlin
@Configuration class AppConfig {
@Bean
fun beanOne() = BeanOne(beanTwo())
@Bean
fun beanTwo() = BeanTwo()
}
As noted earlier, lookup method injection is an advanced feature that you should use
rarely. It is useful in cases where a singleton-scoped bean has a dependency on a
prototype-scoped bean. Using Java for this type of configuration provides a natural
means for implementing this pattern. The following example shows how to use lookup
method injection:
Java
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface Command command = createComma
// set the state on the (hopefully brand new) Command instance command.setState(commandState
return command.execute();
}
// okay... but where is the implementation of this method? protected abstract Command createCom
}
26
Kotlin
abstract class CommandManager {
fun process(commandState: Any): Any {
// grab a new instance of the appropriate Command interface val command = createCommand()
// set the state on the (hopefully brand new) Command instance command.setState(commandState
return command.execute()
}
// okay... but where is the implementation of this method? protected abstract fun createCommand(
}
Java
@Bean @Scope("prototype")
public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand();
// inject dependencies here as required return command;
}
@Bean
public CommandManager commandManager() {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object return new CommandManager() {
protected Command createCommand() { return asyncCommand();
}
}
}
27
Kotlin
@Bean @Scope("prototype")
fun asyncCommand(): AsyncCommand { val command = AsyncCommand()
// inject dependencies here as required return command
}
@Bean
fun commandManager(): CommandManager {
// return new anonymous implementation of CommandManager with createCommand()
// overridden to return a new prototype Command object return object : CommandManager() {
override fun createCommand(): Command { return asyncCommand()
}
}
}
Consider the following example, which shows a @Bean annotated method being called
twice:
Java
@Configuration
public class AppConfig {
@Bean
public ClientService clientService1() {
ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientService clientService2() {
ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao());
return clientService;
}
@Bean
public ClientDao clientDao() { return new ClientDaoImpl();
}
}
28
Kotlin
@Configuration class AppConfig {
@Bean
fun clientService1(): ClientService { return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientService2(): ClientService { return ClientServiceImpl().apply {
clientDao = clientDao()
}
}
@Bean
fun clientDao(): ClientDao { return ClientDaoImpl()
}
}
clientDao() has been called once in clientService1() and once in clientService2(). Since
this method creates a new instance of ClientDaoImpl and returns it, you would normally
expect to have two instances (one for each service). That definitely would be
problematic: In Spring, instantiated beans have a singleton scope by default. This is
where the magic comes in: All @Configuration classes are subclassed at startup-time
with CGLIB. In the subclass, the child method checks the container first for any cached
(scoped) beans before it calls the parent method and creates a new instance.
29
There are a few restrictions due to the fact that CGLIB dynamically
adds features at startup-time. In particular, configuration classes
must not be final. However, as of 4.3, any constructors are allowed on
configuration classes, including the use of @Autowired or a single non-
default constructor declaration for default injection.
declaring your
@Bean methods on non-@Configuration classes (for example, on plain
@Component classes instead). Cross-method calls between @Bean
methods are not then intercepted, so you have to exclusively rely on
dependency injection at the constructor or method level there.
Spring’s Java-based configuration feature lets you compose annotations, which can
reduce the complexity of your configuration.
Much as the <import/> element is used within Spring XML files to aid in
modularizing configurations, the @Import annotation allows for loading @Bean
definitions from another configuration class, as the following example shows:
Java
@Configuration
public class ConfigA {
@Bean
public A a() { return new A();
}
}
@Bean
public B b() { return new B();
}
}
30
Kotlin
@Configuration class ConfigA {
@Bean
fun a() = A()
}
@Bean
fun b() = B()
}
Now, rather than needing to specify both ConfigA.class and ConfigB.class when
instantiating the context, only ConfigB needs to be supplied explicitly, as the following
example shows:
Java
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(ConfigB::class.java)
This approach simplifies container instantiation, as only one class needs to be dealt
with, rather than requiring you to remember a potentially large number of
@Configuration classes during construction.
31
As of Spring Framework 4.2, @Import also supports references to
regular component classes, analogous to the
AnnotationConfigApplicationContext.register method. This is particularly
useful if you want to avoid component scanning, by using a few
configuration classes as entry points to explicitly define all your
components.
The preceding example works but is simplistic. In most practical scenarios, beans
have dependencies on one another across configuration classes. When using XML,
this is not an issue, because no compiler is involved, and you can declare
ref="someBean" and trust Spring to work it out during container initialization. When
using @Configuration classes, the Java compiler places constraints on the
configuration model, in that references to other beans must be valid Java syntax.
32
Java
@Configuration
public class ServiceConfig {
@Bean
public TransferService transferService(AccountRepository
accountRepository) { return new
TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
@Bean
public AccountRepository accountRepository(DataSource
dataSource) { return new
JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class,
RepositoryConfig.class}) public class
SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
33
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Bean
fun transferService(accountRepository: AccountRepository):
TransferService { return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig {
@Bean
fun accountRepository(dataSource: DataSource):
AccountRepository { return
JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class,
RepositoryConfig::class) class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService =
ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
There is another way to achieve the same result. Remember that @Configuration classes
are ultimately only another bean in the container: This means that they can take
advantage of @Autowired and @Value injection and other features the same as any
other bean.
34
Make sure that the dependencies you inject that way are of the
simplest kind only. @Configuration classes are processed quite early
during the initialization of the context, and forcing a dependency to be
injected this way may lead to unexpected early initialization.
Whenever possible, resort to parameter-based injection, as in the
preceding example.
The following example shows how one bean can be autowired to another bean:
35
Java
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
@Configuration
public class RepositoryConfig {
public RepositoryConfig(DataSource
dataSource) { this.dataSource =
dataSource;
}
@Bean
public AccountRepository accountRepository() {
return new
JdbcAccountRepository(dataSource);
}
}
@Configuration
@Import({ServiceConfig.class,
RepositoryConfig.class}) public class
SystemTestConfig {
@Bean
public DataSource dataSource() {
// return new DataSource
}
}
36
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
lateinit var accountRepository: AccountRepository
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(accountRepository)
}
}
@Configuration
class RepositoryConfig(private val dataSource: DataSource) {
@Bean
fun accountRepository():
AccountRepository { return
JdbcAccountRepository(dataSource)
}
}
@Configuration
@Import(ServiceConfig::class,
RepositoryConfig::class) class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return new DataSource
}
}
fun main() {
val ctx = AnnotationConfigApplicationContext(SystemTestConfig::class.java)
// everything wires up across configuration classes...
val transferService =
ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
37
ambiguous. For example, as a developer looking at ServiceConfig, how do you know
exactly where the @Autowired AccountRepository bean is declared? It is not explicit in
the code, and this may be just fine. Remember that the Spring Tools for Eclipse
provides tooling that can render graphs showing how everything is wired, which may
be all you need. Also, your Java IDE can easily find all declarations and uses of the
AccountRepository type and quickly show you the location of @Bean methods that return
that type.
In cases where this ambiguity is not acceptable and you wish to have direct
navigation from within your IDE from one @Configuration class to another, consider
autowiring the configuration classes themselves. The following example shows how to
do so:
Java
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
// navigate 'through' the config class to the @Bean method!
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
Kotlin
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
// navigate 'through' the config class to the @Bean method! return TransferServiceImpl(repositoryC
}
}
38
Java
@Configuration
public class ServiceConfig {
@Autowired
private RepositoryConfig repositoryConfig;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(repositoryConfig.accountRepository());
}
}
@Configuration
public interface RepositoryConfig {
@Bean
AccountRepository accountRepository();
}
@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {
@Bean
public AccountRepository
accountRepository() { return new
JdbcAccountRepository(...);
}
}
@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the
concrete config!
public class SystemTestConfig {
@Bean
public DataSource dataSource() {
// return DataSource
}
39
Kotlin
import org.springframework.beans.factory.getBean
@Configuration
class ServiceConfig {
@Autowired
private lateinit var repositoryConfig: RepositoryConfig
@Bean
fun transferService(): TransferService {
return TransferServiceImpl(repositoryConfig.accountRepository())
}
}
@Configuration
interface RepositoryConfig {
@Bean
fun accountRepository(): AccountRepository
}
@Configuration
class DefaultRepositoryConfig : RepositoryConfig {
@Bean
fun accountRepository():
AccountRepository { return
JdbcAccountRepository(...)
}
}
@Configuration
@Import(ServiceConfig::class, DefaultRepositoryConfig::class) // import the concrete
config!
class SystemTestConfig {
@Bean
fun dataSource(): DataSource {
// return DataSource
}
fun main() {
val ctx =
AnnotationConfigApplicationContext(SystemTestConfig::class.java) val
transferService = ctx.getBean<TransferService>()
transferService.transfer(100.00, "A123", "C456")
}
40
Now ServiceConfig is loosely coupled with respect to the concrete
DefaultRepositoryConfig, and built-in IDE tooling is still useful: You can easily get a type
hierarchy of RepositoryConfig implementations. In this way, navigating @Configuration
classes and their dependencies becomes no different than the usual process of
navigating interface-based code.
Java
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Read the @Profile annotation attributes MultiValueMap<String, Object> attrs =
metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true;
}
}
return false;
}
return true;
}
41
Kotlin
override fun matches(context: ConditionContext, metadata: AnnotatedTypeMetadata): Boolean {
// Read the @Profile annotation attributes
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name) if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
return false
}
return true
}
Spring’s @Configuration class support does not aim to be a 100% complete replacement
for Spring XML. Some facilities, such as Spring XML namespaces, remain an ideal way
to configure the container. In cases where XML is convenient or necessary, you have a
choice: either instantiate the container in an “XML-centric” way by using, for example,
ClassPathXmlApplicationContext, or instantiate it in a “Java-centric” way by using
AnnotationConfigApplicationContext and the @ImportResource annotation to import XML
as needed.
It may be preferable to bootstrap the Spring container from XML and include
@Configuration classes in an ad-hoc fashion. For example, in a large existing
codebase that uses Spring XML, it is easier to create @Configuration classes on an as-
needed basis and include them from the existing XML files. Later in this section, we
cover the options for using @Configuration classes in this kind of “XML- centric”
situation.
42
Java
@Configuration
public class AppConfig {
@Autowired
private DataSource dataSource;
@Bean
public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource);
}
@Bean
public TransferService transferService() {
return new TransferService(accountRepository());
}
}
Kotlin
@Configuration class AppConfig {
@Autowired
private lateinit var dataSource: DataSource
@Bean
fun accountRepository(): AccountRepository { return JdbcAccountRepository(dataSource)
}
@Bean
fun transferService() = TransferService(accountRepository())
}
43
<beans>
<!-- enable processing of annotations such as @Autowired and @Configuration -->
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa
jdbc.password=
Java
public static void main(String[] args) { ApplicationContext ctx = new
ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService t
// ...
}
Kotlin
fun main() {
val ctx = ClassPathXmlApplicationContext("classpath:/com/acme/system-test- config.xml")
val transferService = ctx.getBean<TransferService>()
// ...
}
44
automatically candidates for component scanning. Using the same scenario as described
in the previous example, we can redefine system-test-config.xml to take advantage of
component- scanning. Note that, in this case, we need not explicitly declare
<context:annotation-config/>, because <context:component-scan/> enables the same
functionality.
<beans>
<!-- picks up and registers AppConfig as a bean definition -->
<context:component-scan base-package="com.acme"/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
Java
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppCo
@Bean
public DataSource dataSource() {
return new DriverManagerDataSource(url, username, password);
}
}
45
Kotlin
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") class AppConfig {
@Value("\${jdbc.url}")
private lateinit var url: String
@Value("\${jdbc.username}")
private lateinit var username: String
@Value("\${jdbc.password}")
private lateinit var password: String
@Bean
fun dataSource(): DataSource {
return DriverManagerDataSource(url, username, password)
}
}
properties-config.xml
<beans>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
Java
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferServic
// ...
}
46
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
val ctx = AnnotationConfigApplicationContext(AppConfig::class.java) val transferService = ctx.getB
// ...
}
Properties play an important role in almost all applications and may originate from a
variety of sources: properties files, JVM system properties, system environment
variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and
so on. The role of the Environment object with relation to properties is to provide the
user with a convenient service interface for configuring property sources and
resolving properties from them.
Bean definition profiles provide a mechanism in the core container that allows for
registration of different beans in different environments. The word, “environment,”
can mean different things to different users, and this feature can help with many
use cases, including:
Consider the first use case in a practical application that requires a DataSource. In a
test environment, the configuration might resemble the following:
47
Java
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build();
}
Kotlin
@Bean
fun dataSource(): DataSource { return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("my-schema.sql")
.addScript("my-test-data.sql")
.build()
}
Java
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception { Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
Kotlin
@Bean(destroyMethod = "")
fun dataSource(): DataSource { val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
The problem is how to switch between using these two variations based on the
current environment. Over time, Spring users have devised a number of ways to get
this done, usually relying on a combination of system environment variables and
XML <import/> statements containing ${placeholder} tokens that resolve to the
correct configuration file path depending on the value of an environment variable.
Bean definition profiles is a core container feature that provides a solution to this
problem.
48
If we generalize the use case shown in the preceding example of environment-
specific bean definitions, we end up with the need to register certain bean
definitions in certain contexts but not in others. You could say that you want to
register a certain profile of bean definitions in situation A and a different profile in
situation B. We start by updating our configuration to reflect this need.
Using @Profile
The @Profile annotation lets you indicate that a component is eligible for registration
when one or more specified profiles are active. Using our preceding example, we can
rewrite the dataSource configuration as follows:
Java
@Configuration @Profile("development")
public class StandaloneDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
}
Kotlin
@Configuration @Profile("development") class StandaloneDataConfig {
@Bean
fun dataSource(): DataSource { return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
}
49
Java
@Configuration @Profile("production")
public class JndiDataConfig {
@Bean(destroyMethod="")
public DataSource dataSource() throws Exception { Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
Kotlin
@Configuration @Profile("production") class JndiDataConfig {
@Bean(destroyMethod = "")
fun dataSource(): DataSource { val ctx = InitialContext()
return ctx.lookup("java:comp/env/jdbc/datasource") as DataSource
}
}
The profile string may contain a simple profile name (for example, production) or a
profile expression. A profile expression allows for more complicated profile logic to
be expressed (for example, production & us-east). The following operators are
supported in profile expressions:
You cannot mix the & and | operators without using parentheses. For
example, production & us-east | eu-central is not a valid expression. It must
be expressed as production & (us-east | eu-central).
You can use @Profile as a meta-annotation for the purpose of creating a custom
composed annotation. The following example defines a custom @Production annotation
that you can use as a drop-in replacement for @Profile("production"):
50
Java
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production")
public @interface Production {
}
Kotlin
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @Profile("production
annotation class Production
@Profile can also be declared at the method level to include only one particular
bean of a configuration class (for example, for alternative variants of a particular
bean), as the following example shows:
51
Java
@Configuration
public class AppConfig {
@Bean("dataSource") @Profile("development") ①
public DataSource standaloneDataSource() { return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource") @Profile("production") Ⓒ
public DataSource jndiDataSource() throws Exception { Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
Kotlin
@Configuration class AppConfig {
@Bean("dataSource") @Profile("development") ①
fun standaloneDataSource(): DataSource { return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build()
}
52
With @Profile on @Bean methods, a special scenario may apply: In the
case of overloaded @Bean methods of the same Java method name
(analogous to constructor overloading), a @Profile condition needs to be
consistently declared on all overloaded methods. If the conditions are
inconsistent, only the condition on the first declaration among the
overloaded methods matters. Therefore, @Profile can not be used to
select an overloaded method with a particular argument signature
over another. Resolution between all factory methods for the same
bean
The XML counterpart is the profile attribute of the <beans> element. Our
preceding sample configuration can be rewritten in two XML files, as follows:
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
It is also possible to avoid that split and nest <beans/> elements within the
same file, as the following example shows:
53
<beans
xmlns="http://www.springframework.org/schema/bea
ns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
xmlns:jdbc="http://www.springframework.org/schema
/jdbc"
xmlns:jee="http://www.springframework.org/schema/j
ee" xsi:schemaLocation="...">
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource"
jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
The spring-bean.xsd has been constrained to allow such elements only as the last ones
in the file. This should help provide flexibility without incurring clutter in the XML files.
<beans xsi:schemaLocation="...">
<beans profile="production">
<beans profile="us-east">
<jee:jndi-lookup id="dataSource" jndi- name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
</beans>
54
Activating a Profile
Now that we have updated our configuration, we still need to instruct Spring which
profile is active. If we started our sample application right now, we would see a
NoSuchBeanDefinitionException thrown, because the container could not find the
Spring bean named dataSource.
Activating a profile can be done in several ways, but the most straightforward is to
do it programmatically against the Environment API which is available through an
ApplicationContext. The following example shows how to do so:
Java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnviro
Kotlin
val ctx = AnnotationConfigApplicationContext().apply { environment.setActiveProfiles("developmen
JndiDataConfig::class.java) refresh()
}
Note that profiles are not an “either-or” proposition. You can activate multiple profiles
at once. Programmatically, you can provide multiple profile names to the
setActiveProfiles() method, which accepts String… varargs. The following example
activates multiple profiles:
Java
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
Kotlin
ctx.getEnvironment().setActiveProfiles("profile1", "profile2")
55
-Dspring.profiles.active="profile1,profile2"
Default Profile
The default profile represents the profile that is enabled by default. Consider the
following example:
Java
@Configuration @Profile("default")
public class DefaultDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build();
}
}
Kotlin
@Configuration @Profile("default")
class DefaultDataConfig {
@Bean
fun dataSource(): DataSource { return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.build()
}
}
If no profile is active, the dataSource is created. You can see this as a way to
provide a default definition for one or more beans. If any profile is enabled, the
default profile does not apply.
You can change the name of the default profile by using setDefaultProfiles() on the
Environment or, declaratively, by using the spring.profiles.default property.
PropertySource Abstraction
56
Java
ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment
boolean containsMyProperty = env.containsProperty("my-property"); System.out.println("Does my
Kotlin
val ctx = GenericApplicationContext() val env = ctx.environment
val containsMyProperty = env.containsProperty("my-property")
println("Does my environment contain the 'my-property' property? $containsMyProperty")
In the preceding snippet, we see a high-level way of asking Spring whether the my-
property property is defined for the current environment. To answer this question, the
Environment object performs a search over a set of PropertySource objects. A
PropertySource is a simple abstraction over any source of key-value pairs, and
Spring’s StandardEnvironment is configured with two PropertySource objects — one
representing the set of JVM system properties (System.getProperties()) and one
representing the set of system environment variables (System.getenv()).
57
The search performed is hierarchical. By default, system properties
have precedence over environment variables. So, if the my-property
property happens to be set in both places during a call to
env.getProperty("my-property"), the system property value “wins” and is
returned. Note that property values are not merged but rather
completely overridden by a preceding entry.
Most importantly, the entire mechanism is configurable. Perhaps you have a custom
source of properties that you want to integrate into this search. To do so, implement
and instantiate your own PropertySource and add it to the set of PropertySources for
the current Environment. The following example shows how to do so:
Java
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources so
Kotlin
val ctx = GenericApplicationContext()
val sources = ctx.environment.propertySources sources.addFirst(MyPropertySource())
In the preceding code, MyPropertySource has been added with highest precedence in the
search. If it contains a my-property property, the property is detected and returned, in
favor of any my-property property in any other PropertySource. The
MutablePropertySources API exposes a number of methods that allow for precise
manipulation of the set of property sources.
Using @PropertySource
58
Java
@Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig {
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); retur
}
}
Kotlin
@Configuration @PropertySource("classpath:/com/myco/app.properties") class AppConfig {
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
59
Java
@Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties"
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); retur
}
}
Kotlin
@Configuration @PropertySource("classpath:/com/\${my.placeholder:default/path}/app.properties
@Autowired
private lateinit var env: Environment
@Bean
fun testBean() = TestBean().apply {
name = env.getProperty("testbean.name")!!
}
}
60
resolution process in any way you like. You can change the
61
precedence of searching through system properties and environment variables or
remove them entirely. You can also add your own property sources to the mix, as
appropriate.
Concretely, the following statement works regardless of where the customer property
is defined, as long as it is available in the Environment:
<beans>
<import resource="com/bank/service/${customer}-config.xml"/>
</beans>
To enable load-time weaving, you can add the @EnableLoadTimeWeaving to one of your
@Configuration
classes, as the following example shows:
Java
@Configuration @EnableLoadTimeWeaving public class AppConfig {
}
Kotlin
@Configuration @EnableLoadTimeWeaving class AppConfig
Alternatively, for XML configuration, you can use the context:load-time-weaver element:
<beans>
<context:load-time-weaver/>
</beans>
Once configured for the ApplicationContext, any bean within that ApplicationContext
may implement LoadTimeWeaverAware, thereby receiving a reference to the load-time
weaver instance. This is particularly useful in combination with Spring’s JPA support
where load-time weaving may be necessary for JPA class transformation. Consult the
LocalContainerEntityManagerFactoryBean javadoc for more detail. For more on AspectJ
load-time weaving, see Load-time Weaving with AspectJ in the Spring Framework.
62
2.1.15. Additional Capabilities of the ApplicationContext
As discussed in the chapter introduction, the org.springframework.beans.factory package
provides basic functionality for managing and manipulating beans, including in a
programmatic way. The org.springframework.context package adds the
ApplicationContext interface, which extends the BeanFactory interface, in addition to
extending other interfaces to provide additional functionality in a more application
framework-oriented style. Many people use the ApplicationContext in a completely
declarative fashion, not even creating it programmatically, but instead relying on
support classes such as ContextLoader to automatically instantiate an ApplicationContext
as part of the normal startup process of a Jakarta EE web application.
• String getMessage(String code, Object[] args, String default, Locale loc): The basic method
used to retrieve a message from the MessageSource. When no message is found for
the specified locale, the default message is used. Any arguments passed in become
replacement values, using the MessageFormat functionality provided by the standard
library.
• String getMessage(String code, Object[] args, Locale loc): Essentially the same as the
previous method but with one difference: No default message can be specified. If the
message cannot be found, a NoSuchMessageException is thrown.
• String getMessage(MessageSourceResolvable resolvable, Locale locale): All properties
used in the preceding methods are also wrapped in a class named
MessageSourceResolvable, which you can use with this method.
63
Spring provides three MessageSource implementations, ResourceBundleMessageSource,
ReloadableResourceBundleMessageSource and StaticMessageSource. All of them
implement HierarchicalMessageSource in order to do nested messaging. The
StaticMessageSource is rarely used but provides programmatic ways to add messages to
the source. The following example shows ResourceBundleMessageSource:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
The example assumes that you have three resource bundles called format, exceptions
and windows defined in your classpath. Any request to resolve a message is handled
in the JDK-standard way of resolving messages through ResourceBundle objects. For
the purposes of the example, assume the contents of two of the above resource bundle
files are as follows:
The next example shows a program to run the MessageSource functionality. Remember
that all ApplicationContext implementations are also MessageSource implementations
and so can be cast to the MessageSource interface.
Java
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH); System.out.p
}
64
Kotlin
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml")
val message = resources.getMessage("message", null, "Default", Locale.ENGLISH) println(message
}
Alligators rock!
The next example shows arguments passed to the message lookup. These
arguments are converted into String objects and inserted into placeholders in the
lookup message.
<beans>
<!-- lets inject the above MessageSource into this POJO -->
<bean id="example" class="com.something.Example">
<property name="messages" ref="messageSource"/>
</bean>
</beans>
65
Java
public class Example {
Kotlin
class Example {
lateinit var messages: MessageSource fun execute() {
val message = messages.getMessage("argument.required", arrayOf("userDao"), "Required", Locale
println(message)
}
}
The resulting output from the invocation of the execute() method is as follows:
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
66
Java
public static void main(final String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message =
new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message);
}
Kotlin
fun main() {
val resources = ClassPathXmlApplicationContext("beans.xml") val message = resources.getMessag
arrayOf("userDao"), "Required", Locale.UK) println(message)
}
The resulting output from the running of the above program is as follows:
You can also use the MessageSourceAware interface to acquire a reference to any
MessageSource that has been defined. Any bean that is defined in an
ApplicationContext that implements the MessageSourceAware interface is injected with
the application context’s MessageSource when the bean is created and configured.
67
As of Spring 4.2, the event infrastructure has been significantly
improved and offers an annotation-based model as well as the ability
to publish any arbitrary event (that is, an object that does not
necessarily extend from ApplicationEvent). When such an object is
published, we wrap it in an event for you.
The following table describes the standard events that Spring provides:
Event Explanation
ContextRefreshedEvent Published when the ApplicationContext is initialized or
refreshed (for example, by using the refresh() method on the
ConfigurableApplicationContext interface). Here, “initialized”
means that all beans are loaded, post-processor beans are
detected and activated, singletons are pre-instantiated, and
the ApplicationContext object is ready for use. As long as the
context has not been closed, a refresh can be triggered
multiple times, provided that the chosen ApplicationContext
actually supports such “hot” refreshes. For example,
XmlWebApplicationContext supports hot refreshes, but
GenericApplicationContext does not.
ContextStartedEvent Published when the ApplicationContext is started by using
the start() method on the ConfigurableApplicationContext
interface. Here, “started” means that all Lifecycle beans
receive an explicit start signal. Typically, this signal is used
to restart beans after an explicit stop, but it may also be
used to start components that have not been configured for
autostart (for example, components that have not already
started on initialization).
ContextStoppedEvent Published when the ApplicationContext is stopped by using
the stop() method on the ConfigurableApplicationContext
interface. Here, “stopped” means that all Lifecycle beans
receive an explicit stop signal. A stopped context may be
restarted through a start() call.
68
You can also create and publish your own custom events. The following example
shows a simple class that extends Spring’s ApplicationEvent base class:
69
Java
public class BlockedListEvent extends ApplicationEvent {
Kotlin
class BlockedListEvent(source: Any,
val address: String,
val content: String) : ApplicationEvent(source)
70
Java
public class EmailService implements ApplicationEventPublisherAware {
Kotlin
class EmailService : ApplicationEventPublisherAware {
71
the parameter passed in is the Spring container itself. You are interacting with the
application context through its ApplicationEventPublisher interface.
To receive the custom ApplicationEvent, you can create a class that implements
ApplicationListener
and register it as a Spring bean. The following example shows such a class:
Java
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> { private String
public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificati
}
Kotlin
class BlockedListNotifier : ApplicationListener<BlockedListEvent> { lateinit var notificationAddress
override fun onApplicationEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
The following example shows the bean definitions used to register and configure
each of the classes above:
72
<bean id="emailService" class="example.EmailService">
<property name="blockedList">
<list>
</list>
</property>
</bean>
Putting it all together, when the sendEmail() method of the emailService bean is called,
if there are any email messages that should be blocked, a custom event of type
BlockedListEvent is published. The blockedListNotifier bean is registered as an
ApplicationListener and receives the BlockedListEvent, at which point it can notify
appropriate parties.
You can register an event listener on any method of a managed bean by using the
@EventListener
annotation. The BlockedListNotifier can be rewritten as follows:
Java
public class BlockedListNotifier { private String notificationAddress;
public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificati
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
73
Kotlin
class BlockedListNotifier {
lateinit var notificationAddress: String @EventListener
fun processBlockedListEvent(event: BlockedListEvent) {
// notify appropriate parties via notificationAddress...
}
}
The method signature once again declares the event type to which it listens, but,
this time, with a flexible name and without implementing a specific listener
interface. The event type can also be narrowed through generics as long as the
actual event type resolves your generic parameter in its implementation hierarchy.
If your method should listen to several events or if you want to define it with no
parameter at all, the event types can also be specified on the annotation itself. The
following example shows how to do so:
Java
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) public void handleCon
// ...
}
Kotlin
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class) fun handleContextStar
// ...
}
It is also possible to add additional runtime filtering by using the condition attribute
of the annotation that defines a SpEL expression, which should match to actually invoke
the method for a particular event.
The following example shows how our notifier can be rewritten to be invoked only if
the content
attribute of the event is equal to my-event:
Java
@EventListener(condition = "#blEvent.content == 'my-event'") public void processBlockedListEven
// notify appropriate parties via notificationAddress...
}
74