I'm new to spring.
I've a rulefactory, which would return an instance from a static method
based on the type value
Now i'll get the type from the main methods, argument.
Now i would like to pass the argument type to the factory method getInstance
type argument.
how to do that.
/* Factory class, getInstance will return a subtype of RuleEvaluation, for simplicity, i've not
provided the Implementation class for SingleRuleEvaluation and MassRuleEvaluation. Basically both the classes implements RuleEvaluation */
public class RuleEvalFactory {
public static RuleEvaluation getInstance(String type) {
if (type != null && type.equals("Single")) {
return new SingleRuleEvaluation();
} else if (type != null && type.equals("mass")) {
return new MassRuleEvaluation();
}
return null;
}
}
/* My Main class , i need to get an instance of the RuleEvaluation here based on the type(dyamic)
dont know how to do it.
*/
public class MyApp {
public static void main(String args[]) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Spring-All-Module.xml");
String type = args[0];
/* i want to pass the above type String to the factory method and get the instance how to do that */
RuleEvaluation re = (HarmonyService) context.getBean("rulefactory") ;
}
}
/* my Spring xml configuration file */
Spring 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">
<bean id="instanceMethodFactory" class="test.factory.RuleEvalFactory"> </bean>
<!-- i dont know how to pass the dynamic type from the Myapp main
method into this constructory argument -->
<bean id="rulefactory" factory-bean="instanceMethodFactory" factory-method="getInstance">
<constructor-arg index="0"> </constructor-arg>
</bean>
</beans>
Please give the code in Spring xml and Myapp main method how to inject the type into the factory method's getInstance.
Regards,
Raghu
You need to specify in the bean the constructor argument,
<bean id="myBean" class="A" scope="prototype">
<constructor-arg value="0"/> <!-- dummy value -->
</bean>
And then pass the value to bean factory,
getBean("myBean", argument);
Related
I am trying to inject boolean property from property file. the value of the attribute is alway false
the property
use.virtual.wallet=true
The xml configuration
<bean id="proxyUtil" class="com.util.ProxyServiceUtility">
<property name="useVirtualWallet" value="${use.virtual.wallet}" />
</bean>
the bean
public class ProxyServiceUtility {
private boolean useVirtualWallet;
public void setUseVirtualWallet(boolean useVirtualWallet) {
this.useVirtualWallet = useVirtualWallet;
}
public boolean isUseVirtualWallet() {
return useVirtualWallet;
}
}
useVirtualWallet is alway false
You have to load your properties file into Spring context using PropertyPlaceholderConfigurer.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
The problem fixed using this workaround, instead of injecting boolean, I injected String and then converted that String to boolean on the setter
public void setUseVirtualWallet(String useVirtualWallet) {
this.useVirtualWallet = Boolean.parseBoolean(useVirtualWallet);
}
Another variant
<beans
xmlns:context="http://www.springframework.org/schema/context">
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
...
<beans>
I have a controller whose response is camelCase json value. Now we are re-writing the code with new version and the response required is in snake_case.
I have added a message converter and modified object mapper to setsetPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
public class ResponseJSONConverter extends MappingJackson2HttpMessageConverter {
#Autowired
public ResponseJSONConverter(ObjectMapper objectMapper) {
setObjectMapper(objectMapper);
}
}
I have registered this convertor with spring and its working as expected. Now I want my old endpoints to return in camelCase for backward compatibility for my consumers and new endpoints with snake_case.
I have tried to have one more message convertor with simple object mapper without setting camelCase to Snake case property and registered with spring. Only one message convertor gets applied based on the order declared in the spring configuration.
Is there any way we can achieve this ? Loading message convertor based on the condition ?
EDIT
Added my spring config file
<beans xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<bean id="moneySerializer" class="api.serialize.MoneySerializer"/>
<bean id="moneyDeserializer" class="api.serialize.MoneyDeserializer"/>
<bean id="serializationModule" class="api.serialize.SerializationModule">
<constructor-arg index="0" ref="moneySerializer"/>
<constructor-arg index="1" ref="moneyDeserializer"/>
</bean>
<bean id="customObjectMapper" class="api.serialize.CustomObjectMapper" primary="true">
<constructor-arg ref="serializationModule"/>
</bean>
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="api.serialize.ResponseJSONConverterCamelCaseToSnakeCase" >
<constructor-arg ref="customObjectMapper"/>
</bean>
<bean class="api.serialize.ResponseJSONConverter">
<constructor-arg ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
</beans>
EDIT 2.0
my servlet.xml
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.tgt.promotions.api.serialize.ServiceJSONConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
CustomMessageConverter
public class ServiceJSONConverter extends MappingJackson2HttpMessageConverter {
#Autowired
public ServiceJSONConverter(SnakeCaseObjectMapper snakeCaseObjectMapper) {
setObjectMapper(snakeCaseObjectMapper);
}
}
Custom Object Mapper
#Component
public class SnakeCaseObjectMapper extends ObjectMapper {
#Autowired
public SnakeCaseObjectMapper(PropertyNamingStrategy propertyNamingStrategy) {
setSerializationInclusion(JsonInclude.Include.NON_NULL);
setPropertyNamingStrategy(propertyNamingStrategy);
}
}
Custom Property Naming Strategy
#Component
public class CustomPropertyNamingStrategy extends PropertyNamingStrategy {
#Autowired
private HttpServletRequest request;
private final PropertyNamingStrategy legacyStrategy = PropertyNamingStrategy.LOWER_CASE;
private final PropertyNamingStrategy defaultStrategy = PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES;
#Override
public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
return getStrategy().nameForConstructorParameter(config, ctorParam, defaultName);
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return getStrategy().nameForField(config, field, defaultName);
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return getStrategy().nameForGetterMethod(config, method, defaultName);
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return getStrategy().nameForSetterMethod(config, method, defaultName);
}
private PropertyNamingStrategy getStrategy() {
if (isLegacyEndpoint(request)) {
return legacyStrategy;
} else {
return defaultStrategy;
}
}
private boolean isLegacyEndpoint(HttpServletRequest request) {
return request != null && request.getRequestURL() != null && !request.getRequestURL().toString().contains("/v3");
}
}
Instead of having 2 different object-mappers, I suggest creating a custom implementation of PropertyNamingStrategy, using the 2 other strategies accordingly:
public class AwesomePropertyNamingStrategy extends PropertyNamingStrategy {
private PropertyNamingStrategy legacyStrategy = PropertyNamingStrategy.LOWER_CASE;
private PropertyNamingStrategy defaultStrategy = PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES;
#Override
public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
return getStrategy().nameForConstructorParameter(config, ctorParam, defaultName);
}
// TODO: implement other nameForXXX methods
private PropertyNamingStrategy getStrategy() {
if (isLegacyEndpoint()) {
return legacyStrategy;
} else {
return defaultStrategy;
}
}
private boolean isLegacyEndpoint() {
// TODO: get hold of the RequestContext or some other thead-local context
// that allows you to know it's an old or a new endpoint
return false;
}
}
You should come up with a way to toggle between legacy and new mode:
Using the endpoint URL by accessing the request context in some way
In case your old endpoint use different response objects, use the class of the object that is being converted to determine legacy/normal or your own custom #LegacyResponse annotation on all old classes instead.
Well, nothing worked after many attempts. Finally ended up defining 2 different servlets. one without any version and one with v1 version.
web.xml
<servlet>
<servlet-name>snake-case</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>snake-case</servlet-name>
<url-pattern>/v1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>camel-case</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>camel-case</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Accordingly defined two servlets snake-case-servlet.xml and camel-case-servlet.xml.
snake-case-servlet.xml
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.tgt.promotions.api.serialize.DataJSONConverter">
<constructor-arg ref="snakeCaseObjectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
camel-case-servlet.xml
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.tgt.promotions.api.serialize.DataJSONConverter">
<constructor-arg ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
Now, for any requests with /v1* , snakeCaseObjectMapper is used and for other requests default object mapper is used.
I have some error during the invocation of a bean inside an Aspect, and I also would like to have some suggestions about initialization of beans inside an aspect class.
I am trying to invoke a bean [2] inside my aspects class [1], but in the end I get the following error [4]. It seems that the #Autowired parameter didn't initialize the bean. My configuration file is in [3]. What am I doing wrong?
I want that the bean will be invoked one time during initialization. So, even the map function is called multiple times, the bean is already initialized.
Another solution is, I want (I don't know if it is possible), that the bean will be initialized when the pointcut map or reduce is invoked by the first time.
Are these requests possible to do?
Thanks,
[1] My aspect class
#Aspect
#Configurable
#Component
public class MapReduceAspects {
#Autowired
MedusaDigests digests;
#Before("execution(* map(..))")
public void mymap(JoinPoint joinPoint) {
System.out.println("My Map Execution: " + joinPoint.getArgs() + ":" + joinPoint.getTarget());
Object[] obj = joinPoint.getArgs();
if (obj.length > 0) {
byte[] key = convertToBytes(obj[0]);
byte[] value = convertToBytes(obj[1]);
digests.updateDigest(key, value);
}
}
#Before("execution(* reduce(..))")
public void myreduce(JoinPoint joinPoint) { System.out.println("My Reduce Execution");
Object[] obj = joinPoint.getArgs();
if (obj.length > 0) {
byte[] key = convertToBytes(obj[0]);
byte[] value = convertToBytes(obj[1]);
digests.updateDigest(key, value);
}
}
}
[1] My aspect
#Component
public class MedusaDigests {
private final String MD5 = "MD5";
private MessageDigest mda = null;
public MedusaDigests() {
try {
mda = MessageDigest.getInstance(MD5);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void updateDigest(byte[] key, byte[] value) {
mda.update(key);
mda.update(value);
}
}
[3] My beans-aspects.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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--<aop:include name="mapreduceAspect"/>-->
<!--<aop:include name="jobClient"/>-->
<!--</aop:aspectj-autoproxy>-->
<!--<context:load-time-weaver/>-->
<context:component-scan base-package="org.apache.hadoop.mapred"/>
<bean id="mapreduceAspect" class="org.apache.hadoop.mapred.aspects.MapReduceAspects" factory-method="aspectOf" autowire="byType" />
<bean id="medusaDigests" class="org.apache.hadoop.mapred.MedusaDigests"/>
[4] the error that I get
WARNING: job_local138234703_0001
java.lang.Exception: java.lang.NullPointerException at org.apache.hadoop.mapred.LocalJobRunner$Job.runTasks(LocalJobRunner.java:462)
at org.apache.hadoop.mapred.LocalJobRunner$Job.run(LocalJobRunner.java:522)
Caused by: java.lang.NullPointerException
at org.apache.hadoop.mapred.aspects.MapReduceAspects.mymap(MapReduceAspects.java:50)
at org.apache.hadoop.mapred.examples.DummyWordCount$Map.map(DummyWordCount.java:32)
at org.apache.hadoop.mapred.examples.DummyWordCount$Map.map(DummyWordCount.java:1)
at org.apache.hadoop.mapreduce.Mapper.run(Mapper.java:146)
at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:787)
at org.apache.hadoop.mapred.MapTask.run(MapTask.java:341)
at org.apache.hadoop.mapred.LocalJobRunner$Job$MapTaskRunnable.run(LocalJobRunner.java:243)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
In the first you don't need declare your beans at the xml configuration and using annotation like this:
<bean id="medusaDigests" class="org.apache.hadoop.mapred.MedusaDigests"/>
#Component
public class MedusaDigests {...}
You must declare bean only in one place, for example you can remove tags:
<bean id="mapreduceAspect" class="org.apache.hadoop.mapred.aspects.MapReduceAspects" factory-method="aspectOf" autowire="byType" />
<bean id="medusaDigests" class="org.apache.hadoop.mapred.MedusaDigests"/>
Secondly, tag
<context:component-scan base-package="org.apache.hadoop.mapred"/>
does not scan Autowired annotation, if also you want scan #Autowired annotation you can choose one of two variants:
declare in xml configuration
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
or
declare in xml configuration
<context:annotation-config/>
I have a simple example program here:
package com.test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class Main {
public static void main(String[] args) throws InterruptedException {
try {
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("TestBeanFile.xml");
Object o = context.getBean("AInstance");
}
catch (Throwable e) {
e.printStackTrace();
}
Thread.sleep(Long.MAX_VALUE);
}
private static class A implements InitializingBean {
private B myB;
public A() {
System.out.println("A constructor");
}
public void setB(B aB) {
myB = aB;
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("A aps");
}
}
private static class B implements Runnable, InitializingBean {
private C myC;
private volatile boolean exit = false;
public B() {
System.out.println("B constructor");
}
public void setC(C aC) {
myC = aC;
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("B aps");
if (myC == null) throw new IllegalArgumentException("C cannot be null");
new Thread(this).start();
}
public void exit() {
exit = true;
}
#Override
public void run() {
while (!exit) {
System.out.println(myC.getValue());
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private static class C implements InitializingBean {
private String value = "C's value";
public C() {
System.out.println("C constructor");
}
public String getValue() {
return value;
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("C aps");
}
}
}
And here is a simple XML to bean load them which intentionally has an incorrect fully qualified name to the A class. This should mean that C B and A dont get constructed.
<?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:int="http://www.springframework.org/schema/integration"
xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.1.xsd">
<bean id="CInstance" class = "com.test.Main.C" />
<bean id="BInstance" class = "com.test.Main.B">
<property name="C" ref="CInstance" />
</bean>
<bean id="AInstance" class = "com.test.Main.A1">
<property name="B" ref="BInstance"/>
</bean>
</beans>
When you run this application using the above XML C and B DO get constructed but A doesnt. It will produce the following output (omitting the stack trace when the exception is thrown, but I assure you A does not get constructed):
C constructor
C aps
B constructor
B aps
C's value
C's value
C's value
....... // one a second
If you modify the XML to look like this:
<?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:int="http://www.springframework.org/schema/integration"
xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.1.xsd">
<bean id="AInstance" class = "com.test.Main.A1">
<property name="B" ref="BInstance"/>
</bean>
<bean id="BInstance" class = "com.test.Main.B">
<property name="C" ref="CInstance" />
</bean>
<bean id="CInstance" class = "com.test.Main.C" />
</beans>
The only output you get is the stack trace, and the B and C objects dont get constructed at all.
This seems like its a bug in spring. I dont understand why the marshaling of the objects, and the constructors / afterpropertiesSet methods which are run depend on the order of their appearance in the XML. What this means for me, is that when I have classes which construct threads in their constructors / after properties set methods, if I'm not careful about how I order the elements in the XML I can end up leaking resources if my only reference to them is, in this example, the A class. Because it fails construction in both cases.
So, is this a bug, or is this some feature that I dont understand? If this is a feature, what reason would they have to make the order of marshaling dependent on the XML file, instead of the object graph of the bean definitions?
Spring first reads the <bean> and other elements in the XML configuration and creates appropriate BeanDefinition objects defining them.
It then has to initialize them. To do this, it has to decide on an order. The order of initialization is undefined, unless a bean depends on another or there is a mix of #Order, implementations of Ordered and a few others (depends on the case).
In your first example, CInstance is initialized first. Then BInstance is initialized. Part of its initialization involves invoking its afterPropertiesSet() method which launches a non-daemon thread. Then Spring attempts to initialize AInstance and fails.
In your second example, Spring attempts to initialize AInstance first. It fails immediately, with no change to start a second thread.
Note that in a declaration like
<bean id="AInstance" class="com.example.Spring.A1">
<property name="B" ref="BInstance" />
</bean>
although AInstance depends on BInstance, the actual instance needs to be initialized before any property setters can be invoked to assign BInstance to B. So Spring begins the initialization of AInstance by instantiating the class. If it fails, the entirety of context refresh fails. If it passes, it will then initialize the BInstance bean and use it to set the B property.
I am developing RESTful API for my application. All getters (that use HTTP GET) work fine. I cannot make save method (that uses POST) to work.
I am using HTML form and RESTClient for testing.
Here is my Controller
#Controller
public class EntitiesController {
#RequestMapping(value="/ci/save/", method = RequestMethod.POST)
public ModelAndView saveConfigurationItem(#RequestBody ConfigurationItem body) {
System.out.println("saveConfigurationItem: body=" + body);
return createModelAndView("ci", Collections.emptyList());
}
}
This method is expected to be called when client posts ConfigurationItem.
I am using custom serialization format. It is not XML or JSON. It is VCard or VCalendar format. For my first test I used the following VCard:
BEGIN:VCARD
N:Pooh;Winnie
FN:Winnie the Pooh
TEL:tel:+441234567
END:VCARD
I posted it to URL http://localhost:8080/core.solution-1.0/data/ci/save/.
Here is the response I get:
415
The server refused this request because the request entity is in a format not
supported by the requested resource for the requested method ()
(*) ConfigurationItem is an abstract class. CardEntry extends it. I tried both.
I tried to change the method parameter to String. In this case the method is called but the string is empty. The same happens when following one of recommendations I saw in web I changed the parameter type to MultiValueMap and sent request from simple HTML form.
I saw that marshal() is not called at all.
What's wrong?
Here is what I have. (I put here relevant code only.)
Spring 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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd">
<import resource="classes/spring-config-prod.xml"/>
<context:component-scan base-package="com.mycompany.solution.service" />
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="ciCardView" class="com.mycompany.solution.service.VFormatView">
<constructor-arg>
<bean class="com.mycompany.solution.service.VFormatMarshaller">
<property name="packagesToScan" value="com.mycompany.solution.entity"/>
</bean>
</constructor-arg>
</bean>
</beans>
Marshaller
public class VFormatMarshaller implements Marshaller, Unmarshaller {
#Override
public void marshal(Object obj, Result result)
throws IOException/*, XmlMappingException*/ {
System.out.println("VFormatMarshaller.marshal(" + obj + ")");
marshalStreamResult(obj, (StreamResult)result);
}
#Override
public boolean supports(Class<?> paramClass) {
System.out.println("VFormatMarshaller.supports(" + paramClass + ")");
boolean supports = new HashSet<String>(Arrays.asList(packagesToScan)).contains(paramClass.getPackage().getName());
if (supports) {
return supports;
}
return Collection.class.isAssignableFrom(paramClass);
}
#Override
public Object unmarshal(Source source) throws IOException/*, XmlMappingException*/ {
System.out.println("VFormatMarshaller.unmarshal(" + source + ")");
return unmarshalStreamSource((StreamSource)source);
}
//// .............................
}
View (this is written only to override the content type)
public class VFormatView extends MarshallingView {
public VFormatView() {
super();
setContentType("application/vcard");
System.out.println("VFormatView()");
}
public VFormatView(Marshaller marshaller) {
super(marshaller);
setContentType("application/vcard");
System.out.println("VFormatView(" + marshaller + ")");
}
}
#RequestBody/#ResponseBody are supported by an hierarchy of HttpMessageConverters, that is completely different from ViewResolvers.
In you case you need to configure a MarshallingHttpMessageConverter with appropriate marshaller/unmarshaller and content type (or create your own HttpMessageConverter if you don't need to depend on the existing implementation of marshaller/unmarshaller), and supply a configured instance to AnnotationMethodHandlerAdapter.
The least intrusive way to configure a custom HttpMessageConveter is to create a BeanPostProcessor as follows:
public class Configurer implements BeanPostProcessor {
public void postProcessAfterInitialization(String name, Object bean) {
if (bean instanceof AnnotationMethodHandlerAdapter) {
AnnotationMethodHandlerAdapter a = (AnnotationMethodHandlerAdapter) bean;
HttpMessageConverter[] convs = a.getMessageConverters();
... add new converter ...
a.setMessageConverters(convs);
}
}
...
}