Spring TransactionProxyFactoryBean не зарежда dao за услуга

Имам проблем с настройването на Spring с Hibernate под GWT framework. Аз съм сравнително нов в GWT. Контекстът на приложението е настроен и се зарежда без изходни грешки, но основният ми проблем в момента е, че внедряването на слоя на услугата (PobaseServiceImpl) изисква DAO, който настройвам в контекста на приложението, но не обвива DAO. Естествено моят RPC се опитва да извика dao методите, което води до NullPointerException. PobaseDao не се задава от TransactionProxyFactoryBean, когато го инициализирам.

В обобщение: DAO трябва да бъде създаден от (т.е. конфигуриран в) Spring точно както останалите ми услуги. След това се инжектира към услугите чрез Spring. След това с DAO го увийте в прокси за транзакция на Spring (org.springframework.transaction.interceptor.TransactionProxyFactoryBean) и му дайте Hibernate SessionFactory (org.springframework.orm.hibernate4.LocalSessionFactoryBean). Но по някаква причина не задава дао

Така че контекстът на моето приложение се зарежда чрез ServletContextListener. Ето контекста на моето приложение:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

  <!--
    Spring Framework application context definition for the POBASE Website.
  -->

<beans>

  <!-- Configurer that replaces ${...} placeholders with values from a properties file -->
  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
         <value>classpath:/pobase.properties</value>
         <value>file:${user.home}/pobase.properties</value>
      </list>
    </property>
    <property name="ignoreResourceNotFound" value="no"/>
  </bean>

  <!-- Hibernate Data Source -->
  <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${pobase.database.driver}" />
    <property name="url" value="${pobase.database.url}" />
    <property name="username" value="${pobase.database.user}" />
    <property name="password" value="${pobase.database.password}" />
  </bean>

  <!-- Hibernate SessionFactory -->
  <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource">
      <ref bean="dataSource" />
    </property>
    <property name="packagesToScan" value="nz.co.doltech.pobase.client.entity"/>
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">${pobase.hibernate.dialect}</prop>
        <prop key="hibernate.show_sql">${pobase.hibernate.show_sql}</prop>
        <prop key="javax.persistence.validation.mode">none</prop>
      </props>
    </property>
  </bean>

  <!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
  <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <!-- Default transaction proxy, defining the transactional behaviour for
    a typical Dao configuration -->
  <bean id="baseDaoTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
    abstract="true">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
      <value>*=PROPAGATION_MANDATORY</value>
    </property>
  </bean>

  <!-- Default transaction proxy, defining the transactional behaviour for
    a typical Service configuration -->
  <bean id="baseServiceTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
    abstract="true">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
      <value>*=PROPAGATION_REQUIRED</value>
    </property>
  </bean>

  <!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= -->

  <bean id="pobaseDao" parent="baseDaoTransactionProxy">
    <property name="target" ref="pobaseDaoTarget" />
  </bean>
  <bean id="pobaseDaoTarget" class="nz.co.doltech.pobase.server.dao.PobaseHibernateDao">
    <property name="sessionFactory" ref="sessionFactory" />
  </bean>

  <bean id="pobaseService" parent="baseServiceTransactionProxy">
    <property name="target" ref="pobaseServiceTarget" />
  </bean>
  <bean id="pobaseServiceTarget" class="nz.co.doltech.pobase.server.service.PobaseServiceImpl">
    <property name="pobaseDao" ref="pobaseDao" />
    <!-- property name="accessControlService" ref="accessControlService" />
    <property name="lookupService" ref="lookupService"/>
    <property name="notificationService" ref="notificationService"/ -->
  </bean>

</beans>

и ето моята реализация на RPC сървлет:

package nz.co.doltech.pobase.server.service;

/**
 * Extends RSS and implements the PobaseService
 * @author Ben
 */
@SuppressWarnings("serial")
public class PobaseServiceImpl extends RemoteServiceServlet implements PobaseService {

    @SuppressWarnings("unused")
    private static final Logger logger = Logger.getLogger(PobaseServiceImpl.class.getName());

