Spring init-method params - spring

I am new to spring and I wanted to ask whether or not it is possible to pass params to the init and destroy methods of a bean.
Thanks.

No, you can't. If you need parameters, you will have to inject them as fields beforehand.
Sample Bean
public class Foo{
#Autowired
private Bar bar;
public void init(){
bar.doSomething();
}
}
Sample XML:
<bean class="Foo" init-method="init" />

This method is especially useful when you cannot change the class you are trying to create like in the previous answer but you are rather working with an API and must use the provided bean as it is.
You could always create a class (MyObjectFactory) that implements FactoryBean and inside the getObject() method you should write :
#Autowired
private MyReferenceObject myRef;
public Object getObject()
{
MyObject myObj = new MyObject();
myObj.init(myRef);
return myObj;
}
And in the spring context.xml you would have a simple :
<bean id="myObject" class="MyObjectFactory"/>

protected void invokeCustomInitMethod(String beanName, Object bean, String initMethodName)
throws Throwable {
if (logger.isDebugEnabled()) {
logger.debug("Invoking custom init method '" + initMethodName +
"' on bean with beanName '" + beanName + "'");
}
try {
Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
if (initMethod == null) {
throw new NoSuchMethodException("Couldn't find an init method named '" + initMethodName +
"' on bean with name '" + beanName + "'");
}
if (!Modifier.isPublic(initMethod.getModifiers())) {
initMethod.setAccessible(true);
}
initMethod.invoke(bean, (Object[]) null);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
see spring soruce code in Method initMethod = BeanUtils.findMethod(bean.getClass(), initMethodName, null);
the init method is find and param is null

You cannot pass params to init-method but you can still achieve the same effect using this way:
<bean id="beanToInitialize" class="com.xyz.Test"/>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="beanToInitialize" />
<property name="targetMethod" value="init"/> <!-- you can use any name -->
<property name="arguments" ref="parameter" /> <!-- reference to init parameter, can be value as well -->
</bean>
Note: you can also pass multiple arguments as a list using this
<property name="arguments">
<list>
<ref local="param1" />
<ref local="param2" />
</list>
</property>

Related

Bean property 'feedId' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

Below is code Batch snippet:
XML :
</beans:property> -->
<beans:bean id="RDFieldSetMapper" class="in.gov.tds.batch.mapper.RDFieldSetMapper"
autowire="byName" scope="step">
<!-- <beans:property name="feedId" value="429717"></beans:property> -->
<beans:property name="feedId" value="#{jobParameters[feedId]}"></beans:property>
</beans:bean>
setter method in Java Class:
recordDetail.setFeedId(new Long(feedId));
Please provide the resolution as I am getting Invalid setter method.
More Mapper detail:
public class RDFieldSetMapper implements FieldSetMapper {
private Long feedId;
private int batchCounter;
#Override
public RecordDetail mapFieldSet(FieldSet fieldSet) throws BindException {
if (LOGGER.isDebugEnabled())
LOGGER.debug("Record Detail Mapper:-- " + " " + fieldSet);
RecordDetail recordDetail = new RecordDetail();
// feedId = FeedReader.feedId;
recordDetail.setFeedId(new Long(feedId));
}
}
solved the problem. Issue : setter and getter methods are not present in the mapper class.
public Long getFeedId() {
return feedId;
}
public void setFeedId(Long feedId) {
this.feedId = feedId;
}

spring mvc add common fields in json response

Before the result is sent, we want to add common fields (timestamp, version etc) in JSON response. We don't want to do this in every Controller. Is there any elegant way to do this in spring mvc?
Another similar question is if the parameter validation is failed, how to return the same JSON response.
Yes there is. You can use Spring AOP. You can intercept every service you write and add parameters from one place. For example, using Spring Around Advice. Note you need to write yourself addParameters function that return JsonNode. Good luck!
public class DoAroundMethod implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(DoAroundMethod.class);
#Autowired
ObjectMapper mapper;
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
LOG.debug("****SPRING AOP**** DoAroundMethod: Method name : "
+ methodInvocation.getMethod().getName());
LOG.debug("****SPRING AOP**** DoAroundMethod: Method name : "
+ methodInvocation.getMethod().getName());
LOG.debug("****SPRING AOP**** DoAroundMethod: Method arguments : "
+ Arrays.toString(methodInvocation.getArguments()));
// same with MethodBeforeAdvice
LOG.debug("****SPRING AOP**** DoAroundMethod: Before method executing!");
try {
// proceed to original method call
Object result = methodInvocation.proceed();
// same with AfterReturningAdvice
if(result!=null){
//LOG.debug("Return value "+result.toString());
try{
JsonNode jN = mapper.readTree(result.toString());
result=addParameters(jN);
}catch(JsonParseException e){
LOG.debug("****SPRING AOP**** DoAroundMethod: When JsonParse throws Exception!");
return result;
}
}
LOG.debug("****SPRING AOP**** DoAroundMethod: After method executing!");
return result;
} catch (IllegalArgumentException e) {
// same with ThrowsAdvice
LOG.debug("****SPRING AOP**** DoAroundMethod: When method throws Exception!");
throw e;
}
}
Then to assign this Advice to all services you have, assuming they all end in *Service.java
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Service</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>regexAdvisor</value>
</list>
</property>
</bean>
Maybe a custom Jackson plugin, or a ResponseBodyAdvice? (see this blog post for more details).

Getting ClassCastException error

I have two classes ClientLogic1 and WelcomeBean1 as follows
public class ClientLogic1 {
public static void main(String[] args)
{
Resource res = new ClassPathResource("spconfig.xml");
BeanFactory factory = new XmlBeanFactory(res);
Object o = factory.getBean("id1");
WelcomeBean1 wb = (WelcomeBean1)o;
wb.show();
}
}
2nd class
public class WelcomeBean1 {
private Map data;
public void setData(Map data) {
this.data = data;
}
public void show()
{
Set s=data.entrySet();
Iterator it = s.iterator();
while(it.hasNext())
{
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+ " - "+me.getValue());
}
}
}
I have a xml file as
<beans>
<bean id="id1" class="WelcomeBean1">
<property name="data">
<map>
<entry key="k1">
<vlaue>1323</value>
</entry>
<entry key="k2">
<value>feed</value>
</entry>
</map>
</property>
</bean>
</bean>
I have given the right path.It's just when i run this program i get the following error
Exception in thread "main" java.lang.ClassCastException: WelcomeBean cannot be
cast to mapexmpl.WelcomeBean1 at mapexmpl.ClientLogic1.main(ClientLogic1.java:15)
I am not sure where i am going wrong.Can someone help me plz...
make sure there is no duplicate bean id in spring configuration file. for instance you might have bean WelcomeBean with id id1
change to full package name <bean id="id1" class="mapexmpl.WelcomeBean1">
Actually it is saying as WelcomeBean cannot be ..............But your code is showing all as WelcomeBean1.
You used WelcomeBean some where .Please check it once.
I think before you used WelComeBean.Then changed it to WelComeBean1.Please build agian with clean.

