I'm facing an issue trying to define a context hierarchy using AnnotationConfigApplicationContext.
The problem is when defining a module context inside beanRefContext.xml and setting the 'parent' property with another context (XML/Annotated based).
Example:
beanRefContext.xml in module A
<bean id="moduleA_ApplicationContext"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<property name="configLocations">
<list>
<value>classpath:db-context.xml</value>
</list>
</property>
</bean>
db-context.xml
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;TRACE_LEVEL_SYSTEM_OUT=2"/>
<!-- Hibernate Session Factory -->
<bean name="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="useTransactionAwareDataSource" value="true"/>
<property name="packagesToScan">
<list>
<value>com.example.model</value>
</list>
</property>
<property name="hibernateProperties">
<!-- hibernate props -->
</property>
</bean>
beanRefContext.xml in module B
<bean id="moduleB_ApplicationContext"
class="org.springframework.context.annotation.AnnotationConfigApplicationContext" >
<property name="parent" ref="moduleA_ApplicationContext"/>
<constructor-arg>
<list>
<value>com.example.dao</value>
</list>
</constructor-arg>
</bean>
FooHibernateDao
class FooHibernateDao implements FooDao {
#Autowired
#Qualifier("sessionFactory")
private SessionFactory sessionsFactory;
// CRUD methods
}
Module B application context fails to find bean defined in module A application context.
From looking at the code of AnnotationConfigApplicationContext it seems that the scanning process doesn't use the parent as a reference to resolve beans.
Is there something I'm doing wrong or my attempt to create a hierarchy is impossible with annotation configuration?
The problem stems from the fact that the constructor of the AnnotationConfigApplicationContext does the scan. Thus the parent is not set at this stage, it is only set after the scan is done as the parent is set by a property - thus the reason why it does not find your bean.
The default AnnotationConfigApplicationContext bean does not have a constructor that takes a parent factory - not sure why.
You can either use the normal xml based application context and configure your annotation scanning in there or you can create a custom fatory bean that will do create the annotation application context. This would specify the parent reference and then do the scan.
Take a look at the source...
The factory would look like this:
public class AnnotationContextFactory implements FactoryBean<ApplicationContext> {
private String[] packages;
private ApplicationContext parent;
#Override
public ApplicationContext getObject() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setParent(parent);
context.scan(packages);
context.refresh();
return context;
}
#Override
public Class<ApplicationContext> getObjectType() {
return ApplicationContext.class;
}
#Override
public boolean isSingleton() {
return true;
}
public void setPackages(String... args) {
this.packages = args;
}
public void setParent(ApplicationContext parent) {
this.parent = parent;
}
}
And your bean definition:
<bean id="moduleB_ApplicationContext" class="za.co.test2.AnnotationContextFactory">
<property name="parent" ref="moduleA_ApplicationContext" />
<property name="packages">
<list>
<value>za.co.test2</value>
</list>
</property>
</bean>
Don't use XML for the child context.
Use ctx.setParent then ctx.register. Like this:
public class ParentForAnnotationContextExample {
public static void main(String[] args) {
ApplicationContext parentContext = new AnnotationConfigApplicationContext(ParentContext.class);
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
childContext.setParent(parentContext);
childContext.register(ChildContext.class); //don't add in the constructor, otherwise the #Inject won't work
childContext.refresh();
System.out.println(childContext.getBean(ParentBean.class));
System.out.println(childContext.getBean(ChildBean.class));
childContext.close();
}
#Configuration
public static class ParentContext {
#Bean ParentBean someParentBean() {
return new ParentBean();
}
}
#Configuration
public static class ChildContext {
#Bean ChildBean someChildBean() {
return new ChildBean();
}
}
public static class ParentBean {}
public static class ChildBean {
//this #Inject won't work if you use ChildContext.class in the child AnnotationConfigApplicationContext constructor
#Inject private ParentBean injectedFromParentCtx;
}
}
I run into the same problem,
Another possibility is to extend AnnotationConfigApplicationContext and add just the required constructor or build the context programmatically, if you are instantiating the AnnotationConfigApplicationContext from java.
What I did was the following:
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance("classpath:beanRefContext.xml");
BeanFactoryReference parentContextRef = locator.useBeanFactory("ear.context");
ApplicationContext parentContext = (ApplicationContext) parentContextRef.getFactory();
childContext.setParent(parentContext);
And guess what, it worked :)
PS: If anyone knows how to replace the classpath:beanRefContext.xml with an #Configuration class, please let us all know.
I also run into a similar problem and after some research I found the following approach using a constructor from AnnotationConfigApplicationContext that allows to set the a hierarchy between contexts
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(parentContext);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(lbf);
context.register(annotatedClass1.class, annotatedClass2.class);
context.refresh();
Related
Basically, i have a Sampleapiclass class in which some files are retrieved into a list and the list is looped and for each file performAction is called. if a file fails validation, i want the transaction to roll back. The problem is, i tried annotating with rollback, but even if one file validation fails, transaction is not rolled bak.eg. file 1, succeeds, gets inserted, file 2 fails, but the file 1 is in the database. Any idea what might be wrong ?
As an additonal note, Sampleapiclass is initialied from the main method , may be it has somethin to do that that ?
f.eks: from main class
class MainClass
{
public static void main(String[] args)
{
ApplContext ctx = new ClassPathxmlApplicationContext("application-context.xml");
MainClass app = ctx.getBean(MainClass.class);
app.run(args);
}
void run()
{
Sampleapiclass api = new Sampleapiclass();
DaoClass dao = new Sampleapiclass();
api.apimethod(dao)
}
}
class Sampleapiclass
{
void apimethod(Dao daoclass)
{
serviceClass serviceClass = new ServiceCLass(daoclass);
List<String> files = filesFromSomewhere
for (String file: filesFromSomewhere)
{
try
{
serviceClass.performAction(file);
}
catch(Exception e)
{
}
}
}
}
class serviceClass
{
private final Dao daoclass;
public serviceClass(DaoClass daoclass)
{
this.daoclass = daoclass;
}
#Transactional(rollbackFor=Exception.class)
void performAction(String file) throws CustomException
{
dovalidation(file); // this method can throw CustomExeption if validation fails
daoclass.insert(file)
}
dovalidation(String file) throws CustomExeption
{
if (file.endsWith("somethng") throw new CustomExeption();
}
}
class dao
{
void insert(String file)
{
getNamedParameterJdbcTemplate().update(parameters);
}
}
Contents of app contetxt:
<bean class="Configbean class />
<bean id="dataSource" class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="${main.db.uri}"/>
<property name="username" value="${main.db.username}"/>
<property name="password" value="${main.db.password}"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
The problem is that #Transactional works with AOP. AOP doesn't work on inclass method calls (due to Proxying). Try throwing an exception inside of your #Transactional for it to work or call a method outside of your class also marked with #Transaction(propagation = Propagation.SUPPORTS)
also don't forget #EnableTransactionManagement over your #Configuration class
helpful resource https://javamondays.com/spring-transactions-explained/
I found many solutions to this issue, and choose the below one.
But it still gets NullpointerException, what's wrong?
A Class
#Component
public class A {
private static Foo foo;
#Autowired
public void setFoo(Foo foo) {
A.foo = foo;
}
public static someFunction() {
foo.doSomething();
}
}
B Class
#Service
public class B {
public void someFunction() {
A.someFunction();
}
}
You cannot auto-wire static properties in Spring, Static fields are instantiated during class load as they are the properties of the class while auto wired attributes work after spring initializes the beans.
Although you may use MethodInvokingFactoryBeanin Spring to achieve what you wanted.
some example would be in XML as below
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="foo.bar.Class.setTheProperty"/>
<property name="arguments">
<list>
<ref bean="theProperty"/>
</list>
</property>
</bean>
Edit :- without XML
inside your #Configuration class
do
#Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setStaticMethod("MyClass.staticMethod");
return methodInvokingFactoryBean;
}
let me know in case you need more help.
so I'm trying to run a sql query within this java app. I think I have the DAO set up correctly but it can't find the XML file which contains my queries. The code in question for my DAO implementation is:
private Properties queries;
public void setQueries(Properties queries) {
this.queries = queries;
}
public Boolean checkAssigned(String Id) {
String sql = queries.getProperty("CHECK_IF_ASSIGNED");
Map<String,Object> params = new HashMap<>();
List<String> assignedList;
params.put(":Id",Id);
LOG.info("Checking to see if already assigned \n" + "sql=" + sql
+ "\n" + "params=" + params);
assignedList = getNamedParameterJdbcTemplate().query(sql,params,
new assignedMapper());
if (assignedList == null || assignedList.size() == 0) {
ScreenVo.setSwitch(false);
}
else {
ScreenVo.setSwitch(true);
}
return ScreenVo.getSwitch();
}
My DAO is just:
public interface ScreenDao {
Boolean checkAssigned(String Id);
}
My queries.xml file looks like:
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<util:properties id="queries">
<prop key="CHECK_IF_ASSIGNED">
<![CDATA[
--Long query
]]>
</prop>
</util:properties>
</beans>
The bean for the dao in the applicationContext.xml is:
<bean id="screenDaoImpl" class="com.corp.apps.actionator.dao.ScreenDaoImpl">
<property name="dataSource" ref="datasource"/>
<property name="queries" ref="queries"/>
</bean>
And my declaration of the queries file in the applicationContext is:
<import resource="classpath:queries.xml"/>
It's declared in my web.xml in a similar fashion.
I tried to include everything that could possibly be relevant. I've tried autowiring the bean in ScreenDaoImpl.java but that didn't work. I'm really not sure where to go from here, or what I might have done wrong.
EDIT:
The exception I'm getting is:
javax.faces.event.MethodExpressionActionListener.processAction java.lang.NullPointerException
And my screenDaoImpl is declared before use as:
private static ScreenDao screenDao = new ScreenDaoImpl();
Spring-Bean screenDaoImpl must be created through Spring context, in this case Spring can inject required properties (dataSource and queries) in created bean.
I don't know your architecture of application. But I can offer you a couple of ways.
1 - If you want use screenDaoImpl in spring-bean which declared in spring-xml then you can do it like this:
<bean id="screenServiceImpl" class="com.corp.apps.actionator.service.ScreenServiceImpl">
<property name="screenDao" ref="screenDaoImpl"/>
</bean>
The better way is make all your application in Spring. And create (and inject) beans by spring-context xml. Do not create bean-objects by new. Spring can not inject properties in these objects.
If it is difficult then try to find examples of applications on the Spring site. Maybe try spring-boot (without xml).
2 - If you want use screenDaoImpl in non-spring object you can get screenDaoImpl from spring-context by "bridge". Create class:
package com.corp.apps.actionator.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class AppSpringBridge implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
public static ApplicationContext getApplicationContext() {
return context;
}
}
Define bean in application-context.xml:
<bean id="springBridge" class="com.corp.apps.actionator.util.AppSpringBridge />
Spring create this bean, but method getApplicationContext() (and context property) of this bean is static. And we can use getApplicationContext() in any methods:
ScreenDao screenDao = (ScreenDao)AppSpringBridge.getApplicationContext().getBean("screenDaoImpl");
I fixed it, and for posterity's sake I'll post my solution here:
First I autowired my screenDao bean in the invoking class, and then I created a static method to set screenDao.
#Autowired
private static ScreenDao screenDao;
#PostConstruct
public static void setScreenDao(ScreenDao newScreenDao) {
screenDao = newScreenDao;
}
#PostConstruct
public ScreenDao getScreenDao() {
return screenDao;
}
I'm not really sure if getScreenDao does anything but I added it as well.
Then in my application context I created a bean I called initialize to invoke the static method.
<bean id="initialize" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="com.corp.apps.consolidator.backing.ScreenBean"/>
<property name="targetMethod" value="setScreenDao"/>
<property name="arguments">
<list>
<ref bean="screenDao"/>
</list>
</property>
</bean>
These two changes resolved my issue.
I'm trying to move from a xml based config to java annotations
I need your help getting this to work:
Obviously I can't set the RemoteJco interface to my SapConnector but what can I do to get this xml-config working?
#Bean
public RmiProxyFactoryBean jcoPool(){
RmiProxyFactoryBean jcoPool = new RmiProxyFactoryBean();
jcoPool.setServiceUrl("rmi://localhost/CH");
jcoPool.setServiceInterface(RemoteJco.class);
jcoPool.setRefreshStubOnConnectFailure(true);
return jcoPool;
}
#Bean
public SapConnector SapConnector(){
SapConnector sapConnector = new SapConnector();
sapConnector.setJcoPool(jcoPool());
return sapConnector;
}
this in the XML-Config works just fine:
<!-- JCO-Pool RMI Service -->
<bean id="jcoPool" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl" value="rmi://localhost/CH"/>
<property name="serviceInterface" value="com.itensis.jco.common.RemoteJco"/>
<property name="refreshStubOnConnectFailure" value="true" />
</bean>
<bean id="SapConnector" class="com.itensis.core.SapConnector">
<property name="jcoPool">
<ref bean="jcoPool" />
</property>
</bean>
this is my SAP-Connector
#Service
public class SapConnector {
#Autowired private RemoteJco jcoPool;
public RemoteJco getJcoPool() {
return jcoPool;
}
public void setJcoPool(RemoteJco jcoPool) {
this.jcoPool = jcoPool;
}
}
You have to make some changes on the jcoPool bean:
#Bean
public RemoteJco jcoPool(){
RmiProxyFactoryBean jcoPool = new RmiProxyFactoryBean();
jcoPool.setServiceUrl("rmi://localhost/CH");
jcoPool.setServiceInterface(RemoteJco.class);
jcoPool.setRefreshStubOnConnectFailure(true);
jcoPool.afterPropertiesSet();
return (RemoteJco) jcoPool.getObject();
}
Make sure that you return value has the same class as you used as service interface. And you have to call afterPropertiesSet() before calling getObject on the RmiProxyFacotoryBean instance.
Is it possible to invoke static method in Spring configuration file?
public MyClass {
public static void staticMethod() {
//do something
}
}
<bean id="myBean" class="MyClass">
<!-- invoke here -->
</bean>
When the static method creates an instance of MyClass you an do it like this
config
<bean id="myBean" class="MyClass" factory-method="staticMethod">
<!-- invoke here -->
</bean>
code
public static MyClass staticMethod() {
//create and Configure a new Instance
}
If you want the method only to be called on bean instantiation spring can't do it this way.
config
<bean id="myBean" class="MyClass" init-method="init">
<!-- invoke here -->
</bean>
code
public static void staticMethod() {
//create and Configure a new Instance
}
public void init() {
staticMethod();
}
try this
<bean id="b1" class="org.springframework.beans.factory.config.MethodInvokingBean">
<property name="staticMethod" value="MyClass.staticMethod" />
</bean>
see http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/config/MethodInvokingBean.html
Try something like this:
<!-- call static method -->
<bean id="test" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="MyClass" />
<property name="targetMethod" value="staticMethod" />
<property name="arguments">
<list>
<value>anArgument</value>
</list>
</property>
</bean>
Remove arguments as you might not need them.
Taken from https://gist.github.com/bulain/1139874
I was needing to call a static method. The above code worked fine.
This might be useful as well: How to make spring inject value into a static field.
If you are using annotations for spring configuration you can add the following method into your #Configuration class:
#Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setStaticMethod("MyClass.staticMethod");
return methodInvokingFactoryBean;
}