We saw the bare-minimum basics of Spring Framework in the last chapter. We worked with new things such as beans, bean factories, and containers. This chapter explains them in detail. It discusses writing beans, naming conventions, how they are wired into containers, and so on.
For Spring, all objects are beans! The fundamental step in the Spring Framework is to define your objects as beans. Beans are nothing but object instances that would be created and manage by the Spring Framework by looking at their class definitions. These definitions basically form the configuration metadata. The framework then creates a plan for which objects need to be instantiated, which dependencies need to be set and injected, and the scope of the newly created instance, etc., is based on this configuration metadata.
The metadata can be supplied in a simple XML file, as we saw in Chapter 1. Alternatively, one could provide the metadata as annotation or Java Configuration.
We first discover the definitions of the Spring beans by using a config file which is discussed in the next section.
We saw earlier that the Framework reads the Java classes defined in the XML config and initializes and loads them as Spring beans into a runtime container. The container is a runtime bucket of all the fully prepared instances of Java classes. Let’s take a look at an example of how this process is executed.
We will define a bean with a name
that corresponds to a class person
The Person.
Person
has three properties, two of which
( firstName
and lastName
) were set via the
constructor, while the third one ( age
) is set by using a setter. There
is also another property called address
. However, this property is not an
simple Java type, but instead points (references) to another class
Address
.
The following snippet shows the Person
bean
class’s definition:
public
class
Person
{
private
int
age
=
0
;
private
String
firstName
=
null
;
private
String
lastName
=
null
;
private
Address
address
=
null
;
public
Person
(
String
fName
,
String
lName
){
firstName
=
fName
;
lastName
=
lName
;
}
public
int
getAge
()
{
return
age
;
}
public
void
setAge
(
int
age
)
{
this
.
age
=
age
;
}
public
Address
getAddress
()
{
return
address
;
}
public
void
setAddress
(
Address
address
)
{
this
.
address
=
address
;
}
public
String
getDetails
(){
return
firstName
+
" "
+
lastName
+
" is "
+
getAge
()+
" old and lives at "
+
getAddress
();
}
}
As you can see, only age
and address
variables
have setters—meaning they are set in a different way than the variables set via constructor.
For completeness, the Address
class definition is:
public
class
Address
{
private
int
doorNumber
=
0
;
private
String
firstLine
=
null
;
private
String
secondLine
=
null
;
private
String
zipCode
=
null
;
// getters and setters for these variables go here
....
}
The ultimate goal is to create the Person
and
Address
beans via our configuration files. The classes are declared in
a Spring configuration XML file as shown here:
<?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
name=
"person"
class=
"com.madhusudhan.jscore.beans.Person"
>
<constructor-arg
value=
"Madhusudhan"
/>
<constructor-arg
value=
"Konda"
/>
<property
name=
"age"
value=
"99"
/>
<property
name=
"address"
ref=
"address"
/>
</bean>
<bean
name=
"address"
class=
"com.madhusudhan.jscore.beans.Address"
>
<property
name=
"doorNumber"
value=
"99"
/>
<property
name=
"firstLine"
value=
"Rainbow Vistas"
/>
<property
name=
"secondLine"
value=
"Kukatpally, Hyderabad"
/>
<property
name=
"zipCode"
value=
"101010"
/>
</bean>
</beans>
There are few things that we should take a note of here.
The topmost node declares
as your root element. All
bean definitions would then follow using a <beans>
tag. Usually, the
XML file consists of at least one bean. Each bean definition may contain
sets of information, most importantly the name and the class tags. It
may also have other information, such as the id, the scope of the bean,
the dependencies, and others.<bean>
Basically, when the config file is loaded at runtime, the framework will pick up these
definitions and create the instance of Person
. It then gives a name
as person
. This name is then used to query for the same
instance from the container by using a Framework’s API.
For example, the following code snippet illustrates how a
PersonClient
loads up its container and queries the beans:
public
class
PersonClient
{
private
static
ApplicationContext
context
=
null
;
public
PersonClient
()
{
context
=
new
ClassPathXmlApplicationContext
(
"ch2-spring-beans.xml"
);
}
public
String
getPersonDetails
()
{
Person
person
=
(
Person
)
context
.
getBean
(
"person"
)
;
return
person
.
getDetails
();
}
}
From the above snippet, two steps are significant.
The ApplicationContext
is instantiated by an appropriate beans
config file. This context is nothing but our bucket of beans—a container in Spring’s
terminology.
Querying the container for our newly created Person
bean. We use
the framework’s Context API to search the Java instance, using the
getBean()
method. The string value that you pass to this
getBean()
query API method is the name that you’ve given the bean
in your XML file—person
in this case.
You can split the bean definitions across multiple files.
For example, you create all the beans that deliver the business functions in a file called business-beans.xml, the utility beans in util-beans.xml, data access beans in dao-beans.xml, and so on. We will see how to instantiate the Spring container by using multiple files later in the chapter.
Generally, I follow the convention of creating the files by using two parts separated by a hyphen. The first part usually represents the business function, while the second part simply indicates that these are spring beans. There is no restriction on the naming convention, so feel free to name your beans whatever you like.
Each bean should either have a name or id field attached to it. You can create the beans with neither of these things, making them anonymous beans (which are not available to query in your client code). The name and id fields both serve the same purpose, except that the id field corresponds to XML specification’s id notation. This means that checks are imposed on the id (for example, no special characters in the id value). The name field does not attract any of these restrictions.
The class field declares the fully qualified name of the class. If the instantiation of the class requires any initialization data, it is set via properties or a constructor argument.
For example, in the above XML file, the Person
object is instantiated
with both: a constructor argument and property setters. The firstName and lastName were set
using the <constructor-arg value="..."/>
tag, while the rest of the
properties were set using a simple property tag: <property name="age"
value=".."/>
.
The value fields can be simple values or references to other beans. A ref
tag is used if a the bean needs another bean, as is seen for address: <property
name="address" ref="address"/>
. Note the use of ref
keyword
rather than value
keyword when another bean is referenced.
You can name the bean as you wish. However, I would suggest sticking to a camelCase class name, with the first letter lowercase.
One possible way of Spring wiring is using annotations. When you choose to go along the path of annotations to define your beans and wire them, you are effectively reducing your XML meta data configurations.
Let’s see how we can define beans using annotations. The ReservationManager has one dependency—a ReservationService. The sevice property must be injected into our manager to do its flight reservation work.
The dummy service definition is shown here:
public
class
ReservationService
{
public
void
doReserve
(
ReservationMessage
msg
){
//
}
}
The ResevationManager has a dependency—it needs the reservation service so it process
the flight reservations. In order to fulfil this dependency, we need to inject the service
into the manager. Unlike the config route, we will inject this dependency by using an
annotation called @Autowired
annotation. See how we decorated the
variable reservationService, using this annotation shown in the following snippet:
public
class
ReservationManager
{
@Autowired
private
ReservationService
reservationService
=
null
;
public
void
process
(
Reservation
r
)
{
reservationService
.
reserve
(
r
);
}
}
When a variable, method or constructor is annotated with @Autowired, framework will find
the relevant dependency and inject that dependency automatically. In the preceding case, the
ReservationManager
is looking for a
ReservationService
instance. Behind the scenes, the framework will wire
the bean by finding it byType
(check the autowiring section for more
details).
One last thing we need to do is let framework know we are going to use annotations. The way we do this is to declare a special tag in the XML file:
<beans
xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><context:annotation-config/>
<!-- our reservation service -->
<bean
name=
"resSvcABC"
class=
"com.madhusudhan.jscore.fundamentals.annotations.ReservationService"
/>
<bean
name=
"reservationManager"
class=
"com.madhusudhan.jscore.fundamentals.annotations.ReservationManager"
/>
</beans>
The annotation-config
tag lets the framework know we are following
the annotations route. This way, it starts searching for the classes that may have the
appropriate annotations (in our case, we have ReservationManager
with
@Autowired
annotation) and do fulfil any obligations.
Note that we need to import appropriate context
schemas as shown in
bold in the above snippet.
When ReservationManger gets created, the framework looks for a ReservationService type
bean (we have created one in the bean config) and injects it. I have deliberately set the
name of the service bean to resSvsABC
so you would know that framework
uses byType
rather than byName
when picking up the
dependencies.
When the reservationManager
gets created, it
will always be injected with the service bean!
There are couple of things that we should look at.
We did not declare any properties on the ReservationManager
, for
example, <property name="reservationService" ref="resSvs"/>
, as we
would do normally. There are no setters and getters on the
ReservationManager
too. This is all redundant because the job is
cleanly done by the annotation configuration!
Before we close this section, there’s another attribute that we should see—the
component-scan
attribute on the context
namespace.
In the previous snippet, although we have eliminated much of XML, we still had to define the service bean in the XML. Is there a way we could avoid this and condense our XML further?
Yes, we should use the component-scan
attribute that gobbles away
much of our XML. As the tag may indicate, component-scan
basicaly scans a
particular drectory or directories to find out special annotated classes.
Revisiting the ReservationService
, annotate the class by using the
@Component
annotation:
@Component
public class ReservationService {
..
}
Any class annotated with this @Component
annotation will be picked up
by the framework (as long as the class is the part of the
component-scan
’s base-package value) and gets instantiated. The next
thing we need to do is to declare the component-scan
as shown
here:
<context:component-scan
base-package="com.madhusudhan.jscore.fundamentals.annotations"/>
By declaring this tag, what we are telling the framework is to search all the annotated
(with @Component
) classes in the package indicated by
base-package
attribute. Hence ReservationService
will be searched for and instantiated by the framework and gets injected into the
ReservationManager
bean because of @Autowired
annotation.
If you do not wish to tie up to Spring’s @Autowired
annotation usage,
perhaps think about using JSR-330’s @Inject
annotation. Spring supports
this annotation too. Although we can’t discuss @Inject
annotation here,
it is almost replica of @Autowired
annotation.
There’s a lot of debate on using annotations against XML configurations. Although annotations do not clutter our XML files, they are tied to the source. There are various schools of thought, some encouraging while others are discouraging the use of annotations. Personally, I like annotations and their clean and neat approach to solving configuration issues. However, I prefer to use meta-data for the simple reason that I can change the configuration without having to recompile/rebuild the application.
Sometimes the configuration files seems to have lot of bean information. This is an eyesore to readers at times. However, the good news is that they can be condensed by using schema-based XML configuration. We have been using XML Schemas all along in as the following meta data snippet illustrates:
<?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-3.0.xsd"
>
<!-- declare your beans here -->
<bean>
...</bean>
</beans>
As you can see, the beans
schema has been used in the
above configuration. Spring defines various schemas such as jms
, aop
, jee
, tx
, lang
, util
, and others.
Adding the appropriate schemas is quite straightforward. For example, if you are going to use a jms schema, add the following lines (in bold) to the configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd"> ....</beans>
A xmlns tag defines a jms namespace while the last two line indicate the location of the schema definitions. So, replace the xxx shown in the following pattern with a specific schema you wish to import:
<!-- Replace xxx with one of the many schemas such as jms, aop, tx etc --> http://www.springframework.org/schema/xxx http://www.springframework.org/schema/xxx/spring-xxx-3.0.xsd
Once you have the appropriate schema and the associated namespace (using the xmlns
tag), using the schema-based configuration is a
breeze.
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns=
"http://www.springframework.org/schema/beans"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:jms="http://www.springframework.org/schema/jms" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd"><jms:listener-container>
...</jms:listener-contaner>
</beans>
See how the listener-container
bean is added to the container by
using the jms namespace.
The good old DTD-style configuration is still valid too:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
....</beans>
However, I strongly recommend using the schema-based configuration unless you have a valid reason to stick to older DTD style.
The beans are instances wired together to achieve an application’s goal.
Usually in a standard Java application, we follow a specific life cycle of the components, including their dependencies and associations.
For example, when you start a main class, it automatically creates all the dependencies, sets the properties, and instantiates the dependent instances for your application to progress.
The responsibility of creating the dependency and associating this dependency to the appropriate instance is given to your main class in our standalone application.
However, in Spring, this responsibility is taken away from us and given to the Spring Framework. The instances (a.k.a. beans) are created, the associations are established, and dependencies are injected by the Spring Framework entirely. You and I have no say except in defining and loading them.
So, creating the whole object graph is the responsibility of the framework!
These beans that exists in a container are then queried by the application and act upon them. Of course, you would have to declare these associations and other configuration metadata either in an XML file or provide them as annotations for the Framework to understand what it should do.
One important characteristic of the Framework while creating beans is to follow a fail-fast approach.
When the Framework encounters any issues in loading a bean, it just quits—no container with half-baked beans ever gets created. This is a really good characteristic of a framework as it would be forced to catch all errors during compile time rathen than at runtime.
The Spring Framework does quite a few things behind the scenes.
The life cycle of a bean is easy to understand, yet different from the life cycle exposed in a standard Java application. In a normal Java process, a bean is usually instantiated using a new operator. The Framework executes more actions in addition to simply creating the beans. Once they are created, they are loaded into the appropriate container (we will learn about containers in Chapter 3) for a client to access them.
The usual life-cycle steps are listed here:
The framework factory loads a bean definitions and creates it.
The bean is then populated with the properties as declared in the bean definitions. If the property is a reference to another bean, the other bean will be created and populated, and the reference is injected into this bean.
If our bean implements any of the Spring’s interfaces, such as BeanNameAware
or BeanFactoryAware
, appropriate callback methods will be invoked.
The framework also invokes any BeanPostProcessor
’s associated with your bean for pre-initialzation.
The init-method
, if specified, is invoked
on the bean.
The post-initialization will be performed if specified on the bean.
Do not get stressed if the things mentioned here don’t get digested yet—we will discuss these points in the coming sections in detail.
When comes to creating the beans, beans which have no dependencies will be created normally. Whereas, the beans which has dependencies (that is, some of its properties refer to other beans) will be created only after satisfying the dependencies they have.
We will discuss this process in the next section, using some examples.
Note that we can also create beans by using static methods or Factories. We will look at them in the next chapter in detail.
Do you remember the FileReader
class we defined in our earlier
chapter? Here’s the snippet of the class if you can’t recall:
public
class
FileReader
implements
IReader
{
private
StringBuilder
builder
=
null
;
private
Scanner
scanner
=
null
;
// constructor
public
FileReader
(
String
fileName
)
{
...
}
// read method implementation
public
String
read
()
{
...
}
}
The FileReader
’s constructor takes in a fileName
as its argument in order to get ready for the action. This bean is defined by passing an
argument to the constructor, using meta-data. Look
at the meta data of the bean:
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FileReader"
>
<constructor-arg
value=
"src/main/resources/basics/basics-trades-data.txt"
/>
</bean>
The required fileName
variable is set with a value via the
constructor-arg
tag, providing a value of
src/main/resources/basics/basics-trades-data.txt
as an argument.
When the Framework reads the definition of this class, it creates an instance by using the new operator (in reality, the bean is instantiated by using Java Reflection). As this bean has no dependencies, it will now be instantiated and ready to use.
There is a second case where beans depend on other beans. For example, we have seen a
Person
having an Address
. Unless the
Address
object is created, creating the Person can’t be succesful. So,
the Person bean is dependent on the Address bean. However, if the
FileReader
bean has a dependency on another bean, the
other bean will be created and instantiated. See the following snippet.
See the definition of Person
having an address
property which refers to another bean named address
? The Person object
will be injected with an Address object to satisfy the dependency. The bean dependencies can
be one or more beans.
<bean
name=
"person"
class=
"com.madhusudhan.jscore.beans.Person"
>
....<property
name=
"address"
ref=
"address"
/>
</bean>
<bean
name=
"address"
class=
"com.madhusudhan.jscore.beans.Address"
>
....</bean>
The order of creation is important to Spring. After digesting the configuration
metadata, Spring creates a plan (it allocates certain priorities to each bean) with the
order of beans that needs to be created to satisfy dependencies. Hence, the Address
object is created first, before the Person
. If Spring encounters any exception while creating the
Address
object, it will fail fast and quit the program.
It does not create any further beans and lets the developer know why it won’t progress
further.
Sometimes, we may need to give the same bean a different name—usually as alias. For
example, an Address bean can be called as shipping address or a billing address. Aliasing is a
way of naming the same bean with different names. We use the alias
tag to
give a name to a predefined bean.
<bean
name=
"address"
class=
"com.madhusudhan.jscore.fundamentals.Address"
>
...</bean>
<alias
name=
"address"
alias="billingAddress"/><alias
name=
"address"
alias="shippingAddress"/>
In the above snippet, we have declared an Address
bean with
address
as it’s name. Two aliases, billingAddress
and
shippingAddress
were declared, both pointing to the same bean. We can use
either of the aliases in our application as if they were original beans.
We can also create beans whose existence is associated to the referencing bean only. These types of beans are called Anonymous or Inner beans. They are nameless and hence not available for our programs to query them.
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FileReader"
>
<constructor-arg
value=
"src/main/resources/basics/basics-trades-data.txt"
/>
<property
name=
"platformLineEnder"
<bean
class=
"com.madhusudhan.jscore.basics.readers.WindowsLineEnder"
/>
</property>
</bean>
The platformLineEnder
property
refers to a WindowsLineEnder
bean.
Because the platformLineEnder
bean has been defined as
a property (no ref
tag is defined), it
is not available to any other beans in the context except the FileReader
bean.
Spring allows us to inject the properties via constructors or setters. While both types are equally valid and simple, it’s a matter of personal preference in choosing one over the other.
One advantage to using constructor types over setters is that we do not have to write additional setter code. Having said that, it is not ideal to create constructors with lots of properties as arguments. I detest writing a class with a constructor that has more than a couple of arguments!
In the previous examples, we have seen how to inject the
properties via constructors by using the constructor-arg
attribute. Those snippets
illustrate the constructor injection method. The basic idea is that the
class will have a constructor that takes the arguments, and these
arguments are wired via the config file.
Here is an FtpReader
code snippet that has a
constructor taking two arguments:
public
class
FtpReader
implements
IReader
{
private
String
ftpHost
=
null
;
private
int
ftpPort
=
0
;
public
FtpReader
(
String
ftpHost
,
int
ftpPort
)
{
this
.
ftpHost
=
ftpHost
;
this
.
ftpPort
=
ftpPort
;
}
@Override
public
String
read
()
{
// your impl goes here
return
null
;
}
}
The
ftpHost
and ftpPort
arguments are then wired using
attributes defined in the XML config file:
constructor-arg
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FtpReader"
>
<constructor-arg
value=
"madhusudhan.com"
/>
<constructor-arg
value=
"10009"
/>
</bean>
You can set references to other beans, too via the constructor arguments. For example,
the following snippet injects a reference FileReader
into
the ReaderService
constructor:
<bean
name=
"readerService"
class=
"com.madhusudhan.jscore.basics.service.ReaderService"
>
<constructor-arg
ref=
"reader"
/>
</bean>
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FileReader"
>
<constructor-arg
value=
"src/main/resources/basics/basics-trades-data.txt"
/>
</bean>
This is how the ReaderService
will look with a constructor accepting
an IReader
type:
public
class
ReaderService
{
private
IReader
reader
=
null
;
public
ReaderService
(
IReader
reader
)
{
this
.
reader
=
reader
;
}
...
}
One quick note about constructor type injection—there are couple of rules that
framework will follow when resolving the types of the arguments. In the preceding FtpReader
example, the first argument was ftpHost
followed by ftpPort
. The case is straightforward—the constructor expects a string and an
integer, so the framework picks the first argument as String type and the second one as
Integer type.
Although you declare them as string values in your config file (such as value=".."
), the java.beans.
PropertyEditor
’s are used by
the framework to convert the string value to the appropriate property type.
Ideally, the declaration should define the types too as shown in the following snippet:
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FtpReader"
>
<constructor-arg
type="String" value="madhusudhan.com" /><constructor-arg
type="int" value="10009" /></bean>
The types are normal Java types—such as int
,
boolean
, double
, String
, and so
on.
You could also set index’s on the values, starting the index from zero as shown here:
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FtpReader"
>
<constructor-arg
index="0" type="String" value="madhusudhan.com" /><constructor-arg
index="1" type="int" value="10009" /></bean>
In addition to injecting the dependent beans and properties via constructors, Spring also allows them to be injected via setters, too. In order to use the setter injection, we have to provide setters on the respective variables. If the property exhibits read and write characteristics, provide both a setter and a getter on the variable.
So, in our ReaderService
class,
create a variable of IReader
type and
a matching setter/getter for that property. The constructor is left
empty as the properties are now populated using the setters. You should
follow the normal bean conventions when creating setters and
getters.
Modified ReaderService
is given here:
public
class
ReaderService
{
private
IReader
reader
=
null
;
// Setter and getter
public
void
setReader
(
IReader
reader
)
{
this
.
reader
=
reader
;
}
public
IReader
getReader
()
{
return
reader
;
}
...
}
The significant points are the the setter and getter methods on the IReader
variable and the omission of the constructor altogether.
The configuration of the class in our XML file looks like this:
<bean
name=
"readerService"
class=
"com.madhusudhan.jscore.basics.service.ReaderService"
>
<!-- Setter type injection -->
<property
name=
"reader"
ref=
"reader"
/>
</bean>
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.basics.readers.FileReader"
>
...</bean>
The notable change is to create a property called reader
and set it with a reference to the FileReader
class. The framework will check the ReaderService
for a
reader
property and invoke setReader
by passing the FileReader
instance.
You can mix and match the injection types as you like, too. The revised FileReader
class listed here has a constructor as well as few
other properties. The componentName
is set using setter,
while the fileName
is injected via constuctor.
<bean
name=
"reader"
class=
"com.madhusudhan.jscore.fundamentals.injection.FileReader"
>
<constructor-arg
value=
"src/main/resources/basics/basics-trades-data.txt"
/>
<property
name=
"componentName"
value=
"TradeFileReader"
/>
</bean>
Although mixing and matching the injection types is absolutely possible, I recommend sticking with one or the other of them, rather than both, to avoid complicating matters.
Spring Framework provides a couple of hooks to our beans in the form of callback methods.
These methods provide opportunity for the bean to initialize properties or clean up resources.
There are two such method hooks: init-method
and destroy-method
.
When a bean is being created, we can let Spring invoke a specific method on our bean to initialize. This method provides a chance for the bean to do housekeeping stuff and any initialization, such as creating data structures, creating thread pools, and so on.
Say we have a requirement of creating a class for fetching Foreign Exchange (FX) rates. The FxRateProvider is a class that provides us with these rates when queried (mind you, it’s a dummy implementation of FX Rates!).
See the code snippet here:
public
class
FxRateProvider
{
private
double
rate
=
0.0
;
private
String
baseCurrency
=
"USD"
;
private
Map
<
String
,
Double
>
currencies
=
null
;
/* Invoked via Spring's init-method callback */
public
void
initMe
()
{
currencies
=
new
HashMap
<
String
,
Double
>();
currencies
.
put
(
"GBP"
,
1.5
);
currencies
.
put
(
"USD"
,
1.0
);
currencies
.
put
(
"JPY"
,
1000.0
);
currencies
.
put
(
"EUR"
,
1.4
);
currencies
.
put
(
"INR"
,
50.00
);
}
public
double
getRate
(
String
currency
){
if
(!
currencies
.
containsKey
(
currency
))
return
0
;
return
currencies
.
get
(
currency
);
}
}
A noticeable point is the initMe
method. It is a normal method
invoked by the Framework during the process of its creation.
The associated configuration of the bean is provided in the following XML:
<bean
name=
"fxRateProvider"
class=
"com.madhusudhan.jscore.fundamentals.callbacks.FxRateProvider"
init-method="initMe"><property
name=
"baseCurrency"
value=
"USD"
/>
</bean>
Similar to the initialization, framework provides a destroy method to clean up before
destroying the bean—named as destroy-method.
The
FxRateProvider shown here has a destroy
method:
public
class
FxRateProvider
{
public
void
destroyMe
()
{
// do your cleanup operations here
currencies
=
null
;
}
...
}
We should refer the destroyMe
method in the XML
declaration like
this:
<bean
name=
"fxRateProvider"
class=
"com.madhusudhan.jscore.fundamentals.callbacks.FxRateProvider"
init-method=
"initMe"
destroy-method="destroyMe"><property
name=
"baseCurrency"
value=
"USD"
/>
</bean>
When the program quits, the framework destroys the beans. During the process of
destruction, when the config metadata declares destroyMe
as the destroy method, the destroyMe
method is
invoked. This gives the bean a chance to do some housekeeping activities if we wish.
Ideally, this method should be coded to free up resources, nullify objects, and other cleanup operations.
Let’s say we religiously code a init
and destroy
methods on all our beans. Does it mean we have to declare the init-method
and destroy-method
explicitly on each and every bean? Well, not
exactly.
As long as we define the same method names across all the beans, we can use Framework’s
facility of declaring default callbacks—default-init-method
and
default-destroy-method
.
Instead of declaring the individual methods on each of the bean, we need to declare
these default callbacks at the topmost beans
element. See how they have
been declared in the following XML snippet:
<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"
default-init-method="initMe" default-destroy-method="destroyMe" ><bean>
...</bean>
</beans>
Note that these default methods are associated to the beans
element
rather than a bean
element.
From the above example, the initMe
and
destroyMe
methods will be invoked automaticaly on all
the beans. If any of the beans don’t have these methods, Framework
ignores them and no action is performed.
This chapter discussed the Spring Framework in detail. It explained the concept of beans and bean factories. We have also learned about the life cycle of the bean scopes and touched upon the property editors used in injecting Java Collections and other types of objects.
We discuss the containers and application contexts in the next chapter, which forms a crucial concept in putting the framework to work.
52.15.42.128