Spring managed components
interoperability with JPA/JAXB or others
As Spring is a de-facto standard in creating JEE
application, one problem faced commonly is interoperability between Spring managed beans and objects created by
other runtimes like JPA or JAXB. As most
of the application is using JPA or JAXB we will look into ways to use Spring
beans in other objects and injecting other objects in Spring beans. In This
article I will refer objects created by other runtime like JAXB or JPA as
external object and the classes as external class.
Configuring
bean programmatically in Java class:
For configuring beans programmatically
Spring provided their <beans> and <bean> equivalent in Java.
package
interop.config.test;
import
org.springframework.beans.factory.annotation.Value;
import
org.springframework.context.annotation.Bean;
import
org.springframework.context.annotation.Configuration;
@Configuration
public
class
ConfigurationClass {
class SampleBean{
}
@Bean(name="sample")
SampleBean
getBean(@Value("#{systemProperties['user.dir']}")String
param, ServiceClass service){
return new SampleBean();
}
}
1. Annotate the class with @Configuration
2. Annotate method for creating
beans with @Bean
Spring will execute this method
and register the returned object as bean with name as method name.
Parameters passed into these
creator methods are of type any Spring bean or annotated as @Value. If this
method requires other objects, then call other bean methods and those returned
beans will be available to it.
@Configuration is equivalent to XML configuration and @Bean is equivalent to <bean/>
and has all configuration options like Scope, Name etc are available.
Other useful annotations:
@ImportResource – Allows us to import a spring xml file or other
@Configuration class for importing beans and resource like XML configuration.
@Value – Use SpEL expressions to inject properties or other
bean attributes.
This class
must be under component scan.
For more details:
Injecting
Spring components (beans) into external classes (instantiated using new
operator):
This is useful in using Spring components/services in external classes
instantiated using new operator. Spring intercepts the autowired beans in the
Configurable class and new calls for the class. There are several strategies for
weaving those classes like weaving at compile or load time. Look resource #1.
This can be done using @Configurable class.
This needs following jars:
cglib-2.2.2.jar
asm-all-3.3.1.jar
aspectjrt-1.6.9.jar
aspectjweaver-1.6.9.jar
org.springframework.aspects-<VERSION>.jar
org.springframework.instrument-<VERSION>.jar
Add following in applicatioContext.xml
<context:spring-configured
/>
<context:load-time-weaver
/>
Run application with VM argument:
-javaagent:lib/org.springframework.instrument-<VERSION>.jar
Sample Code:
@Configurable class:
package
interop.config.test;
import
interop.config.test.ConfigurationClass.SampleBean;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.beans.factory.annotation.Configurable;
@Configurable
public
class
ConfigurableClass {
@Autowired
SampleBean
sampleBean;
public SampleBean getSampleBean() {
return sampleBean;
}
}
applicationContext.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"
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-3.0.xsd">
<context:component-scan base-package="interop.config.test" />
<context:spring-configured />
<context:load-time-weaver />
</beans>
@Configuration class to create
the autowired bean is the class described in previous topic.
Issues with @Configurable
Resources:
Injecting
external objects in Spring context to use them in Spring components:
Adding objects to application
context [singletons]:
Singletons can be added the way
described in resources listed #1 and dynamic bean definitions can be added as
listed in Resources #2.
Sample code for registering singleton
in ApplicationContext:
public class ConfigTest {
public static void main(String[] args) {
ConfigurableApplicationContext
context = new ClassPathXmlApplicationContext("/applicationContext.xml");
//registering singleton
context.stop();
ConfigurableListableBeanFactory
factory = context.getBeanFactory();
factory.registerSingleton("auto", new Auto("fgfdg", "fgfd"));
context.start();
System.out.println(Arrays.asList(context.getBean("auto")));
}
static class Auto{
String a;
String b;
public Auto(String a, String b) {
this.a=a;
this.b=b;
}
}
}
Now how to register beans in
ptototype scope dynamically?
Use Case:
- Application is
receiving data from external API (REST or Web Services) in XML format.
- JAXB populates
object graph from XML.
- Application has
@Service (s) which needs this object graph populated by JAXB. For example,
populating data transfer object (DTO) by using SpEL (@Value annotated
fields) or the @Service consuming this object graph by any other purpose.
How to make this object graph available to those
services (Spring components)?
First of all such Service need to of scope
prototype as it depends on data retrieved from external sources (will be
different every time, so objects state depends on this data. This object is
stateful). A Service can be singleton which is using that data only in a method
and by this object graph as argument, but in scenario like populating DTO using
SpEL where fields of class is annotated with @Value(<expression>). We
will look into prototype scoped bean.
Scope is not necessarily be prototype, can be
changed according to requirements.
We need
following things:
- A Spring
component bean in which we will store our object. This component will be
singleton and will be in application context already. This will be
annotated with @Component and will have fields for holding data.
package
interop.config.test;
import
java.util.Map;
import
java.util.TreeMap;
import
org.springframework.stereotype.Component;
@Component
public
class
ExternalObjectContainer {
public
Map<String, Object> container
= new
TreeMap<String, Object>();
public
void
addObject(String key, Object value){
container.put(key, value);
}
public
Object getObject(String key){
return container.get(key);
}
}
In
this class currently there is only one field of type Map for holding data. This
field can be of Object type or some specific JAXB root class type. Also, for
concurrent access or storing more objects, data structure and storage methods
can be changed as per requirement.
External class:
package
interop.external;
public
class Root {
public
Customer customer;
public
Order order;
public
Object object;
public
class Customer{
public
String name;
public
Customer(String name) {
this.name=name;
}
}
public
class Order{
public
int quantity;
public
Order(int
quantity) {
this.quantity=quantity;
}
}
}
- The requester of
the external API will set the object graph, populated from response
received to the singleton component, created in previous step. This needs
reference to the component. This reference can be acquired by autowiring
the bean to this requester. If this requester is not a spring component
then make this @Configurable.
package
interop.config.test;
import
interop.external.Root;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.context.ApplicationContext;
import
org.springframework.stereotype.Service;
@Service
public
class
ServiceClass {
@Autowired
ExternalObjectContainer
container;
@Autowired
ApplicationContext
context;
public void service(){
/*
* In real this object will be created by data
received from external sources, e.g.
* ReST API response XML converted to object
model by JAXB.
*/
Root
root = new
Root();
root.customer=root.new Customer("customeR");
root.order=root.new Order(9);
//the container we created has Map to store objects, so put any
random key.
//this depends on storage model.
container.addObject("randomKey", root);
/*
* request the consumer of this data, prototype
scoped bean from application context.
* This will create new object with all mapped
fields populated from external object.
* Cannot autowire because this will
instantiate on creation of this service class and will
* not have data or may even fail to inject
required dependency.
*/
ExternalObjectConsumerComponent
component = (ExternalObjectConsumerComponent) context.getBean("externalObjectConsumerComponent");
//check data populated in the bean
System.out.println(component);
//Doing the same thing with non Spring component, a configurable
class.
ExternalObjectConsumerConfigurable
configurable = new
ExternalObjectConsumerConfigurable();
System.out.println(configurable);
}
}
- The @Service
class which will populate the DTO from this object graph. This will be a
prototype scoped bean as this will have fields annotated with
@Value(<SpEL expression>). This is the actual DTO. This bean will be
requested only after step #2 is done. Obviously, DTO can be populated only
if one have the required data.
One can make this singleton by moving these annotations to
other class.
Using this injected external object in Spring
component:
package
interop.config.test;
import
org.springframework.beans.factory.annotation.Value;
import
org.springframework.context.annotation.Scope;
import
org.springframework.stereotype.Service;
@Service
@Scope("prototype")
public
class
ExternalObjectConsumerComponent {
@Value("#{externalObjectContainer.container.get('randomKey').customer.name}")
String
name;
@Value("#{externalObjectContainer.container.get('randomKey').order.quantity}")
int qty;
@Override
public String toString() {
return super.toString()+"{Name : "+name+",
Quantity : "+qty+"}";
}
}
Using this injected external object in external
object:
package
interop.config.test;
import
javax.annotation.PostConstruct;
import
interop.external.Root;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.beans.factory.annotation.Configurable;
/**
*This example is for using external object
injected in Spring component in a non Spring/external object.
*
*This will be useful in using external object
in external object but in flow there is Spring services in between.
*/
@Configurable
public
class
ExternalObjectConsumerConfigurable {
@Autowired
ExternalObjectContainer
container;
String
name;
int qty;
/*
*
Rather than using SpEL the data can be accessed directly from the bean.
*
Put this initialization logic in in method annotated with PostConstruct to
populate data
*
just after initializing object. This is just an alternative for SpEL and
nothing to do with
*
it is Spring component or not.
*/
@PostConstruct
public void mapProperties(){
Root
root = (Root) container.getObject("randomKey");
name=root.customer.name;
qty=root.order.quantity;
}
@Override
public String toString() {
return super.toString()+"{Name : "+name+",
Quantity : "+qty+"}";
}
}
Resources:
Testing all this:
import
interop.config.test.ConfigurableClass;
import
interop.config.test.ServiceClass;
import
org.springframework.context.ApplicationContext;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public
class Main {
public static void main(String[] args) {
ApplicationContext
context = new
ClassPathXmlApplicationContext("/applicationContext.xml");
System.out.println("\n\n\n");
//check sample bean is configured through @Configuration class
System.out.println(context.getBean("sample"));
System.out.println("\n\n\n");
//check Spring components injected in external objects through
@Configurable class
ConfigurableClass
configurable = new
ConfigurableClass();
System.out.println("\n\n\n");
System.out.println(configurable.getSampleBean());
System.out.println("\n\n\n");
//Test injecting external objects into Spring components
ServiceClass
service = (ServiceClass)context.getBean("serviceClass");
service.service();
System.out.println("\n\n\n");
}
}
Running this with java agent:
In Eclipse configure Run
Configuration like this:
Project overview:
Injecting prototype scoped bean into Singleton bean such that every time singleton bean is requested it will have new instance of prototype scoped bean.
It can be achieved using proxy scoped bean. In @Scope annotation parameter set proxyMode parameter. For details look.
Alternatively, method injection can be used.
Get project code
and add Spring framework libraries as in eclipse projects classpath.