- Spring Essentials
- Shameer Kunjumohamed Hamidreza Sattari
- 2081字
- 2021-07-16 13:05:47
Beans in detail
A Spring application is composed of a set of beans that perform functionality specific to your application layers and are managed by the IoC container. You define your beans with configuration metadata in the form of XML, annotation, or JavaConfig.
Note
The default scope of a Spring bean is singleton
. This means that a single instance is shared between clients anywhere in the application. Beware of keeping state (class level data) in singleton
classes, as a value set by one client will be visible to all others. The best use case for such singleton
classes are stateless services.
Beans are uniquely identified by an id
attribute, any of the values supplied to the (comma, semicolon, or space separated) name
attribute of the bean definition, or even as an alias
definition. You can refer to a bean anywhere in the application with id
or any of the names or aliases specified in the bean definition.
It's not necessary that you always provide an id
or name to the bean. If one isn't provided, Spring will generate a unique bean name for it; however, if you want to refer to it with a name or an id
, then you must provide one.
Spring will try to autowire beans by type if id
or name is not provided. This means that ApplicationContext
will try to match the bean with the same type or implementation in case it is an interface.
You can refer to a bean by type if it is either the only bean registered of that type or marked as @Primary
(primary="true"
for XML). Generally, for nested bean definitions and autowire collaborators, you don't need to define a name unless you refer to it outside the definition.
You can alias a bean outside the bean definition using the <alias/>
tag, as follows:
<alias name="fromName" alias="toName"/>
Bean definition
A bean definition object that you define to describe a bean has the following metadata:
Let's take a look at a sample bean definition in XML form:
<bean id="xmlTaskService" class="com...XmlDefinedTaskService" init-method="init" destroy-method="cleanup"> <constructor-arg ref="userService"/> <constructor-arg> <bean class="com...TaskInMemoryDAO"></bean> </constructor-arg> </bean>
In this sample application-context
file, the bean, xmlTaskService
, is autowired via a constructor, that is, dependencies are injected via a constructor. The first constructor argument refers to an existing bean definition, and the second one is an inline bean definition without an id
. The bean has init-method
and destroy-method
pointed to its own methods.
Now, let's take a look at an annotated bean with slightly different features:
@Service public class AnnotatedTaskService implements TaskService { ... @Autowired private UserService userService; @Autowired private TaskDAO taskDAO; @PostConstruct public void init() { logger.debug(this.getClass().getName() + " started!"); } @PreDestroy public void cleanup() { logger.debug(this.getClass().getName() + " is about to destroy!"); } public Task createTask(String name, int priority, int createdByuserId, int assigneeUserId) { Task task = new Task(name, priority, "Open", userService.findById(createdByuserId), null, userService.findById(assigneeUserId)); taskDAO.createTask(task); logger.info("Task created: " + task); return task; } ... }
This @Service
bean autowires its dependencies on its fields (properties) using an @Autowired
annotation. Note the @PostConstruct
and @PreDestroy
annotations, the equivalents of init-method
and destroy-method
in the previous XML bean definition example. These are not Spring specific but are JSR 250 annotations. They work pretty well with Spring.
Instantiating beans
Bean definitions are recipes for instantiating bean instances. Depending on metadata attributes such as scope
, lazy
, and depends-on
, Spring Framework decides when and how an instance is created. We will discuss it in detail later. Here, let's look at the "how" of instance creation.
With constructors
Any bean definition with or without constructor arguments but without a factory-method
is instantiated via its own constructor, using the new
operator:
<bean id="greeter" class="com...GreetingBean"></bean>
Now let's see an annotated @Component
with a default constructor-based instantiation:
@Component("greeter") public class GreetingService { ... }
With a static factory-method
A static method within the same class, marked as factory-method
, will be invoked to create an instance in this case:
<bean id="Greeter" class="...GreetingBean" factory-method="newInstance"></bean>
With Java configuration, you can use an @Bean
annotation instead of factory methods:
@Configuration @ComponentScan(basePackages = "com.springessentialsbook") public class SpringJavaConfigurator { ... @Bean public BannerService createBanner() { return new BannerServiceImpl(); } ... }
With an instance factory-method
In this case, bean definition does not need a class attribute, but you specify the factory-bean
attribute, which is another bean, with one of its non-static methods as factory-method
:
<bean id="greeter" factory-bean="serviceFactory" factory-method="createGreeter"/> <bean id="serviceFactory" class="...ServiceFactory"> <!— ... Dependencies ... --> </bean>
Injecting bean dependencies
The main purpose of an IoC container is to resolve the dependencies of objects (beans) before they are returned to the clients who called for an instance (say, using the getBean
method). Spring does this job transparently based on the bean configuration. When the client receives the bean, all its dependencies are resolved unless specified as not required (@Autowired(required = false)
), and it is ready to use.
Spring supports two major variants of DI—constructor-based and setter-based DI—right out of the box.
Constructor-based Dependency Injection
In constructor-based DI, dependencies to a bean are injected as constructor arguments. Basically, the container calls the defined constructor, passing the resolved values of the arguments. It is best practice to resolve mandatory dependencies via a constructor. Let's look at an example of a simple POJO @Service
class, a ready candidate for constructor-based DI:
public class SimpleTaskService implements TaskService { ... private UserService userService; private TaskDAO taskDAO; public SimpleTaskService(UserService userService, TaskDAO taskDAO) { this.userService = userService; this.taskDAO = taskDAO; } ... }
Now, let's define this as a Spring bean in XML:
<bean id="taskService" class="com...SimpleTaskService""> <constructor-arg ref="userService" /> <constructor-arg ref="taskDAO"/> </bean>
The Spring container resolves dependencies via a constructor based on the argument's type. For the preceding example, you don't need to pass the index or type of the arguments, since they are of complex types.
However, if your constructor has simple types, such as primitives (int
, long
, and boolean
), primitive wrappers (java.lang.Integer
, Long
, and so on) or String
, ambiguities of type and index may arise. In this case, you can explicitly specify the type and index of each argument to help the container match the arguments, as follows:
<bean id="systemSettings" class="com...SystemSettings"> <constructor-arg index="0" type="int" value="5"/> <constructor-arg index="1" type="java.lang.String" value="dd/mm/yyyy"/> <constructor-arg index="2" type="java.lang.String" value="Taskify!"/> </bean>
Remember, index numbers start from zero. The same applies to setter-based injection as well.
Setter-based Dependency Injection
The container calls the setter methods of your bean in the case of setter-based DI after the constructor (with or without args
) is invoked. Let's see how the bean definition for the previous SystemSettings
would look if the dependencies were injected via setter methods, assuming the SystemSettings
now has a no-args
constructor:
<bean id="systemSettings" class="com...SystemSettings"> <property name="openUserTasksMaxLimit" value="5"/> <property name="systemDateFormat" value="dd/mm/yyyy"/> <property name="appDisplayName" value="Taskify!"/> </bean>
Spring validates the bean definitions at the startup of the ApplicationContext
and fails with a proper message in case of a wrong configuration. The string values given to properties with built-in types such as int
, long
, String
, and boolean
are converted and injected automatically when the bean instances are created.
Constructor-based or setter-based DI – which is better?
Which of these DI methods is better purely depends on your scenario and some requirements. The following best practices may provide a guideline:
- Use constructor-based DI for mandatory dependencies so that your bean is ready to use when it is first called.
- When your constructor gets stuffed with a large number of arguments, it's the figurative bad code smell. It's time to break your bean into smaller units for maintainability.
- Use setter-based DI only for optional dependencies or if you need to reinject dependencies later, perhaps using JMX.
- Avoid circular dependencies that occur when a dependency (say, bean B) of your bean (bean A) directly or indirectly depends on the same bean again (bean A), and all beans involved use constructor-based DI. You may use setter-based DI here.
- You can mix constructor-based and setter-based DI for the same bean, considering mandatory, optional, and circular dependencies.
In a typical Spring application, you can see dependencies injected using both approaches, but this depends on the scenario, considering the preceding guidelines.
Cleaner bean definitions with namespace shortcuts
You can make your bean definitions cleaner and more expressive using p:(property)
and c:(constructor)
namespaces, as shown here. While the p
namespace enables you to use the <bean/>
element's attributes instead of the nested <property/>
elements in order to describe your property values (or collaborating bean refs), the c
namespace allows you to declare the constructor args
as the attributes of the <bean/>
element:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context/spring-context.xsd"> <bean id="p-taskService" class="com...SimpleTaskService" c:userService-ref="userService" c:taskDAO-ref="taskDAO"/> <bean id="p-systemSettings" class="com...SystemSettings" p:openUserTasksMaxLimit="5" p:systemDateFormat"dd/mm/yyyy" p:appDisplayName="Taskify!"/> </beans>
The bean definitions in the preceding listing are cleaner but more expressive. Both c:
and p:
namespaces follow the same conventions. You need to declare both at the XML root element (<beans/>
) before using them with the <bean/>
elements. Note that you use the -ref
suffix for bean references.
Wiring a List as a dependency
On occasion, we will need to inject static collections of data as bean dependencies. Spring provides a natural method to wire lists. See this example:
<bean id="systemSettings" class="com...SystemSettings"> . . . <constructor-arg> <list> <value>admin@taskify.ae</value> <value>it@taskify.ae</value> <value>devops@taskify.ae</value> </list> </constructor-arg> </bean>
The preceding example wires a java.util.List<String>
for simplicity. If your list contains a collection of beans, you can replace <value>
with <ref>
or <bean>
.
Wiring a Map as a dependency
You can inject java.util.Map
instances too in a similar fashion. Look at this example:
<bean id="systemSettings" class="com...SystemSettings"> . . . <property name="emails"> <map> <entry key="admin" value="admin@taskify.ae"></entry> <entry key="it" value="it@taskify.ae"></entry> <entry key="devops" value="devops@taskify.ae"></entry> </map> </property> </bean>
You can inject beans as values, replacing <value>
with <ref>
or <bean>
.
Autowiring dependencies
Spring can autowire dependencies of your beans automatically by inspecting the bean definitions present in the ApplicationContext
if you specify the autowire mode. In XML, you specify the autowire
attribute of the <bean/>
element. Alternatively, you can annotate a bean with @Autowired
to autowire dependencies. Spring supports four autowiring modes: no
, byName
, byType
, and constructor
.
Note
The default autowiring of Spring beans is byType
. If you are autowiring an interface, Spring will try to find an implementation of that interface configured as a Spring bean. If there are multiple, Spring will look for the primary
attribute of the configuration to resolve; if not found, it will fail, complaining about an ambiguous bean definition.
Here is an example of autowiring constructor arguments:
@Service public class AnnotatedTaskService implements TaskService { ... @Autowired public AnnotatedTaskService(UserService userService, TaskDAO taskDAO) { this.userService = userService; this.taskDAO = taskDAO; } ... }
Alternatively, you can autowire at the field level, as follows:
@Service public class AnnotatedTaskService implements TaskService { ... @Autowired private UserService userService; @Autowired private TaskDAO taskDAO; ... }
Autowiring can be fine-tuned with an @Qualifier
annotation and required attribute:
@Autowired(required = true) @Qualifier("taskDAO") private UserService userService;
You can use @Qualifier
at the constructor level too:
@Autowired public AnnotatedTaskService(@Qualifier("userService") UserService userService, @Qualifier("taskDAO") TaskDAO taskDAO) { this.userService = userService; this.taskDAO = taskDAO; }
Bean scoping
When defining a bean with its dependencies and other configuration values, you can optionally specify the scope of a bean in the bean definition. The scope determines the life span of the bean. Spring comes up with six built-in scopes out of the box and supports the creation of custom scopes too. If not explicitly specified, a bean will assume the singleton
scope, which is the default scope. The following table lists the built-in Spring scopes:
While singleton
and prototype
work in all environments, request, session, and application work only in web environments. The globalSession
scope is for portlet environments.
In an XML bean definition, the scope is set via the scope
attribute of the <bean/>
element:
<bean id="userPreferences" class="com...UserPreferences" scope="session">... </bean>
You can annotate the bean scope as a meta-annotation to @Component
or its derivations, such as @Service
and @Bean
, as shown in the following listing:
@Component @Scope("request") public class TaskSearch {...}
Generally, service classes and Spring data repositories are declared as singleton
, since they are built stateless according to best practice.
Dependency Injection with scoped beans
Beans of different scopes can be wired up as collaborators in your configuration metadata. For example, if you have a session-scoped bean as a dependency to singleton
and face an inconsistency problem, the first instance of the session-scoped bean will be shared between all users. This can be solved using a scoped proxy in place of the scoped bean:
<bean id="userPreferences" class="com...UserPreferences" scope="session"> <aop:scoped-proxy /> </bean> <bean id="taskService" class="com...TaskService"> <constructor-arg ref="userPreferences"/> </bean>
Every time the scoped bean is injected, Spring creates a new AOP proxy around the bean so that the instance is picked up from the exact scope. The annotated version of the preceding listing would look like this:
@Component @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public class UserPreferences { ... } public class AnnotatedTaskService implements TaskService { ... @Autowired private UserPreferences userPreferences; ... }
Creating a custom scope
At times, the scopes supplied by Spring are not sufficient for your specific needs. Spring allows you to create your own custom scope for your scenario. For example, if you want to keep some business process level information throughout its life, you will want to create a new process scope. The following steps will enable you to achieve this:
- Create a Java class extending
org.springframework.beans.factory.config.Scope
. - Define it in your application context (XML or annotation) as a Spring bean.
- Register the scope bean with your
ApplicationContext
either programmatically or in XML withCustomScopeConfigurer
.