    private PobaseDao pobaseDao;
    private final HashMap<Integer, PobaseEntity> pobaseEntities = new HashMap<Integer, PobaseEntity>();

    private void fetchExternPobase()
    {
        pobaseEntities.clear();
        List<PobaseEntity> pobaseList = pobaseDao.getAllPobase();
        for (int i = 0; i < pobaseList.size(); i++)
        {
            PobaseEntity en = pobaseList.get(i);
            if(en != null) {
                pobaseEntities.put(en.getId(), en);
            }
        }
    }

    public void setPobaseDao(PobaseDao dao)
    {
        this.pobaseDao = dao;
    }

    public PobaseDao getPobaseDao()
    {
        return this.pobaseDao;
    }

    public PobaseData addLocalPobase(PobaseData pobase)
    {
        PobaseEntity entity = new PobaseEntity();
        entity.mirrorObjectData(pobase);

        pobase.setId(pobaseEntities.size());
        pobaseEntities.put(pobase.getId(), entity); 

        return entity.getDataObject();
    }

    public PobaseData updateLocalPobase(PobaseData pobase)
    {
        PobaseEntity entity = new PobaseEntity();
        entity.mirrorObjectData(pobase);

        pobaseEntities.remove(entity.getId());
        pobaseEntities.put(entity.getId(), entity);

        return entity.getDataObject();
    }

    public Boolean deleteLocalPobase(int id)
    {
        pobaseEntities.remove(id);
        return true;
    }

    public ArrayList<PobaseData> deleteLocalPobases(ArrayList<Integer> ids)
    {
        for (int i = 0; i < ids.size(); ++i) {
            deleteLocalPobase(ids.get(i));
        }

        return getLocalPobaseData();
    }

    public ArrayList<PobaseData> getLocalPobaseData()
    {
        ArrayList<PobaseData> pobaseList = new ArrayList<PobaseData>();
        Iterator<Integer> it = pobaseEntities.keySet().iterator();
        while(it.hasNext())
        {
            PobaseData pobase = pobaseEntities.get(it.next()).getDataObject();
            pobaseList.add(pobase);
        }
        return pobaseList;
    }

    public PobaseData getLocalPobase(int id)
    {
        return pobaseEntities.get(id).getDataObject();
    }

    public ArrayList<PobaseData> resyncExternPobase()
    {
        fetchExternPobase();
        return getLocalPobaseData();
    }

}

Тук е и дневникът за стартиране на уеб приложението:

ServletContextListener started
Nov 12, 2012 8:20:33 PM nz.co.doltech.pobase.SpringInitialiser initSpringContext
INFO: Creating new Spring context. Configs are [/nz/co/doltech/pobase/appcontext.xml]
Nov 12, 2012 8:20:33 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@8423321: startup date [Mon Nov 12 20:20:33 NZDT 2012]; root of context hierarchy
Nov 12, 2012 8:20:33 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [nz/co/doltech/pobase/appcontext.xml]
Nov 12, 2012 8:20:33 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from class path resource [pobase.properties]
Nov 12, 2012 8:20:33 PM org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
INFO: Loading properties file from URL [file:/home/ben/pobase.properties]
Nov 12, 2012 8:20:33 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4c56666d: defining beans [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,dataSource,sessionFactory,transactionManager,baseDaoTransactionProxy,baseServiceTransactionProxy,pobaseDao,pobaseDaoTarget,pobaseService,pobaseServiceTarget]; root of factory hierarchy
Nov 12, 2012 8:20:33 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: org.postgresql.Driver
Nov 12, 2012 8:20:33 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
Nov 12, 2012 8:20:33 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.7.Final}
Nov 12, 2012 8:20:33 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Nov 12, 2012 8:20:33 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Nov 12, 2012 8:20:34 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
Nov 12, 2012 8:20:34 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Nov 12, 2012 8:20:34 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
Nov 12, 2012 8:20:34 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
Nov 12, 2012 8:20:34 PM org.springframework.orm.hibernate4.HibernateTransactionManager afterPropertiesSet
INFO: Using DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource@55acc0d4] of Hibernate SessionFactory for HibernateTransactionManager
Starting Jetty on port 8888