Spring: import a module with specified environment

Is there anything that can achieve the equivalent of the below:
<import resource="a.xml">
<prop name="key" value="a"/>
</import>
<import resource="a.xml">
<prop name="key" value="b"/>
</import>
Such that the beans defined in resouce a would see the property key with two different values? The intention would be that this would be used to name the beans in the imports such that resource a.xml would appear:
<bean id="${key}"/>
And hence the application would have two beans named a and b now available with the same definition but as distinct instances. I know about prototype scope; it is not intended for this reason, there will be many objects created with interdepednencies that are not actually prototypes. Currently I am simply copying a.xml, creating b.xml and renaming all the beans using the equivalent of a sed command. I feel there must be a better way.
I suppose that PropertyPlaceholderConfigurers work on a per container basis, so you can't achieve this with xml imports.
Re The application would have two beans named a and b now available with the same definition but as distinct instances
I think you should consider creating additional application contexts(ClassPathXmlApplicationContext for example) manually, using your current application context as the parent application context.
So your many objects created with interdependencies sets will reside in its own container each.
However, in this case you will not be able to reference b-beans from a-container.
update you can postprocess the bean definitions(add new ones) manually by registering a BeanDefinitionRegistryPostProcessor specialized bean, but this solution also does not seem to be easy.
OK, here's my rough attempt to import xml file manually:
disclaimer: I'm very bad java io programmer actually so double check the resource related code :-)
public class CustomXmlImporter implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
private Map<String, String> properties;
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public Map<String, String> getProperties() {
return properties;
}
private void readXml(XmlBeanDefinitionReader reader) {
InputStream inputStream;
try {
inputStream = new ClassPathResource(this.classpathXmlLocation).getInputStream();
} catch (IOException e1) {
throw new AssertionError();
}
try {
Scanner sc = new Scanner(inputStream);
try {
sc.useDelimiter("\\A");
if (!sc.hasNext())
throw new AssertionError();
String entireXml = sc.next();
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${",
"}", null, false);
Properties props = new Properties();
props.putAll(this.properties);
String newXml = helper.replacePlaceholders(entireXml, props);
reader.loadBeanDefinitions(new ByteArrayResource(newXml.getBytes()));
} finally {
sc.close();
}
} finally {
try {
inputStream.close();
} catch (IOException e) {
throw new AssertionError();
}
}
}
private String classpathXmlLocation;
public void setClassPathXmlLocation(String classpathXmlLocation) {
this.classpathXmlLocation = classpathXmlLocation;
}
public String getClassPathXmlLocation() {
return this.classpathXmlLocation;
}
#Override
public void postProcessBeanDefinitionRegistry(
BeanDefinitionRegistry registry) throws BeansException {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
readXml(reader);
}
}
XML configuration:
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="a" />
</map>
</property>
</bean>
<bean class="CustomXmlImporter">
<property name="classPathXmlLocation" value="a.xml" />
<property name="properties">
<map>
<entry key="key" value="b" />
</map>
</property>
</bean>
this code loads the resources from classpath. I would think twice before doing something like that, anyway, you can use this as a starting point.

