Не удалось внедрить автонастройки зависимостей при использовании @Transactional - PullRequest
7 голосов
/ 22 марта 2012

Я тестирую свой DAO, но он не работает.Возникает следующая ошибка:

Tests in error: 
  testAccountOperations(com.tsekhan.rssreader.dao.HibernateControllerTest): Error creating bean with name 'com.tsekhan.rssreader.dao.HibernateControllerTest': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: com.tsekhan.rssreader.dao.HibernateController com.tsekhan.rssreader.dao.HibernateControllerTest.hibernateController; nested exception is java.lang.IllegalArgumentException: Can not set com.tsekhan.rssreader.dao.HibernateController field com.tsekhan.rssreader.dao.HibernateControllerTest.hibernateController to $Proxy25

Мой DAO:

@Service
@Scope("singleton")
public class HibernateController extends HibernateDaoSupport {

    @Autowired
    public SessionFactory sessionFactory;

    @Transactional
    public void addAcount(Account account) {
        sessionFactory.getCurrentSession().saveOrUpdate(account);
    }
}

Мой тест для этого DAO:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/applicationContext.xml")
public class HibernateControllerTest {

    @Autowired
    HibernateController hibernateController;

    private Set<Channel> getTestChannelList(String channelLink) {
        Channel testChannel = new Channel();
        testChannel.setSourceLink(channelLink);
        Set<Channel> testChannelList = new HashSet<Channel>();
        testChannelList.add(testChannel);
        return testChannelList;
    }

    private Account getTestAccount(String accountLogin, String channelLink) {
        Account testAccount = new Account();
        testAccount.setAccountLogin(accountLogin);
        testAccount.setChannelList(getTestChannelList(channelLink));
        return testAccount;
    }

    @Test
    public void testAccountOperations() {
        hibernateController
                .addAcount(getTestAccount("test_login", "test_link"));
    }
}

Мой 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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd"
        default-autowire="byName">

    <!-- Enabling spring-transaction annotations -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!-- Enabling annotation-driven configurating -->
    <context:annotation-config />

    <!-- Creation of transaction manager -->

    <bean id="transactionManager" scope="singleton" 
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" scope="singleton"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="configLocation" value="classpath:/hibernate.cfg.xml"/>
        <property name="configurationClass">
            <value>org.hibernate.cfg.AnnotationConfiguration</value>
        </property>
    </bean>
    <!--
    A Spring interceptor that takes care of Hibernate session lifecycle.
    -->
    <bean id="hibernateInterceptor" 
            class="org.springframework.orm.hibernate3.HibernateInterceptor">
        <property name="sessionFactory">
            <ref bean="sessionFactory"/>
        </property>
    </bean>

    <bean name="employeeDAO" scope="prototype" 
        class="com.tsekhan.rssreader.dao.HibernateController" />

    <!-- Searching for hibernate POJO files in package com.tsekhan.rssreader.web -->
    <context:component-scan base-package="com.tsekhan.rssreader.web" />
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

</beans>

Замечу, что если вы комментируете @Transactional в DAO, бин создается правильно.Что происходит?

Ответы [ 2 ]

22 голосов
/ 22 марта 2012

Во-первых, очень плохо давать имя, оканчивающееся на Controller, DAO, это очень запутанно, у Controller и DAO все вместе разные цели.

Когда вы добавляете @Transactional к классу службы или дао, для Spring, чтобы заставить его работать в транзакции, необходимо создать прокси этого класса, своего рода оболочку, в которой он находится перед выполнением прокси-класса (класс в соображение, которое проксируется) метод spring запускает транзакцию и после выполнения в случае, если никакие исключения не завершают транзакцию, это может быть сделано весной через AOP и Annotations. Для описания в коде.

public class OriginalDaoImpl implements OriginalDao extends DaoSupport {

  public void save(Object o){
      manager.save(o);
  }
}

public class ProxyDaoImpl implements OriginalDao {

    private OriginalDao originalDaoImpl; //instance of OriginalDaoImpl
    public void save(Object o){
       try{
            transaction.start();
            originalDaoImpl.save(o);
            transaction.commit(); 
       }catch(Exception e){
            transaction.rollback();
       }finally{
            //clean up code
       }
    }
}

Как видите, это не точная реализация, а базовый код, как волшебно работает транзакция для вас. Ключевым моментом является наличие интерфейса OriginalDao, который делает эту инъекцию легкой, поскольку OriginalDaoImpl и ProxyDaoImpl реализуют один и тот же интерфейс. Следовательно, их можно поменять местами, т. Е. Прокси, заменяющий оригинал. Этот динамический прокси может быть создан в Java динамическим прокси Java. Теперь вопрос: что, если ваш класс не реализует интерфейс, замена будет сложнее. Насколько мне известно, одна из библиотек CGLIB помогает в таком сценарии, когда она генерирует динамический подкласс для рассматриваемого класса и в переопределенном методе выполняет магию, как описано выше, вызывая super.save(o) для делегирования в исходный код.

Теперь к проблеме инъекций.

  1. Создайте интерфейс и сделайте так, чтобы ваш dao реализовал это, и Spring по умолчанию будет использовать JDK-прокси, как сейчас.
  2. Добавить атрибут proxy-target-class="true" к <tx:annotation-driven transaction-manager="transactionManager"/>

Что касается исключения, то он генерирует, поскольку ожидает, что внедренный компонент будет иметь тип HibernateController, но это не так.

Для справки вы можете обратиться по ссылкам ниже.

  1. 10.5.6 Использование @ Transactional
  2. Spring AOP Doc

Надеюсь, это поможет !!!!!.

0 голосов
/ 15 октября 2012

Если вы используете Spring MVC, обязательно сканируйте только определенные классы контроллеров в servlet context file.В противном случае он будет сканироваться 2 раза, а транзакция недоступна в контексте приложения.

...