ProSpring5Chap3 - SpotBugsExtensionForSpringFrameWork/CS5098 GitHub Wiki
Previously Chapter : dependency injection = IoC (interchangeable)
- Inversion of control concepts
- Inversion of control in Spring
- DI services
- Setter injection
- Constructor injection
- method injection
- DI services
- Dependency injection in Spring
- Implementation of IoC container
- BeanFactory
- ApplicationContext
- Implementation of IoC container
- Configuring the Spring application context
- XML configuration
- Annotation configuration
Two subtypes of IoC
- Dependency Lookup
- Dependency Injection
DL vs DI
- Dependency Lookup (DL) : traditional / a component must acquire a reference to a dependency
- Dependency Injection (DI) : more flexible and usable / dependencies are injected into the component by the IoC container
Two types of DL - No need to know
- Dependency Pull : dependencies are pulled from a registry as required.
- Contextualized Dependency Lookup (CDL) : similar to dependency pull, but in CDL, lookup is performed against the container that is managing the resource, not from some central registry, and it is usually performed at some set point.
public class ContextualizedDependencyLookup implements ManagedComponent {
private Dependency dependency;
@Override
public void performLookup(Container container) {
this.dependency = (Dependency) container.getDependency("myDependency");
}
@Override
public String toString() {
return dependency.toString();
}
}
// By implementing this interface, a component is signaling to the container that it wants to obtain a dependency
public interface ManagedComponent {
void performLookup(Container container);
}
Two common flavors of DI
- constructor injection
- The component declares a constructor or a set of constructors, taking as arguments its dependencies, and the IoC container passes the dependencies to the component when instantiation occurs
- particularly useful when you must have an instance of the dependency class before your component is used.
- setter dependency injection
- A component’s setters expose the dependencies the IoC container can manage.
- an object can be created without its dependencies, and they can be provided later by calling the setter.
public class ConstructorInjection {
private Dependency dependency;
public ConstructorInjection(Dependency dependency) {
this.dependency = dependency;
}
@Override
public String toString() {
return dependency.toString();
}
}
public class SetterInjection {
private Dependency dependency;
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
@Override
public String toString() {
return dependency.toString();
}
}
Which one should use?
- mandated by the container you are using
- EJB 2.1 or prior versions - lookup
- Spring - injection
-
injection: (adv)
- zero impact on your components’ code
- completely decoupled from the IoC container / easier testing
- lookup: (disadv) always dependent on the classes and interfaces defined by the container (=tightly coupled) / difficult to test
Overall: write simple and less code using injection
- must have an instance of the dependency class before your component is used
- by using constructor injection, you assert the requirement for the dependency in a container-agnostic manner.
- Constructor injection also helps achieve the use of immutable objects.
- the component is exposing its dependencies to the container but is happy to provide its own defaults
- it allows dependencies to be declared on an interface
- let each implementation class define its own dependencies and keep the business interface for business methods.
package com.apress.prospring5.ch3;
public interface NewsletterSender {
void setSmtpServer(String smtpServer);
String getSmtpServer();
void setFromAddress(String fromAddress);
String getFromAddress();
void send();
}
The reason is that these values, the SMTP server address and the address the e-mails are sent from, are not dependencies in the practical sense; rather, they are configuration details that affect how all implementations of the NewsletterSender interface function.
See whether a dependency should be classified as a configuration parameter.
- Configuration parameters are passive.
- Configuration parameters are usually information, not other components.
- Configuration parameters are usually simple values or collections of simple values.
Setter-based injection allows dependencies to be swapped out without creating new objects and also lets your class choose appropriate defaults without the need to explicitly inject an object.
Constructor injection is a good choice when you want to ensure that dependencies are being passed to a component and when designing for immutable objects.
Spring's dependency injection mechanism: In a Spring-based application, it is always preferable to use dependency injection to pass collaborators to dependent objects.
Refer to Figure 3-3.
Although dependency injection is the preferred mechanism for wiring together collaborators and dependent objects, you need dependency lookup to access the dependent objects. For example:
- bootstrap Spring’s container in the main() method and obtain the dependencies (via the ApplicationContext interface)
- But building web applications by using Spring’s MVC support, Spring can avoid this
- Basics of Spring’s dependency injection container,
- Looking at setter, constructor, and Method Injection,
- How dependency injection is configured in Spring.
BeanFactory
Interface: Managing components
- dependencies
- life cycles
Bean is used to refer to any component managed by the container.
- must create an instance of a class that implements the BeanFactory interface
- configures it with bean and dependency information
Application can access the beans via BeanFactory and get on with its processing
Although the BeanFactory can be configured programmatically, it is more common to see it configured externally using some kind of configuration file.
Each bean can be assigned an ID, a name, or both.
//interface package com.apress.prospring5.ch3;
public interface Oracle { String defineMeaningOfLife(); }
//implementation package com.apress.prospring5.ch3;
public class BookwormOracle implements Oracle {
private Encyclopedia encyclopedia;
public void setEncyclopedia(Encyclopedia encyclopedia) {
this.encyclopedia = encyclopedia;
}
@Override
public String defineMeaningOfLife() {
return "Encyclopedias are a waste of money - go see the world instead";
}
}
// Application
package com.apress.prospring5.ch3;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
public class XmlConfigWithBeanFactory {
public static void main(String... args) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader rdr = new XmlBeanDefinitionReader(factory);
rdr.loadBeanDefinitions(new ClassPathResource("spring/xml-bean-factory-config.xml"));
Oracle oracle = (Oracle) factory.getBean("oracle");
System.out.println(oracle.defineMeaningOfLife());
}
}
Using DefaultListableBeanFactory
: one of the two main BeanFactory implementations supplied with Spring
Reading in the BeanDefinition information from an XML file by using XmlBeanDefinitionReader
.
<?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="oracle"
name="wiseworm"
class="com.apress.prospring5.ch3.BookwormOracle"/>
</beans>
The XML file declares a Spring bean, gives it an ID of oracle and a name of wiseworm, and tells Spring that the underlying implementation class is com.apress.prospring4.ch3.BookwormOracle.
In addition to XmlBeanDefinitionReader, Spring also provides PropertiesBeanDefinitionReader, which allows you to manage your bean configuration by using properties rather than XML.
In Spring, the ApplicationContext interface is an extension to BeanFactory.
In addition to DI services, ApplicationContext provides other services.
- Transaction
- AOP service
- Message source for internationlization
- application event handling
- ...
In developing Spring-based applications, it’s recommended that you interact with Spring via the ApplicationContext interface.
Details of how to configure a Spring application, various aspects of configuring Spring application
ApplicationContext interface, which provides many more configuration options than the traditional BeanFactory interface.
Types of configuration:
- XML
- Annotations
- mix of both
Using an XML file can externalize all configuration from Java code, while annotations allow the developer to define and view the DI setup from within the code.
One common approach is:
- Defining the application infrastructure (for example, data source, transaction manager, JMS connection factory, or JMX) in an XML file.
- Defining the DI configuration (injectable beans and beans’ dependencies) in annotations.
For XML configuration, you need to declare the required namespace base provided by Spring that your application requires.
XML-style configuration
app-context-xml.xml
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
XML configuration with annotation support
app-context-annotation.xml
<?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:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.apress.prospring5.ch3.annotation"/>
</beans>
The <context:component-scan>
tag tells Spring to scan the code for injectable beans annotated with @Component, @Controller, @Repository, and @Service as well as supporting the @Autowired, @Inject, and @Resource annotations under the package (and all its subpackages) specified.
Moreover, the tag supports inclusion and exclusion of a component scan for more fine-grained control.
<context:component-scan base-package="com.apress.prospring5.ch3.annotation">
<context:exclude-filter type="assignable" expression="com.example.NotAService"/>
</context:component-scan>
The previous tag tells Spring to scan the package as specified but omit the classes that were assignable to the type as specified in the expression (can be either a class or an interface).
After you develop some kind of service class and want to use it in a Spring-based application, you need to tell Spring that those beans are eligible for injection to other beans and have Spring manage them for you.
package com.apress.prospring5.ch2.decoupled;
//renderer interface
public interface MessageRenderer {
void render();
void setMessageProvider(MessageProvider provider);
MessageProvider getMessageProvider();
}
// rendered implementation
public class StandardOutMessageRenderer implements MessageRenderer {
private MessageProvider messageProvider;
@Override
public void render() {
if (messageProvider == null) {
throw new RuntimeException( "You must set the property messageProvider of class:" +
StandardOutMessageRenderer.class.getName());
}
System.out.println(messageProvider.getMessage()); }
@Override
public void setMessageProvider(MessageProvider provider) { this.messageProvider = provider; }
@Override
public MessageProvider getMessageProvider() { return this.messageProvider; }
}
//provider interface
public interface MessageProvider { String getMessage(); }
//provider implementation
public class HelloWorldMessageProvider implements MessageProvider {
@Override
public String getMessage() { return "Hello World!"; }
}
<bean id="provider"
class="com.apress.prospring5.ch2.decoupled.HelloWorldMessageProvider"/>
<bean id="renderer"
class="com.apress.prospring5.ch2.decoupled.StandardOutMessageRenderer"
p:messageProvider-ref="provider"/>
The previous tags declare two beans, one with an ID of provider with the HelloWorldMessageProvider
implementation, and the other with an ID of renderer with the StandardOutMessageRenderer
implementation.
The bean classes must be annotated with the appropriate stereotype annotation,
and the methods or constructors must be annotated with @Autowired to tell the Spring IoC container to look for a bean of that type and use it as an argument when calling that method.
package com.apress.prospring5.ch3.annotation;
//simple bean
@Component("provider")
public class HelloWorldMessageProvider implements MessageProvider {
...
}
//complex, service bean
@Service("renderer")
public class StandardOutMessageRenderer implements MessageRenderer {
...
@Override
public void render() {
...
}
@Override
@Autowired
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}
@Override
public MessageProcider getMessageProvider() {
...
}
app-context-annotation.xml
: Spring will seek out those components and instantiate the beans with the specified names:
<context:component-scan base-package="com.apress.prospring5.ch3.annotation"/>
Using either approach doesn’t affect the way you obtain the beans from ApplicationContext.
package com.apress.prospring5.ch3;
import com.apress.prospring5.ch2.decoupled.MessageRenderer;
import org.springframework.context.support.GenericXmlApplicationContext;
public class DeclareSpringComponents {
public static void main(String... args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:spring/app-context-xml.xml");
ctx.refresh();
MessageRenderer messageRenderer = ctx.getBean("renderer", MessageRenderer.class);
messageRenderer.render(); ctx.close();
}
}
Instead of DefaultListableBeanFactory
, an instance of GenericXmlApplicationContext
is instantiated. The GenericXmlApplicationContext
class implements the ApplicationContext
interface and is able to bootstrap Spring’s ApplicationContext from the configurations defined in XML files.
app-context-xml.xml can be replaced with a configuration class, without modifying the classes representing the bean types being created
A configuration class:
- annotated with
@Configuration
- contains methods annotated with
@Bean
that are called directly by the Spring IoC container to instantiate the beans- The bean name will be the same as the name of the method used to create it.
@Configuration
public class HelloWorldConfiguration {
@Bean
public MessageProvider provider() { return new HelloWorldMessageProvider(); }
@Bean
public MessageRenderer renderer(){
MessageRenderer renderer = new StandardOutMessageRenderer();
renderer.setMessageProvider(provider());
return renderer;
}
}
A different implementation of ApplicationContext
is needed.
public class HelloWorldSpringAnnotated {
public static void main(String... args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext (HelloWorldConfiguration.class);
MessageRenderer mr = ctx.getBean("renderer", MessageRenderer.class);
mr.render();
}
}
Instead of DefaultListableBeanFactory
, an instance of AnnotationConfigApplicationContext
is instantiated.
A configuration class can be used to read the annotated beans definitions as well. In this case, because the bean’s definition configuration is part of the bean class, the class will no longer need any @Bean annotated methods.
package com.apress.prospring5.ch3.annotation;
@ComponentScan(basePackages = {"com.apress.prospring5.ch3.annotation"})
@Configuration
public class HelloWorldConfiguration {
}
@Component("provider")
public class HelloWorldMessageProvider implements MessageProvider {
@Override
public String getMessage() {
return "Hello World!";
}
}
@Service("renderer")
public class StandardOutMessageRenderer implements MessageRenderer {
private MessageProvider messageProvider;
@Override
public void render() {
if (messageProvider == null) {
throw new RuntimeException(
"You must set the property messageProvider of class:"
+ StandardOutMessageRenderer.class.getName());
}
System.out.println(messageProvider.getMessage());
}
@Override
@Autowired
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}
@Override
public MessageProvider getMessageProvider() {
return this.messageProvider;
}
}
This is done by annotating the configuration class with an annotation that is the equivalent of the <context:component-scanning ../>
element. This annotation is @ComponentScanning
and has the same parameters as the XML analogous element.
@ImportResource(locations = {"classpath:spring/app-context-xml.xml"})
@Configuration
public class HelloWorldConfiguration {
}
To configure setter injection by using XML configuration, you need to specify <property>
tags under the <bean>
tag for each <property>
into which you want to inject a dependency.
<beans ...>
<bean id="renderer" class="com.apress.prospring5.ch2.decoupled.StandardOutMessageRenderer">
<property name="messageProvider" ref="provider"/>
</bean>
<bean id="provider" class="com.apress.prospring5.ch2.decoupled.HelloWorldMessageProvider"/>
</beans>
The provider bean is assigned to the messageProvider
property. You can use the ref
attribute to assign a bean reference to a property.
Spring 2.5 or newer and have the p namespace declared in your XML configuration file.
xmlns:p="http://www.springframework.org/schema/p"
p:messageProvider-ref="provider"
just need to add an @Autowired annotation to the setter method
@Service("renderer")
public class StandardOutMessageRenderer implements MessageRenderer {
...
@Override
@Autowired
public void setMessageProvider(MessageProvider provider) {
this.messageProvider = provider;
}
}
package com.apress.prospring5.ch3.xml;
import com.apress.prospring5.ch2.decoupled.MessageProvider;
public class ConfigurableMessageProvider implements MessageProvider {
private String message;
public ConfigurableMessageProvider(String message) {
this.message = message;
}
@Override
public String getMessage() { return message; }
}
it is impossible to create an instance of ConfigurableMessageProvider without providing a value for the message.
The following code snippet shows how you can
- redefine the provider bean definition to create an instance of ConfigurableMessageProvider,
- injecting the message by using constructor injection:
<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="messageProvider"
class="com.apress.prospring5.ch3.xml.ConfigurableMessageProvider">
<constructor-arg value="I hope that someone gets my message in a bottle"/>
</bean>
</beans>
In this code, instead of using a <property>
tag, we used a <constructor-arg>
tag.
There is only one good reason for using
<constructor-arg>
, and that is to create immutable objects.
Because we are not passing in another bean this time, just a String literal, we use the value
attribute instead of ref
to specify the value for the constructor argument.
If more than one constructor, give each <constructor-arg>
tag an index attribute to specify the index of the argument, starting at 0, in the constructor signature.
you can also use the c namespace
xmlns:c="http://www.springframework.org/schema/c"
c:message="I hope that someone gets my message in a bottle"
we also use the @Autowired annotation in the target bean’s constructor method
@Autowired
public ConfigurableMessageProvider(@Value("Configurable message") String message) {
this.message = message;
}
@Value
, to define the value to be injected into the constructor
package com.apress.prospring5.ch3;
import com.apress.prospring5.ch2.decoupled.MessageProvider;
import org.springframework.context.support.GenericXmlApplicationContext;
public class DeclareSpringComponents {
public static void main(String... args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:spring/app-context-annotation.xml");
ctx.refresh();
MessageProvider messageProvider = ctx.getBean("provider", MessageProvider.class);
System.out.println(messageProvider.getMessage());
}
}
A good practice is to externalize those values for injection.
<beans ...>
<context:component-scan base-package="com.apress.prospring5.ch3.annotated"/>
<bean id="message" class="java.lang.String"
c:_0="I hope that someone gets my message in a bottle"/>
</beans>
Use the c namespace for constructor injection to set the string value, and _0 indicates the index for the constructor argument.
Having the bean declared, we can take away the @Value annotation from the target bean
@Autowired
public ConfigurableMessageProvider(String message) { this.message = message; }
public ConstructorConfusion(String someValue) {
System.out.println("ConstructorConfusion(String) called");
this.someValue = someValue;
}
public ConstructorConfusion(int someValue) {
System.out.println("ConstructorConfusion(int) called");
this.someValue = "Number: " + Integer.toString(someValue);
}
If:
<constructor-arg>
<value>90</value>
</constructor-arg>
ConstructorConfusion(String)
called.
<constructor-arg type="int">
<value>90</value>
</constructor-arg>
Notice now that the
<constructor-arg>
tag has an additional attribute, type, that specifies the type of argument Spring should look for
There is a third type of dependency injection supported in Spring called field injection. As the name says, the dependency is injected directly in the field, with no constructor or setter needed.
Done by annotating the class member with the Autowired annotation
@Service("singer")
public class Singer {
@Autowired
private Inspiration inspirationBean;
public void sing() { System.out.println("... " + inspirationBean.getLyric()); }
}
package com.apress.prospring5.ch3.annotated;
import org.springframework.context.support.GenericXmlApplicationContext;
public class FieldInjection {
public static void main(String... args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:spring/app-context.xml");
ctx.refresh();
Singer singerBean = ctx.getBean(Singer.class);
singerBean.sing();
ctx.close();
}
}
- be careful not to violate the single responsibility principle
- Using field injections, it can become unclear what type of dependency is really needed and if the dependency is mandatory or not.
- the bean is no longer a POJO and cannot be instantiated independently.
- Field injection cannot be used for final fields.
- Field injection introduces difficulties when writing tests
<bean id="injectSimpleSpel" class="com.apress.prospring5.ch3.xml.InjectSimpleSpel" p:name="John Mayer" p:age="39" p:height="1.92" p:programmer="false" p:ageInSeconds="1241401112"/>
define properties on your bean that accept String values, primitive values, or primitive wrapper values and then inject values for these properties by using the tag.
Apply the @Value annotation to the bean properties.
@Service("injectSimple")
public class InjectSimple {
@Value("John Mayer")
private String name;
@Value("39")
private int age;
...
}
SpEL enables you to evaluate an expression dynamically and then use it in Spring’s ApplicationContext.
(Need to complete)
Basically, using @Component has the same effect as @Service. Both annotations are instructing Spring that the annotated class is a candidate for autodetection using annotation-based configuration and classpath scanning.
Practically, @Service is a specialization of @Component, which indicates that the annotated class is providing a business service to other layers within the application.
Spring supports a hierarchical structure for ApplicationContext so that one context (and hence the associating BeanFactory) is considered the parent of another.
By allowing ApplicationContexts to be nested, Spring allows you to split your configuration into different files, which is a godsend on larger projects with lots of beans.
To nest one GenericXmlApplicationContext
inside another, simply call the setParent()
method in the child ApplicationContext.
public class HierarchicalAppContextUsage {
public static void main(String... args) {
GenericXmlApplicationContext parent = new GenericXmlApplicationContext();
parent.load("classpath:spring/parent-context.xml");
parent.refresh();
GenericXmlApplicationContext child = new GenericXmlApplicationContext();
child.load("classpath:spring/child-context.xml");
child.setParent(parent);
child.refresh();
Song song1 = (Song) child.getBean("song1");
Song song2 = (Song) child.getBean("song2");
Song song3 = (Song) child.getBean("song3");
System.out.println("from parent ctx: " + song1.getTitle());
System.out.println("from child ctx: " + song2.getTitle());
System.out.println("from parent ctx: " + song3.getTitle());
child.close();
parent.close();
}
}
Inside the configuration file for the child ApplicationContext, referencing a bean in the parent ApplicationContext works exactly like referencing a bean in the child ApplicationContext.
parent-context.xml: this configuration simply defines two beans: childTitle and parentTitle.
<beans ...>
<bean id="childTitle" class="java.lang.String" c:_0="Daughters"/>
<bean id="parentTitle" class="java.lang.String" c:_0="Gravity"/>
</beans>
child-context.xml:
<beans ...>
<bean id="song1" class="com.apress.prospring5.ch3.Song"
p:title-ref="parentTitle"/>
<bean id="song2" class="com.apress.prospring5.ch3.Song"
p:title-ref="childTitle"/>
<bean id="song3" class="com.apress.prospring5.ch3.Song">
<property name="title">
<ref parent="childTitle"/>
</property>
</bean>
<bean id="childTitle" class="java.lang.String" c:_0="No Such Thing"/>
</beans>
We have defined four beans here. childTitle
in this code is similar to childTitle
in the parent except that the String
it represents has a different value, indicating that it is located in the child ApplicationContext.
The song1 bean is using the bean ref attribute to reference the bean named parentTitle. Because this bean exists only in the parent BeanFactory. There are two points of interest here.
- First: use the bean attribute to reference beans in both the child and the parent ApplicationContexts.
- ??? Second: you can’t use the local attribute to refer to beans in the parent ApplicationContext.
The song2 bean is using the bean ref attribute to reference childTitle. Because that bean is defined in both ApplicationContexts, the song2 bean receives a reference to childTitle in its own ApplicationContext.
The song3 bean is using the <ref>
tag to reference childTitle directly in the parent ApplicationContext.
As expected, the song1 and song3 beans both get a reference to beans in the parent ApplicationContext, whereas the song2 bean gets a reference to a bean in the child ApplicationContext.
from parent ctx: Gravity
from child ctx: No Such Thing
from parent ctx: Daughters
Using the collection is simple: you choose either <list>
, <map>
, <set>
, or <props
> to represent a List, Map, Set, or Properties instance, and then you pass in the individual items just as you would with any other injection.
public class CollectionInjection {
private Map<String, Object> map;
private Properties props;
private Set set;
private List list;
// main method
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("classpath:spring/app-context-xml.xml");
ctx.refresh();
CollectionInjection instance = (CollectionInjection) ctx.getBean("injectCollection");
instance.displayInfo();
// set method
}
main()
method retrieves a CollectionInjection bean from Spring and then calls the displayInfo() method.
<bean id="injectCollection" class="com.apress.prospring5.ch3.xml.CollectionInjection">
<property name="map">
<map>
<entry key="someValue">
<value>It's a Friday, we finally made it</value>
</entry>
<entry key="someBean">
<ref bean="lyricHolder"/>
</entry>
</map>
</property>
<property name="props">
<props>
<prop key="firstName">John</prop>
<prop key="secondName">Mayer</prop>
</props>
</property>
...
injected values into all four setters exposed on the CollectionInjection class.
with the <list>
, <map>
, and <set>
elements, you can employ any of the tags used to set the value of noncollection properties to specify the value of one of the entries in the collection.
Using this functionality, it is much easier to modularize your application and provide different, userselectable implementations of key pieces of application logic.
Using Spring’s collection injection, you can create a standard interface for this functionality, as shown:
public interface ArtworkSender {
void sendArtwork(String artworkPath, Recipient recipient);
String getFriendlyName();
String getShortName();
}
From this interface, you can create multiple implementations, each of which is capable of describing itself to a human, such as the one shown here:
public class FtpArtworkSender implements ArtworkSender {
@Override
public void sendArtwork(String artworkPath, Recipient recipient) {
// ftp logic here...
}
@Override
public String getFriendlyName() { return "File Transfer Protocol"; }
@Override
public String getShortName() { return "ftp"; }
}
Imagine that you then develop an ArtworkManager class that supports all available implementations of the Artwork-Sender interface. With the implementations in place, you simply pass a List to your ArtworkManager class, and you are on your way. Using the getFriendlyName()
method, you can display a list of delivery options for the system administrator to choose from when you are configuring each stationery template.
public class ArtworkManager {
private List<ArtworkSender> senders;
public ArtworkManager(List<ArtworkSender> senders) {
this.senders = senders;
}
public void displaySenders() {
System.out.println("All Senders:\n");
for (ArtworkSender sender : senders) {
System.out.println("Sender: " + sender.getFriendlyName());
}
}
public static void main(String[] args) {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.load("classpath:sender.xml");
context.refresh();
ArtworkManager manager = (ArtworkManager) context.getBean("artworkManager");
manager.displaySenders();
}
}
- Lookup Method Injection
- Method Replacement
Lookup Method Injection provides another mechanism by which a bean can obtain one of its dependencies.
Method replacement allows you to replace the implementation of any method on a bean arbitrarily, without having to change the original source code.
Lookup Method Injection was added to Spring in version 1.1 to overcome the problems encountered when a bean depends on another bean with a different life cycle, specifically, when a singleton depends on a nonsingleton.
LockOpener
class - relies on a KeyHelper
class.
KeyHelper
class is not suitable for reuse. Every time the openLock()
method is called, a new KeyHelper instance is required.
Therefore, LockOpener
will be a singleton.
To make sure that a new instance of the KeyHelper instance is passed into the openLock()
method every time it is invoked, we need to use Lookup Method Injection.
you can achieve this by having the singleton bean implement the ApplicationContextAware
interface (we discuss this interface in the next chapter). Then, using the ApplicationContext instance, the singleton bean can look up a new instance of the nonsingleton dependency every time it needs it.
public interface DemoBean {
Singer getMySinger();
void doSomething();
}
The doSomething()
method is a simple method that depends on the Singer class to do its processing.
public class StandardLookupDemoBean implements DemoBean {
private Singer mySinger;
public void setMySinger(Singer mySinger) {
this.mySinger = mySinger;
}
@Override
public Singer getMySinger() {
return this.mySinger;
}
@Override
public void doSomething() {
mySinger.sing();
}
}
but notice that the doSomething()
method uses the stored instance of Singer to complete its processing.
Method Injection to obtain an instance of the Singer
class.
public abstract class AbstractLookupDemoBean implements DemoBean {
public abstract Singer getMySinger();
@Override
public void doSomething() {
getMySinger().sing();
}
}
<bean id="abstractLookupBean" class="com.apress.prospring5.ch3.AbstractLookupDemoBean">
<lookup-method name="getMySinger" bean="singer"/>
</bean>
For abstract-LookupBean, you need to configure the lookup method by using the <lookup-method>
tag. The name attribute of the <lookup-method>
tag tells Spring the name of the method on the bean that it should override.
DemoBean abstractBean = ctx.getBean("abstractLookupBean", DemoBean.class);
DemoBean standardBean = ctx.getBean("standardLookupBean", DemoBean.class);
displayInfo("abstractLookupBean", abstractBean); displayInfo("standardLookupBean", standardBean);
Compared to two methods, Clearly, standardLookupBean should be faster because it returns the same instance each time.
[abstractLookupBean]: Singer Instances the Same? false 100000 gets took 431 ms
[standardLookupBean]: Singer Instances the Same? true 100000 gets took 1 ms
As you can see, the Singer instances are, as expected, the same when we use standardLookupBean and different when we use abstractLookupBean
Lookup Method Injection is intended for use when you want to work with two beans of different life cycles. Avoid the temptation to use Lookup Method Injection when the beans share the same life cycle, especially if they are singletons.
When we define a bean with the singleton scope, the container creates a single instance of that bean; all requests for that bean name will return the same object, which is cached. Any modifications to the object will be reflected in all references to the bean. This scope is the default value if no other scope is specified.
Using method replacement, you can replace the implementation of any method on any beans arbitrarily without having to change the source of the bean you are modifying.
For example, change a third-party library.
Internally, you achieve this by creating a subclass of the bean class dynamically. You use CGLIB and redirect calls to the method you want to replace to another bean that implements the MethodReplacer interface.
public class ReplacementTarget {
public String formatMessage(String msg) {
return "<h1>" + msg + "</h1>";
}
public String formatMessage(Object msg) {
return "<h1>" + msg + "</h1>";
}
}
You can replace any of the methods on the ReplacementTarget class by using Spring’s method replacement functionality.
Replacing formatMessage(String)
method:
first need to create an implementation of the MethodReplacer
interface
public class FormatMessageReplacer implements MethodReplacer {
@Override
public Object reimplement(Object arg0, Method method, Object[] args)
throws Throwable {
if (isFormatMessageMethod(method)) {
String msg = (String) args[0];
return "<h2>" + msg + "</h2>";
} else {
throw new IllegalArgumentException("Unable to reimplement method "
+ method.getName());
}
}
private boolean isFormatMessageMethod(Method method) {
if (method.getParameterTypes().length != 1) {
return false;
}
if (!("formatMessage".equals(method.getName()))) {
return false;
}
if (method.getReturnType() != String.class) {
return false;
}
if (method.getParameterTypes()[0] != String.class) {
return false;
}
return true;
}
}
Method replacement can prove quite useful in a variety of circumstances, especially when you want to override only a particular method for a single bean rather than all beans of the same type.
Every bean must have at least one name that is unique within the containing ApplicationContext
.
id
attribute -> as the name
If not, Spring looks for name
attribute.
If not, Spring uses class name.
-
name
attribute
Spring allows a bean to have more than one name. You can achieve this by specifying a space-, comma-, or semicolon-separated list of names in the name attribute of the bean’s <bean>
tag.
-
alias
attribute
<beans ...>
<bean id="john" name="john johnny,jonathan;jim" class="java.lang.String"/>
<alias name="john" alias="ion"/>
</beans>
Consider the following scenario: you have an application in which 50 beans, configured using Spring, all require an implementation of the Foo interface. Twenty-five of the beans use the StandardFoo implementation with the bean name standardFoo, and the other 25 use the SuperFoo implementation with the superFoo bean name. Six months after you put the application into production, you decide to move the first 25 beans to the SuperFoo implementation.
To do this, you have three options.
The second option is to update the injection configuration for the 25 beans that are changing, which changes the beans’ names from standardFoo to superFoo. This approach is not the most elegant way to proceed. You could perform a find and replace, but then rolling back your changes when management isn’t happy means retrieving an old version of your configuration from your version control system.
The third, and most ideal, approach is to remove (or comment out) the definition for the standardFoo bean and make standardFoo an alias to superFoo. This change requires minimal effort, and restoring the system to its previous configuration is just as simple.
(Can't understand so many annotations)
By default, all beans in Spring are singletons. This means Spring maintains a single instance of the bean, all dependent objects use the same instance, and all calls to ApplicationContext.getBean()
return the same instance.
- Singleton -> Nonsingleton (Prototype)
although changing the instantiation mode of your bean won’t affect your application code, it does cause some problems if you rely on Spring’s life-cycle interfaces. We cover this in more detail in Chapter 4.
@Component("nonSingleton")
@Scope("prototype")
public class Singer {
private String name = "unknown";
public Singer(@Value("John Mayer") String name) {
this.name = name;
}
@Override public String toString() {
return name;
}
}
@Scope : The prototype scope instructs Spring to instantiate a new instance of the bean every time a bean instance is requested by the application.
In general, singletons should be used in the following scenarios:
- Shared object with no state
- Shared object with read-only state
- Shared object with shared state
- High-throughput objects with writable state
Using nonsingletons in the following scenarios:
- Objects with writable state
- Objects with private state
- Singleton
- Prototype
- Request: web application.
- Session: web application
- Global session: portlet-based web applications
- Thread: A new bean instance will be created by Spring when requested by a new thread.
- Custom
During normal operation, Spring is able to resolve dependencies by simply looking at your configuration file or annotations in your classes.
Unfortunately, Spring is not aware of any dependencies that exist between beans in your code that are not specified in the configuration. Spring maybe unaware to inject the dependencies, but simply get an instance of the class.
Configuration
<beans ...>
<bean id="johnMayer" class="com.apress.prospring5.ch3.xml.Singer" depends-on="gopher"/>
<bean id="gopher" class="com.apress.prospring5.ch3.xml.Guitar"/>
</beans>
depends-on: Spring should take this into consideration when instantiating the beans and ensure that gopher is created before johnMayer.
To do this, though, johnMayer needs to access ApplicationContext. Thus, we also have to tell Spring to inject this reference, so when the johnMayer.sing() method will be called, it can be used to procure the gopher bean.
This is done by making the Singer bean implement the ApplicationContextAware interface.
@Component("johnMayer")
@DependsOn("gopher")
public class Singer implements ApplicationContextAware{
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
private Guitar guitar;
public Singer(){
}
public void sing() {
guitar = applicationContext.getBean("gopher", Guitar.class);
guitar.sing();
}
}
@DependsOn annotation
Autowiring feature of spring framework enables you to inject the object dependency implicitly. It internally uses setter or constructor injection.
It requires the less code because we don't need to write the code to inject the dependency explicitly.
- byName: The byName mode injects the object dependency according to name of the bean. In such case, property name and bean name must be same. It internally calls setter method.
- byType: The byType mode injects the object dependency according to type. So property name and bean name can be different. It internally calls setter method.
- constructor: This functions just like byType wiring, except that it uses constructors rather than setters to perform the injection. Spring attempts to match the greatest numbers of arguments it can in the constructor.
- no: default
-
property name and the bean name must be the same
-
setter method
Actually, the property name and the bean name do not need to be the same, but the setter method's name which defines the property needs to be exactly the same as the bean's name, except the 'set' word. For example,
<bean id="barOne" .../>
and public void setBarOne(Bar bar)
needs to be the same. 除了set和第一个大写字母。
- The byType mode injects the object dependency according to type. So property name and bean name can be different.
- It internally calls setter method.
It works fine, but if more than one bean of the same type is available in the container, the framework will throw a fatal exception.
To resolve this conflict, we need to tell Spring explicitly which bean we want to inject.
- First is to use
primary
attribute. But it is a solution only when there are just two bean-related types. - Second is use @Qualifier
In some cases, autowiring can save you time, but it does not really take that much extra effort to define your wiring explicitly, and you benefit from explicit semantics and full flexibility on property naming and on how many instances of the same type you manage.
In all, suggest no.
In some cases, you may need multiple definitions of beans that are the same type or implement a shared interface.
Spring allows you to provide a <bean>
definition that inherits its property settings from another bean in the same ApplicationContext instance.
<bean id="parent" class="com.apress.prospring5.ch3.xml.Singer"
p:name="John Mayer" p:age="39" />
<bean id="child" class="com.apress.prospring5.ch3.xml.Singer"
parent="parent" p:age="0"/>
main
method
Singer parent = (Singer) ctx.getBean("parent");
Singer child = (Singer) ctx.getBean("child");
Result:
Parent:
Name: John Mayer Age: 39
Child:
Name: John Mayer Age: 0
As expected, the inheritChild
bean inherited the value for its name property from the inheritParent
bean but was able to provide its own value for the age property.