Can a Spring BeanFactoryPostProcessor find nested beans with no name?

I am trying to write a Spring BeanFactoryPostProcessor that can find any bean which defines an init-method. I am having luck finding beans that have names, but not nested nameless beans as in the target bean in following example:
<bean id="aclDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="target">
<bean class="com.vidsys.dao.impl.acl.ACLDaoImpl" init-method="init">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
When I list the beans in my BeanFactoryPostProcessor I only seem to get the ones with names as in the following code:
public class BeanInitializationFinder implements BeanFactoryPostProcessor, Ordered {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
//String[] beanDefs = BeanFactoryUtils.beanNamesIncludingAncestors(beanFactory);
String[] beanDefs = beanFactory.getBeanDefinitionNames();
for(String defName : beanDefs) {
BeanDefinition def = beanFactory.getBeanDefinition(defName);
if(null == def.getBeanClassName() || !(def instanceof AbstractBeanDefinition))
return;
}
AbstractBeanDefinition abd = (AbstractBeanDefinition) def;
try {
if(abd.getFactoryMethodName() == null && abd.getFactoryBeanName() == null)
Class<?> beanClass = Class.forName(abd.getBeanClassName());
if(InitializingBean.class.isAssignableFrom(beanClass) || null != abd.getInitMethodName()) {
beansWithInits.add(defName);
}
}
}
catch(Exception e) {
throw new BeanIntrospectionException("Failed to instrospect bean defs", e);
}
}
}
}
I would like to get all the bean that have an init-method, including nested beans that are nameless. Can I do that?
You can retrieve nested BeanDefinitions, but not via beanFactory.getBeanDefinition. The only way to get to nested bean definitions is through the PropertyValues of the parent BeanDefinition - you need to walk the graph.
By way of example (and missing any null-checking):
BeanDefinition parentDef = beanFactory.getBeanDefinition(defName);
for (PropertyValue property : parentDef.getPropertyValues().getPropertyValues()) {
Object value = property.getValue();
if (value instanceof BeanDefinitionHolder) {
BeanDefinition nestedDef = ((BeanDefinitionHolder)value).getBeanDefinition();
}
}
Given that graph traversal works well with the Visitor pattern, you can subclass org.springframework.beans.factory.config.BeanDefinitionVisitor to do this in a more concise fashion.

Resources