Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (5.49 MB, 753 trang )
9799ch03.qxd
46
5/5/08
4:50 PM
Page 46
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
The application context provides more advanced features than the bean factory while
keeping the basic features compatible. So, I strongly recommend using the application context for every application unless the resources of this application are restricted, such as when
running in an applet or a mobile device.
The interfaces for the bean factory and the application context are BeanFactory and
ApplicationContext, respectively. The interface ApplicationContext is a subinterface of
BeanFactory for maintaining compatibility.
How It Works
Instantiating a Bean Factory
To instantiate a bean factory, you have to load the bean configuration file into a Resource
object first. For example, the following statement loads your configuration file from the root of
the classpath:
Resource resource = new ClassPathResource("beans.xml");
Resource is only an interface, while ClassPathResource is one of its implementations for
loading a resource from the classpath. Other implementations of the Resource interface, such
as FileSystemResource, InputStreamResource, and UrlResource, are used to load a resource
from other locations. Figure 3-1 shows the common implementations of the Resource interface in Spring.
Figure 3-1. Common implementations of the Resource interface
Next, you can use the following statement to instantiate a bean factory by passing in a
Resource object with the configuration file loaded:
BeanFactory factory = new XmlBeanFactory(resource);
As mentioned, BeanFactory is only an interface for abstracting the operations of a bean
factory, while XmlBeanFactory is the implementation that builds a bean factory from an XML
configuration file.
9799ch03.qxd
5/5/08
4:50 PM
Page 47
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
Instantiating an Application Context
Like BeanFactory, ApplicationContext is an interface only. You have to instantiate an implementation of it. The ClassPathXmlApplicationContext implementation builds an application
context by loading an XML configuration file from the classpath. You can also specify multiple
configuration files for it.
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Besides ClassPathXmlApplicationContext, there are several other ApplicationContext
implementations provided by Spring. FileSystemXmlApplicationContext is used to load
XML configuration files from the file system, while XmlWebApplicationContext and
XmlPortletApplicationContext can be used in web and portal applications only. Figure 3-2
shows the common implementations of the ApplicationContext interface in Spring.
Figure 3-2. Common implementations of the ApplicationContext interface
Getting Beans from the IoC Container
To get a declared bean from a bean factory or an application context, you just make a call to
the getBean() method and pass in the unique bean name. The return type of the getBean()
method is java.lang.Object, so you have to cast it to its actual type before using it.
SequenceGenerator generator =
(SequenceGenerator) context.getBean("sequenceGenerator");
Up to this step, you are free to use the bean just like any object you created using a constructor. The complete source code for running the sequence generator application is given in
the following Main class:
package com.apress.springrecipes.sequence;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
47
9799ch03.qxd
48
5/5/08
4:50 PM
Page 48
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
SequenceGenerator generator =
(SequenceGenerator) context.getBean("sequenceGenerator");
System.out.println(generator.getSequence());
System.out.println(generator.getSequence());
}
}
If everything is fine, you should see the following sequence numbers output, along with
some logging messages that you may not be interested in:
30100000A
30100001A
3-3. Resolving Constructor Ambiguity
Problem
When you specify one or more constructor arguments for a bean, Spring will attempt to find
an appropriate constructor in the bean class and pass in your arguments for bean instantiation. However, if your arguments can be applied to more than one constructor, it may cause
ambiguity in constructor matching. In this case, Spring may not be able to invoke your
expected constructor.
Solution
You can specify the attributes type and index for the
Spring in finding your expected constructor.
How It Works
Now let’s add a new constructor to the SequenceGenerator class with prefix and suffix as
arguments.
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
...
public SequenceGenerator(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
9799ch03.qxd
5/5/08
4:50 PM
Page 49
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
}
}
In its bean declaration, you can specify one or more constructor arguments through the
class and pass in your arguments for bean instantiation. Recall that there’s not a name attribute
in
class="com.apress.springrecipes.sequence.SequenceGenerator">
It’s easy for Spring to find a constructor for these two arguments, as there is only one
constructor that requires two arguments. Suppose you have to add another constructor to
SequenceGenerator with prefix and initial as arguments.
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
...
public SequenceGenerator(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
public SequenceGenerator(String prefix, int initial) {
this.prefix = prefix;
this.initial = initial;
}
}
To invoke this constructor, you make the following bean declaration to pass a prefix and
an initial value. The remaining suffix is injected through the setter method.
class="com.apress.springrecipes.sequence.SequenceGenerator">
However, if you run the application now, you will get the following result:
300A
301A
49
9799ch03.qxd
50
5/5/08
4:50 PM
Page 50
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
The cause of this unexpected result is that the first constructor, with prefix and suffix as
arguments, has been invoked, but not the second. This is because Spring resolved both of your
arguments as String type by default and considered that the first constructor was most suitable, as no type conversion was required. To specify the expected type of your arguments, you
have to set it in the type attribute in
class="com.apress.springrecipes.sequence.SequenceGenerator">
Now add one more constructor to SequenceGenerator with initial and suffix as arguments, and modify your bean declaration for it accordingly.
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
...
public SequenceGenerator(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
public SequenceGenerator(String prefix, int initial) {
this.prefix = prefix;
this.initial = initial;
}
public SequenceGenerator(int initial, String suffix) {
this.initial = initial;
this.suffix = suffix;
}
}
class="com.apress.springrecipes.sequence.SequenceGenerator">
If you run the application again, you may get the right result or the following unexpected
result:
30100000null
30100001null
9799ch03.qxd
5/5/08
4:50 PM
Page 51
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
The reason for this uncertainty is that Spring internally scores each constructor for
compatibility with your arguments. But during the scoring process, the order in which your
arguments appear in the XML is not considered. This means that from the view of Spring, the
second and the third constructors will get the same score. Which one to pick depends on
which one is matched first. According to the Java Reflection API, or more accurately the
Class.getDeclaredConstructors() method, the constructors returned will be in an arbitrary
order that may differ from the declaration order. All these factors, acting together, cause ambiguity in constructor matching.
To avoid this problem, you have to indicate the indexes of your arguments explicitly
through the index attribute of
Spring will be able to find the expected constructor for a bean accurately.
class="com.apress.springrecipes.sequence.SequenceGenerator">
However, if you are quite sure that your constructors won’t cause ambiguity, you can skip
the type and index attributes.
3-4. Specifying Bean References
Problem
The beans that make up your application often need to collaborate with each other to complete the application’s functions. For beans to access each other, you have to specify bean
references in the bean configuration file.
Solution
In the bean configuration file, you can specify a bean reference for a bean property or a constructor argument by the element. It’s as easy as specifying a simple value by the
element. You can also enclose a bean declaration in a property or a constructor argument
directly as an inner bean.
How It Works
Accepting a string value as the prefix of your sequence generator is not flexible enough to
adapt to future requirements. It would be better if the prefix generation could be customized
with some kind of programming logic. You can create the PrefixGenerator interface to define
the prefix generation operation.
package com.apress.springrecipes.sequence;
public interface PrefixGenerator {
public String getPrefix();
}
51
9799ch03.qxd
52
5/5/08
4:50 PM
Page 52
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
One prefix generation strategy is to use a particular pattern to format the current system
date. Let’s create the DatePrefixGenerator class that implements the PrefixGenerator
interface.
package com.apress.springrecipes.sequence;
...
public class DatePrefixGenerator implements PrefixGenerator {
private DateFormat formatter;
public void setPattern(String pattern) {
this.formatter = new SimpleDateFormat(pattern);
}
public String getPrefix() {
return formatter.format(new Date());
}
}
The pattern of this generator will be injected through the setter method setPattern() and
then used to create a java.text.DateFormat object to format the date. As the pattern string will
not be used any more once the DateFormat object is created, it’s not necessary to store it in a
private field.
Now you can declare a bean of type DatePrefixGenerator with an arbitrary pattern string
for date formatting.
class="com.apress.springrecipes.sequence.DatePrefixGenerator">
Specifying Bean References for Setter Methods
To apply this prefix generator approach, the SequenceGenerator class should accept an object
of type PrefixGenerator instead of a simple prefix string. You may choose setter injection to
accept this prefix generator. You have to delete the prefix property and its setter methods and
constructors that cause compile errors.
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
...
private PrefixGenerator prefixGenerator;
public void setPrefixGenerator(PrefixGenerator prefixGenerator) {
this.prefixGenerator = prefixGenerator;
}
public synchronized String getSequence() {
StringBuffer buffer = new StringBuffer();
9799ch03.qxd
5/5/08
4:50 PM
Page 53
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
buffer.append(prefixGenerator.getPrefix());
buffer.append(initial + counter++);
buffer.append(suffix);
return buffer.toString();
}
}
Then a SequenceGenerator bean can refer to the datePrefixGenerator bean as its
prefixGenerator property by enclosing a element inside.
class="com.apress.springrecipes.sequence.SequenceGenerator">
The bean name in the element’s bean attribute can be a reference to any bean in the
IoC container, even if it’s not defined in the same XML configuration file. If you are referring to
a bean in the same XML file, you should use the local attribute, as it is an XML ID reference.
Your XML editor can help to validate whether a bean with that ID exists in the same XML file
(i.e., the reference integrity).
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
There is also a convenient shortcut to specify a bean reference in the ref attribute of the
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
But in this way, your XML editor will not be able to validate the reference integrity. Actually, it has the same effect as specifying the element’s bean attribute.
Spring 2.x provides another convenient shortcut for you to specify bean references. It’s by
using the p schema to specify bean references as attributes of the
shorten the lines of XML configuration.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
53
9799ch03.qxd
54
5/5/08
4:50 PM
Page 54
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
class="com.apress.springrecipes.sequence.SequenceGenerator"
p:suffix="A" p:initial="1000000"
p:prefixGenerator-ref="datePrefixGenerator" />
To distinguish a bean reference from a simple property value, you have to add the -ref
suffix to the property name.
Specifying Bean References for Constructor Arguments
Bean references can also be applied to constructor injection. For example, you can add a constructor that accepts a PrefixGenerator object as an argument.
package com.apress.springrecipes.sequence;
public class SequenceGenerator {
...
private PrefixGenerator prefixGenerator;
public SequenceGenerator(PrefixGenerator prefixGenerator) {
this.prefixGenerator = prefixGenerator;
}
}
In the
the
class="com.apress.springrecipes.sequence.SequenceGenerator">
The shortcut for specifying a bean reference also works for
class="com.apress.springrecipes.sequence.SequenceGenerator">
...
9799ch03.qxd
5/5/08
4:50 PM
Page 55
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
Declaring Inner Beans
Whenever a bean instance is used for one particular property only, it can be declared as an
inner bean. An inner bean declaration is enclosed in
without any id or name attribute set. In this way, the bean will be anonymous so that you can’t
use it anywhere else. In fact, even if you define an id or a name attribute for an inner bean, it
will be ignored.
class="com.apress.springrecipes.sequence.SequenceGenerator">
An inner bean can also be declared in a constructor argument.
class="com.apress.springrecipes.sequence.SequenceGenerator">
3-5. Checking Properties with Dependency Checking
Problem
In a production-scale application, there may be hundreds or thousands of beans declared in
the IoC container, and the dependencies between them are often very complicated. One of the
shortcomings of setter injection is that you cannot make sure a property will be injected. It’s
very hard for you to check if all required properties have been set.
Solution
Spring’s dependency checking feature can help you to check if all properties of certain types
have been set on a bean. You simply have to specify the dependency checking mode in the
dependency-check attribute of
check if the properties have been set, but can’t check if their value is not null. Table 3-1 lists all
the dependency checking modes supported by Spring.
55
9799ch03.qxd
56
5/5/08
4:50 PM
Page 56
CHAPTER 3 ■ BEAN CONFIGURATION IN SPRING
Table 3-1. Dependency Checking Modes Supported by Spring
Mode
Description
none*
No dependency checking will be performed. Any properties can be left unset.
simple
If any properties of the simple types (the primitive and collection types) have not been
set, an UnsatisfiedDependencyException will be thrown.
objects
If any properties of the object types (other than the simple types) have not been set, an
UnsatisfiedDependencyException will be thrown.
all
If any properties of any type have not been set, an UnsatisfiedDependencyException
will be thrown.
* The default mode is none, but this can be changed by setting the default-dependency-check attribute of the
this attribute with great care as it will alter the default dependency checking mode for all the beans in the
IoC container.
How It Works
Checking Properties of the Simple Types
Suppose the suffix property was not set for the sequence generator. Then the generator
would generate sequence numbers whose suffix was the string null. This kind of issue is often
very hard to debug, especially in a complicated bean. Fortunately, Spring is able to check if all
properties of certain types have been set. To ask Spring to check properties of the simple types
(i.e., the primitive and collection types), set the dependency-check attribute of
simple.
class="com.apress.springrecipes.sequence.SequenceGenerator"
dependency-check="simple">
If any properties of such types have not been set, an UnsatisfiedDependencyException will
be thrown, indicating the unset property.
Exception in thread "main"
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating
bean with name 'sequenceGenerator' defined in class path resource [beans.xml]:
Unsatisfied dependency expressed through bean property 'suffix': Set this property
value or disable dependency checking for this bean.