2016-11-17 14 views
1

我目前正在將我的一個項目窗體「自我配置的彈簧」遷移到彈簧啓動。而大多數的東西已經工作,我有一個@Transactional方法的問題,當它被稱爲上下文不存在以前設置由於調用「目標」實例,而不是「代理」實例(我會試着在下面詳細說明)。@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()的調用中,ExporterImpl的字段config爲空,儘管它已被設置在之前。

我到目前爲止發現的: 在exporter.startExport();的斷點處,我檢查了eclipse調試器顯示的導出器實例的id。在寫作這篇文章的debbug回合中,它是16585。繼續執行到startExport()的呼叫/第一行,我再次檢查了id(這次是this),期望它是相同的,但是意識到它不是。這裏是16606 ......所以對startExport()的調用是在該類的另一個實例上完成的...在之前的一輪調試中,我檢查了哪個實例/編號爲setConfig()的調用將進行到第一個調用(16585在這種情況下)。這解釋了爲什麼配置字段在16606實例中爲空。

爲了理解在我打電話給exporter.startExport();的行與實際的第一行startExport()之間發生了什麼,我點擊了eclipse調試器中這兩行之間的步驟。

在那裏,我來到line 655 in CglibAopProxy看起來像這樣:

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

這裏檢查的論點我發現proxy是ID爲16585和target一個與16606.

可惜我實例沒有深入到彈簧aop的東西,知道這是如何應該是...

我只是想知道爲什麼有兩個實例調用不同的方法。呼叫setConfig()去代理實例和呼叫做startExport()到達目標實例,因此無法訪問以前設置的配置...

如上所述,項目已遷移到春季啓動,但我們在哪裏之前已經在使用Athens-RELEASE版本的彈簧平臺。從我可以告訴的是,在遷移之前沒有特殊的AOP配置,遷移後沒有明確設置值。

得到這個問題固定的(或至少以某種方式工作)我已經嘗試過許多東西:

  • 從子類
  • 移動@Transactional從方法層面類
  • 覆蓋刪除@Scope子類中的startExport()並將@Transactional放在這裏
  • 將@EnableAspectJAutoProxy添加到應用程序類(我甚至無法登錄 - 無錯誤消息)
  • set spring.aop.proxy-tar獲得一流的,以不同勢組合上面真正
  • ...

我目前就如何得到這個工作回來的線索......

在此先感謝

*希望有人可以幫助*

+0

如果你不直接調用該字段,但getConfig()會發生什麼? –

+0

'getConfig()'在與'startExport()'相同的實例上被調用並返回null。 – Dodge

+0

刪除'setConfig'上的'final'關鍵字。代理被創建,但它是一個基於類的代理。導致在代理上調用'setConfig'並在代理實例上調用'startExport'。或者將'spring.aop.proxy-target-class'切換到'false'來使用基於接口的代理。 –

回答

3

在您可能有基於接口(JDK動態代理)之前,Spring Boot會嘗試創建一個基於類的代理的cglib代理。

由於這個原因,您的Cars2ExporterImpl的子類被創建,所有方法都被覆蓋,並且將應用這些建議。但是,由於您的setConfig方法是final,無法重寫,因此該方法實際上將在代理上而不是在代理實例上調用。

因此,要麼刪除final關鍵字,以便可以創建CgLib代理或顯式禁用事務的基於類的代理。添加@EnableTransationManagement(proxy-target-class=false)也應該這樣做。除非有別的東西觸發基於類的代理。

+0

非常感謝!特別是與你的評論相比更加詳細的解釋! – Dodge