Някой има ли идеи защо не работи? Някои спецификации тук:

  • Пролет 3.2.0
  • Хибернация4
  • GWT SDK 2.4.0

Оценявам всяка помощ, която мога да получа!

Наздраве, Бен


person Ben Dol    schedule 12.11.2012    source източник
comment
моля, добавете проследяването на стека и освен това сигурни ли сте, че искате да заредите данни и да ги задържите във вашата услуга impl?   -  person Francisco Spaeth    schedule 12.11.2012
comment
Има ли по-традиционен начин за съхраняване на данните? Аз съм сравнително нов в GWT, така че не съм 100% сигурен кои са най-добрите практики. (Ще добави проследяване на стека към основната публикация) Нулевото изключение се дължи на това, че pobaseDao е извикан преди да бъде зададен. Но доколкото разбирам, проксито за транзакции и сервизният компонент, конфигурирани в appcontext.xml, трябва да се грижат за това?   -  person Ben Dol    schedule 12.11.2012
comment
Е, зависи от вашия подход, но съхраняването на данни в изпълнението на услугата е разумно, когато искате да изпълните нещо като кеш, в противен случай това не е правилният начин.   -  person Francisco Spaeth    schedule 12.11.2012
comment
Ок, ще го имам предвид, благодаря. Това приложение е тестово приложение за изграждане на знанията ми в GWT с Spring и Hibernate :)   -  person Ben Dol    schedule 12.11.2012
comment
Вместо ‹property name=pobaseDao ref=pobaseDao /› опитайте ‹property name=pobaseDao ref=pobaseDaoTarget /› и вижте как върви.   -  person Sajan Chandran    schedule 12.11.2012
comment
@SajanChandran благодаря за отговора, за съжаление не проработи. Вярвам, че пропускам нещо общо с внедряването на услугата може би. Публикацията, която маркирах като отговор, е най-добрият отговор досега, защото това е един от методите за правенето му, но въз основа на това, което ми беше казано, има друг начин да го закача, без да отмените метода за иницииране на RemoteServiceServlet и т.н.   -  person Ben Dol    schedule 13.11.2012


Отговори (1)


Вашият PobaseServiceImpl клас не е безопасен за нишки. В Java всяка отдалечена извикана услуга (и разбира се същото важи и за сингълтоните в контейнера) трябва да бъде подготвена, че нейните методи ще бъдат извикани от различните нишки и същият метод може да се изпълнява едновременно в различните нишки.

Целият клас има нужда от подробна проверка.

Първо, трябва да използвате ConcurrentHashMap вместо HashMap. И всички методи трябва да бъдат внимателно пренаписани - например методът, който връща ArrayList ( public ArrayList<PobaseData> resyncExternPobase()), трябва да направи защитно копие на данните.

Като алтернатива трябва да обмислите да направите публичните методи вашите методи за услуга синхронизирани, но това ще принуди методите да се изпълняват последователно и това ще подкопае производителността, ако се очаква услугата да се използва активно от различни нишки.

За справка можете да прочетете Brian Goetz Практическа съвместимост на Java, глава 5. Градивни блокове и особено 5.1 Синхронизирани колекции и 5.2. Едновременни колекции

актуализация: Вашият PobaseServiceImpl изглежда е сървлет, който се инстанцира от контейнера на сървлета - за да попълните неговите полета с Spring beans, които сте, трябва да използвате методи за помощна програма на Spring - например WebApplicationContextUtils - има много примери в интернет, като http://pgt.de/2009/07/17/non-invasive-gwt-and-spring-integration-reloaded/

