2016-11-17 13 views
1

В настоящее время я переношу одну из моих проектов на «самонастраиваемую пружину» на весеннюю загрузку. в то время как большая часть материала уже работает, у меня есть проблема с методом @Transactional, где, когда он называется контекстом, отсутствует, как установлено ранее, из-за вызова экземпляра «target» вместо экземпляра «proxy» (я буду попытайтесь подробно остановиться ниже).Метод с @Transactional вызван на цель не на экземпляр прокси

Сначала урезанная вид моей иерархии классов:

 
@Entity 
public class Config { 
    // fields and stuff 
} 

public interface Exporter { 

    int startExport() throws ExporterException; 

    void setConfig(Config config); 
} 


public abstract class ExporterImpl implements Exporter { 
    protected Config config; 

    @Override 
    public final void setConfig(Config config) { 
     this.config = config; 
     // this.config is a valid config instance here 
    } 

    @Override 
    @Transactional(readOnly = true) 
    public int startExport() throws ExporterException { 
     // this.config is NULL here 
    } 

    // other methods including abstract one for subclass 
} 

@Scope("prototype") 
@Service("cars2Exporter") 
public class Cars2ExporterImpl extends ExporterImpl { 

    // override abstract methods and some other 
    // not touching startExport() 
} 

// there are other implementations of ExporterImpl too 
// in all implementations the problem occurs 

код вызова выглядит так:

 
@Inject 
private Provider<Exporter> cars2Exporter; 

public void scheduleExport(Config config) { 
    Exporter exporter = cars2Exporter.get(); 
    exporter.setConfig(config); 
    exporter.startExport(); 
    // actually I'm wrapping it here in a class implementing runnable 
    // and put it in the queue of a `TaskExecutor` but the issue happens 
    // on direct call too. :(
} 

Что именно проблема?

При звонке в startExport() поле config из ExporterImpl имеет значение null, хотя оно было установлено ранее.

Что я нашел: С точкой останова на exporter.startExport(); Я проверил идентификатор экземпляра экспортера, показанный отладчиком eclipse. В раунде debbug при написании этого сообщения это 16585. Продолжение выполнения в строке вызова/первой строки startExport(), где я снова проверил идентификатор(), ожидая, что он будет таким же, но осознает, что это не так. Здесь 16606 ... так что вызов startExport() выполняется в другом экземпляре класса ... в предыдущем раунде отладки, который я проверил на экземпляр/id, вызов setConfig() идет ... к первому на (16585 в этом случае). Это объясняет, почему поле конфигурации имеет значение null в экземпляре 16606.

Чтобы понять, что происходит между строкой, где я звоню exporter.startExport();, и первой линией возбуждения startExport(), я нажал на шаги между этими двумя строками в отладчике eclipse.

Там я пришел к line 655 in CglibAopProxy который выглядит следующим образом:

retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); 

проверки аргументов здесь я обнаружил, что proxy является экземпляром с идентификатором 16585 и target той с 16606.

к сожалению, я не так глубоко в springs, чтобы знать, как это должно быть ...

Мне просто интересно, почему есть два экземпляра, которые вызываются в разных методах. вызов setConfig() отправляется на экземпляр прокси, а вызов do startExport() достигает целевого экземпляра и, следовательно, не имеет доступа к ранее установленной конфигурации ...

Как уже упоминалось, проект был перенесен на весеннюю загрузку, но мы, где раньше уже используя версию весенней платформы весом Athens-RELEASE. Из того, что я могу сказать там, где нет специальных конфигураций АОП до миграции, и явным образом не заданы значения после миграции.

Чтобы получить эту проблему фиксированной (или, по крайней мере, как-то работает) я уже пробовал несколько вещей:

  • удалить @Scope из вспомогательного класса
  • шаг @Transactional от уровня метода для класса
  • переопределения startExport() в подклассе и положить @Transactional здесь
  • добавить @EnableAspectJAutoProxy к классу приложений (я не смог даже не войти в систему - не сообщение об ошибке)
  • набор spring.aop.proxy-деготь получить класс к истинным
  • выше в diffrent комбинаций ...

В настоящее время я из подсказок о том, как получить это обратно рабочие ...

Заранее спасибо

* надеется, что кто-то может помочь *

+0

Что произойдет, если вы не вызываете это поле напрямую, а 'getConfig()'? –

+0

'getConfig()' вызывается в том же экземпляре, что и 'startExport()', и возвращает null. – Dodge

+0

Удалите ключевое слово 'final' на' setConfig'. Прокси создается, но это прокси-сервер на основе класса. Для вызова прокси-сервера необходимо вызвать 'setConfig' для прокси-сервера и' startExport'. Или переключите 'spring.aop.proxy-target-class' на' false', чтобы иметь прокси-сервер на основе интерфейса. –

ответ

3

Spring Boot пытается создать прокси-сервер cglib, который является прокси-сервером на основе класса, прежде чем вы, вероятно, создали интерфейс (JDK Dynamic Proxy).

В связи с этим создается подкласс вашего Cars2ExporterImpl, и все методы переопределяются, и рекомендации будут применены. Однако, поскольку ваш метод setConfig равен final, который нельзя переопределить, и в результате этот метод будет фактически вызываться на прокси вместо этого в прокси-экземпляре.

Таким образом, либо удалите ключевое слово final, чтобы прокси-сервер CgLib мог быть создан или явно отключить прокси-объекты на основе классов для транзакций. Добавить @EnableTransationManagement(proxy-target-class=false) также должен сделать трюк. Если есть что-то еще, вызывающее прокси-классы на основе классов.

+0

Большое спасибо! Особенно для более подробного объяснения по сравнению с вашим комментарием! – Dodge

 Смежные вопросы

  • Нет связанных вопросов^_^