This post is below my usual quality standards, it’s just bunch of notes that I made while reading “Spring in Action”. Still I publish it mostly for myself but I hope someone may find it helpful too.
Registering and using beans via Spring
Let’s start with simple StdOutNotificationService
bean:
package beandemo;
public interface NotificationService {
void showNotification(String message);
}
public class StdOutNotificationService implements NotificationService {
@Override
public void showNotification(String message) {
System.out.println("NOTIFICATION: " + message);
}
}
To register it with Spring we must add the following configuration file as a resource to our application:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="notificationService"
class="beandemo.StdOutNotificationService" />
</beans>
NOTE: The id
attribute of bean
element is optional - we can refer to beans
either by their name or their type.
Now we may create ApplicationContext
from our configuration file
and finally use our beans:
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {
"beandemo/bean-config.xml"
// other configuration files
});
NotificationService notificationService =
context.getBean(NotificationService.class);
// or: context.getBean("notificationService")
notificationService.showNotification("Yay! It works!");
}
Injection using constructor
Let’s start with a simple example ConstructorDemo
bean requires two
other beans (NotificationService
and SystemUserNameProvider
) to work:
public class ConstructorDemo {
private final NotificationService notificationService;
private final SystemUserNameProvider systemUserNameProvider;
public ConstructorDemo(
NotificationService notificationService,
SystemUserNameProvider systemUserNameProvider) {
super();
this.notificationService = notificationService;
this.systemUserNameProvider = systemUserNameProvider;
}
// ...
}
In this case we should use Spring constructor-arg
element to
tell Spring how it should resolve constructor arguments:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="...">
<bean id="notificationService" class="beandemo.StdOutNotificationService" />
<bean id="systemUserNameProvider" class="beandemo.JdkSystemUserNameProvider" />
<bean id="constructorDemo" class="beandemo.ConstructorDemo">
<constructor-arg ref="notificationService" />
<constructor-arg ref="systemUserNameProvider" />
</bean>
</beans>
We must provide all constructor arguments
otherwise Spring will throw UnsatisfiedDependencyException
.
The order of constructor-arg
elements is not important because
arguments are matched by type.
In these rare cases when ambiguity arise we may use name
and index
attributes
of constructor-arg
element to specify to which parameter dependency should
be bound, for example:
<bean id="someBean" class="beandemo.SomeBean">
<constructor-arg ref="dependency1" name="parameter1" />
<constructor-arg ref="dependency2" name="parameter2" />
</bean>
When providing constructor arguments we are not limited to
references to other beans, we may also provide standard Java types like
String
. For example given bean:
public class ValueBean {
public ValueBean(String stringValue, Integer intValue, Boolean boolValue) {
super();
this.stringValue = stringValue;
this.intValue = intValue;
this.boolValue = boolValue;
}
}
We may register it as follows (Spring will take care of converting strings to apropriate types):
<bean id="prefixSuffix" class="beandemo.ValueBean">
<constructor-arg value="some string" />
<constructor-arg value="123" />
<constructor-arg value="false" />
</bean>
Sometimes we want to set some constructor arguments to null
,
we may use <null />
element to do exactly that:
<bean class="...">
<constructor-arg>
<null />
</constructor-arg>
<constructor-arg ref="fooService" />
</bean>
Injection using setters
The most popular industry convention is that all required bean dependencies should be injected via constructor and optional dependencies via setters.
Let’s see how it works on example: FooService
bean
requires BarService
and has optional dependency on BazService
.
This means that even without BazService
FooService
will work but
some features will not be accessible.
public class FooService {
private final BarService barService;
private BazService bazService;
public FooService(BarService barService) {
super();
this.barService = barService;
}
public void setBazService(BazService bazService) {
this.bazService = bazService;
}
public void foo() {
barService.bar();
if (bazService != null)
bazService.baz();
}
}
And here is configuration for our example:
<beans xmlns="...">
<bean id="fooService" class="beandemo.FooService">
<constructor-arg ref="barService" />
<!-- here we provide optional dependency -->
<property name="bazService" ref="bazService" />
</bean>
<bean id="barService" class="beandemo.BarService" />
<bean id="bazService" class="beandemo.BazService" />
</beans>
Property injection can also be used to populate JavaBeans, for example given bean:
public class Configuration {
private String outputFile;
private String inputFile;
private Boolean enableLogging;
public String getOutputFile() {
return outputFile;
}
public void setOutputFile(String outputFile) {
this.outputFile = outputFile;
}
// ...
}
<bean id="configuration" class="beandemo.Configuration">
<property name="enableLogging" value="false" />
<property name="inputFile" value="input.txt" />
<property name="outputFile" value="output.txt" />
</bean>
As with constructor-arg
we use ref
attribute to refer to other beans and
value
attribute to provide inline value. We can set property value to null
by writing:
<property name="propertyName">
<null />
</property>
Since writing property
may be tiring (at least in case of JavaBeans) there
is a shortcut, first we must add XML p
namespace to configuration file:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
...
Then we may use p:<propertyName>=value
syntax in configuration:
<bean id="configuration" class="beandemo.Configuration"
p:inputFile="input.txt"
p:outputFile="output.txt"
p:enableLogging="true">
</bean>
There is also short cut for <property name="" ref="" />
, just add -ref
to
the end of property name e.g. p:bazService-ref="baz"
.
Injecting collections
Sometime we want to inject not single beans but collections of beans, for example given bean:
public class MessageFilterService {
private final List<MessageFilter> filters;
public MessageFilterService(List<MessageFilter> filters) {
this.filters = filters;
}
public List<Message> getGoodMessages(List<Message> messages) {
return messages.stream()
.filter(msg -> filters.stream()
.allMatch(filter -> filter.allow(msg)))
.collect(toList());
}
}
public interface MessageFilter {
boolean allow(Message message);
}
To wire list of selected MessageFilter
s into MessageFilterService
we must use list
element:
<bean id="messageSizeMessageFilter"
class="beandemo.MessageSizeMessageFilter" />
<bean id="stopSpanMessageFilter"
class="beandemo.StopSpamMessageFilter" />
<bean id="messageFilterService"
class="beandemo.MessageFilterService">
<constructor-arg>
<list>
<ref bean="messageSizeMessageFilter" />
<ref bean="stopSpanMessageFilter" />
</list>
</constructor-arg>
</bean>
To wire list of values we may write:
<bean id="listOfValues" class="beandemo.ListOfValues">
<constructor-arg>
<list>
<value>foo</value>
<value>bar</value>
<value>nyu</value>
</list>
</constructor-arg>
</bean>
When injecting collections we are not limited to list
,
we may also inject set
, map
and
props
(instance of java.util.Properties
):
<constructor-arg name="set">
<set>
<value>foo</value>
<value>bar</value>
<value>nyu</value>
<ref bean="beanId" />
</set>
</constructor-arg>
<constructor-arg name="map">
<map>
<entry key="foo" value="bar" />
<entry key="baz" value-ref="beanId" />
<entry key-ref="beanId" value="42" />
</map>
</constructor-arg>
<constructor-arg name="props">
<props>
<prop key="foo">foo is great</prop>
<prop key="bar">bar is great too</prop>
</props>
</constructor-arg>
Miscellaneous
Bean lifetime
By default Spring tread are registered beans as singletons, to change
lifetime of bean use scope
attribute:
<bean id="..." class="..." scope="prototype" />
Two most useful Spring scopes in console applications are
singleton
- only single instance of bean will exists perApplicationContext
prototype
- create new bean instance per usage (if we inject bean three times we will get three instances)
Springs also supports request
and session
scopes, they are
mainly used in Spring MVC/Spring Boot applications.
Inline beans
Sometimes we want to create bean just to inject it in one place, instead of writing:
<bean id="barUtil" class="BarUtil" />
<bean id="foo">
<constructor-arg ref="barUtil" />
</bean>
We may write:
<bean id="foo">
<constructor-arg>
<bean class="BarUtil" />
</constructor-arg>
</bean>
Init and Destroy events
Sometimes we want Spring to call initialization method after bean
instance is created, and likewise to call bean cleanup method just
before ApplicationContext
is destroyed.
To specify such methods we may use bean
element
init-method
and destroy-method
attributes:
<bean id="foo" class="beandemo.FooService"
init-method="init"
destroy-method="destroy">
<constructor-arg ref="bar" />
<property name="bazService" ref="baz" />
</bean>
And a bean:
public class FooService {
public void init() { ... }
public void destroy() { ... }
...
}
Creating beans using factory method
Given singleton implementation:
public enum MySingleton {
INSTANCE;
public void doStuff() {
System.out.println("Singletons are good!");
}
// factory method just for Spring:
public static MySingleton getInstance() {
return MySingleton.INSTANCE;
}
}
We may register it in Spring as a bean using configuration:
<bean id="mySingleton"
class="beandemo.MySingleton"
factory-method="getInstance" />
Here we are just telling Spring to create mySingleton
bean
by invoking method getInstance()
on MySingleton
class.