person Boris Treukhov    schedule 12.11.2012
comment
Благодаря ви за отговора, със сигурност ще разгледам това, което споменахте. Аз съм доста нов в този тип разработка, така че изучаването на по-добрите практики в отдалечените услуги и т.н. е наистина добро. По принцип това е тестово приложение, за да науча повече за GWT и разработването на големи уеб приложения. И така, както и да е, това, което споменавате, причинява ли проблема ми с опаковането на DAO? - person Ben Dol; 12.11.2012
comment
@BenDol Първо заменете HashMap с ConcurrentHashMap, добавете коментар, ако нещо се промени. - person Boris Treukhov; 12.11.2012
comment
Няма промяна в поведението. Наистина не формулирах много добре какъв е проблемът ми в основната публикация. По принцип изглежда, че Spring DAO не се обвива, когато инициализирам контекста на приложението. Доколкото разбирам, в моя appcontext.xml съм дефинирал bean с id pobaseService, който сочи към целевия bean(pobaseServiceTarget), който има клас PobaseServiceImpl и свойство ‹property name=pobaseDao ref=pobaseDao /›, което трябва да обвие DAO в обслужване. Но явно пропускам нещо, не съм много сигурен какво. - person Ben Dol; 12.11.2012
comment
Опитвали ли сте да стартирате приложението в режим на отстраняване на грешки и да влезете в метода fetchExternPobase, за да намерите действителния ред, където възниква изключението? - person Boris Treukhov; 12.11.2012
comment
Това е, когато извикам pobaseDao, който не е инициализиран правилно в услугата. 'List‹PobaseEntity› pobaseList = pobaseDao.getAllPobase();' - person Ben Dol; 12.11.2012
comment
@BenDol Но препратката към Дао не е нулева? - person Boris Treukhov; 12.11.2012
comment
То е нищожно, това е проблемът. Пролетният контекст трябва да инициализира DAO, тъй като го конфигурирах по този начин в appcontext.xml. Освен ако не съм го направил грешно. - person Ben Dol; 12.11.2012
comment
DAO трябва да бъде създаден от (т.е. конфигуриран в) Spring точно както останалите ми услуги. След това се инжектира към услугите чрез Spring. Това, което правя за DAO, е да го увия в прокси за транзакции Spring (org.springframework.transaction.interceptor.TransactionProxyFactoryBean) и да му дам Hibernate SessionFactory (org.springframework.orm.hibernate4.LocalSessionFactoryBean). Но по някаква причина не задава дао. - person Ben Dol; 12.11.2012
comment
@BenDol, все още нямам опит с GWT, но не RemoteServiceServlet екземплярите се инициализират извън контейнера на Spring - тъй като те са външни за Spring. mitosome.blogspot.ru/2011/01/ Също така, ако това е сървлет - сървлетите трябва да бъдат прикачени към контейнера ръчно, тъй като се инициализират от уеб сървъра. - person Boris Treukhov; 12.11.2012
comment
Да, но Spring го инициализира в услугата с проксито, което вярвам. Освен ако не съм твърде оптимист в мисълта, че ще бъде толкова автоматизирано? Може би пропускам нещо общо с анотацията @autowired... не съм сигурен, че ще продължа да проучвам и се надявам, че някой ще може да хвърли малко светлина върху това. - person Ben Dol; 12.11.2012
comment
В този случай Spring просто създава друг екземпляр на Servlet (защото това е Java клас, деклариран в контекстна xml конфигурация) - как би се променил жизненият цикъл на уеб приложението? :) - person Boris Treukhov; 12.11.2012
comment
@BenDol тук няма магия - сървлетите се инстанциират от контейнера на сървлета - за достъп до контекста на основното уеб приложение на Spring от там трябва да използвате помощни класове на Spring, например както се прави в milan answer: stackoverflow.com/questions/8933778/ - има някои класове в мрежата за опростяване на GWT интеграцията - които просто попълват полетата на Servlet чрез извикване на помощни методи или предоставят фасада на Spring с метод getBean(beanName), който също се изпълнява чрез помощни класове на Spring. - person Boris Treukhov; 12.11.2012
comment
Много благодаря за помощта, сега има много повече смисъл. Най-оценен. - person Ben Dol; 13.11.2012