ejb能调用另一个ejb吗_异步EJB只是一个Gi头吗?
ejb能調(diào)用另一個(gè)ejb嗎
在之前的文章( 此處和此處 )中,我展示了當(dāng)服務(wù)器負(fù)載沉重時(shí),創(chuàng)建非阻塞異步應(yīng)用程序可以提高性能。 EJB 3.1引入了@Asynchronous批注,用于指定方法將在將來(lái)的某個(gè)時(shí)間返回其結(jié)果。 Javadocs聲明必須返回void或Future 。 下面的清單顯示了使用此注釋的服務(wù)示例:
Service2.java
注釋在第4行上。該方法返回String類型的Future ,并在第10行上通過(guò)將輸出包裝在AsyncResult 。 在客戶端代碼調(diào)用EJB方法時(shí),容器攔截了該調(diào)用并創(chuàng)建了一個(gè)任務(wù),它將在另一個(gè)線程上運(yùn)行,以便它可以立即返回Future 。 當(dāng)容器然后使用其他線程運(yùn)行任務(wù)時(shí),它將調(diào)用EJB的方法并使用AsyncResult來(lái)完成給定調(diào)用者的Future 。 即使看起來(lái)與Internet上所有示例中的代碼完全一樣,此代碼也存在一些問(wèn)題。 例如, Future類僅包含用于獲取Future結(jié)果的阻塞方法,而不包含用于在回調(diào)完成時(shí)注冊(cè)回調(diào)的任何方法。 這將導(dǎo)致如下所示的代碼,當(dāng)容器處于加載狀態(tài)時(shí),這是很糟糕的:
客戶端程序
//type 1 Future<String> f = service.foo(s); String s = f.get(); //blocks the thread, but at least others can run //... do something useful with the string...//type 2 Future<String> f = service.foo(s); while(!f.isDone()){try {Thread.sleep(100);} catch (InterruptedException e) {...} } String s = f.get(); //... do something useful with the string...這種代碼是不好的,因?yàn)樗鼘?dǎo)致線程阻塞,這意味著它們?cè)谶@段時(shí)間內(nèi)無(wú)法做任何有用的事情。 當(dāng)其他線程可以運(yùn)行時(shí),需要進(jìn)行上下文切換,這會(huì)浪費(fèi)時(shí)間和精力(有關(guān)成本或我以前的文章的結(jié)果,請(qǐng)參見(jiàn)這篇好文章)。 像這樣的代碼會(huì)使已經(jīng)處于負(fù)載狀態(tài)的服務(wù)器承受更大的負(fù)載,并停止運(yùn)行。
那么是否有可能使容器異步執(zhí)行方法,而編寫不需要阻塞線程的客戶端呢? 它是。 以下清單顯示了一個(gè)servlet。
AsyncServlet2.java
@WebServlet(urlPatterns = { "/AsyncServlet2" }, asyncSupported = true) public class AsyncServlet2 extends HttpServlet {@EJB private Service3 service;protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {final PrintWriter pw = response.getWriter();pw.write("<html><body>Started publishing with thread " + Thread.currentThread().getId() + "<br>");response.flushBuffer(); // send back to the browser NOWCompletableFuture<String> cf = new CompletableFuture<>();service.foo(cf);// since we need to keep the response open, we need to start an async contextfinal AsyncContext ctx = request.startAsync(request, response);cf.whenCompleteAsync((s, t)->{try {if(t!=null) throw t;pw.write("written in the future using thread " + Thread.currentThread().getId()+ "... service response is:");pw.write(s);pw.write("</body></html>");response.flushBuffer();ctx.complete(); // all done, free resources} catch (Throwable t2) { ...第1行聲明Servlet支持異步運(yùn)行-不要忘記這一點(diǎn)! 第8-10行開(kāi)始將數(shù)據(jù)寫入響應(yīng),但是有趣的是第13行中的調(diào)用異步服務(wù)方法的行。 我們沒(méi)有將Future用作返回類型,而是向其傳遞了CompletableFuture ,它用于將結(jié)果返回給我們。 怎么樣? 第16行會(huì)啟動(dòng)異步servlet上下文,因此我們?nèi)匀豢梢栽赿oGet方法返回后寫入響應(yīng)。 從第17行開(kāi)始,然后有效地在CompletableFuture上注冊(cè)了一個(gè)回調(diào),一旦CompletableFuture完成并返回結(jié)果,該回調(diào)將被調(diào)用。 這里沒(méi)有阻塞代碼–沒(méi)有線程被阻塞,沒(méi)有線程被輪詢,等待結(jié)果! 在負(fù)載下,服務(wù)器中的線程數(shù)可以保持最少,從而確保服務(wù)器可以高效運(yùn)行,因?yàn)樾枰^少的上下文切換。
服務(wù)實(shí)現(xiàn)如下所示:
Service3.java
@Stateless public class Service3 {@Asynchronouspublic void foo(CompletableFuture<String> cf) {// simulate some long running processThread.sleep(5000);cf.complete("bar");} }第7行確實(shí)很丑陋,因?yàn)樗鼤?huì)阻塞,但假裝這是代碼調(diào)用使用大多數(shù)Web服務(wù)客戶端和JDBC驅(qū)動(dòng)程序會(huì)阻塞的API調(diào)用在Internet或較慢的數(shù)據(jù)庫(kù)中遠(yuǎn)程部署的Web服務(wù)的代碼。 或者,使用異步驅(qū)動(dòng)程序 ,當(dāng)結(jié)果可用時(shí),完成第9行所示的將來(lái)。然后向CompletableFuture發(fā)出信號(hào),可以調(diào)用在先前清單中注冊(cè)的回調(diào)。
這不只是使用簡(jiǎn)單的回調(diào)嗎? 這肯定是相似的,下面的兩個(gè)清單顯示了使用自定義回調(diào)接口的解決方案。
AsyncServlet3.java
@WebServlet(urlPatterns = { "/AsyncServlet3" }, asyncSupported = true) public class AsyncServlet3 extends HttpServlet {@EJB private Service4 service;protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { ...final AsyncContext ctx = request.startAsync(request, response);service.foo(s -> { ...pw.write("</body></html>");response.flushBuffer();ctx.complete(); // all done, free resources ...Service4.java
@Stateless public class Service4 {@Asynchronouspublic void foo(Callback<String> c) {// simulate some long running processThread.sleep(5000);c.apply("bar");}public static interface Callback<T> {void apply(T t);} }同樣,在客戶端中,絕對(duì)沒(méi)有任何阻塞。 但是,由于以下原因,使用CompletableFuture的AsyncServlet2和Service3類的早期示例更好一些:
- CompletableFuture的API允許出現(xiàn)異常/失敗,
- CompletableFuture類提供用于異步執(zhí)行回調(diào)和相關(guān)任務(wù)的方法,即在fork-join池中,以便整個(gè)系統(tǒng)使用盡可能少的線程運(yùn)行,從而可以更有效地處理并發(fā)性,
- 可將CompletableFuture與其他對(duì)象結(jié)合使用,以便您可以注冊(cè)一個(gè)回調(diào),僅在多個(gè)CompletableFuture完成時(shí)才能調(diào)用該回調(diào),
- 回調(diào)不會(huì)立即被調(diào)用,而是池中有限數(shù)量的線程按它們應(yīng)運(yùn)行的順序?yàn)镃ompletableFuture的執(zhí)行提供服務(wù)。
在第一個(gè)清單之后,我提到異步EJB方法的實(shí)現(xiàn)存在一些問(wèn)題。 除了阻塞客戶端之外,另一個(gè)問(wèn)題是,根據(jù)EJB 3.1 Spec的 4.5.3章,客戶端事務(wù)上下文不會(huì)通過(guò)異步方法調(diào)用傳播。 如果您想使用@Asynchronous批注創(chuàng)建兩個(gè)可以并行運(yùn)行并在單個(gè)事務(wù)中更新數(shù)據(jù)庫(kù)的方法,那么它將不起作用。 這在某種程度上限制了@Asynchronous注釋的使用。
使用CompletableFuture ,您可能會(huì)認(rèn)為可以在同一個(gè)事務(wù)上下文中并行運(yùn)行多個(gè)任務(wù),方法是先在EJB中啟動(dòng)一個(gè)事務(wù),然后創(chuàng)建多個(gè)可運(yùn)行對(duì)象,并使用runAsync方法運(yùn)行它們,該方法在執(zhí)行中運(yùn)行它們池,然后注冊(cè)一個(gè)回調(diào)以使用allOf方法完成所有操作后allOf 。 但是您可能會(huì)因?yàn)槎喾N原因而失敗:
- 如果您使用容器管理的事務(wù),那么一旦導(dǎo)致事務(wù)開(kāi)始的EJB方法將控制權(quán)返回給容器,事務(wù)將被提交-如果那時(shí)您的期貨還沒(méi)有完成,您將不得不阻止運(yùn)行EJB方法的線程這樣它就等待并行執(zhí)行的結(jié)果,而阻塞正是我們要避免的,
- 如果運(yùn)行任務(wù)的單個(gè)執(zhí)行池中的所有線程都被阻塞,等待它們的數(shù)據(jù)庫(kù)調(diào)用應(yīng)答,那么您將有可能創(chuàng)建性能不佳的解決方案–在這種情況下,您可以嘗試使用非阻塞的異步驅(qū)動(dòng)程序 ,但不能每個(gè)數(shù)據(jù)庫(kù)都有這樣的驅(qū)動(dòng)程序,
- 一旦任務(wù)在不同的線程(例如執(zhí)行池中的線程)上運(yùn)行,線程本地存儲(chǔ)(TLS)就不再可用,因?yàn)檎谶\(yùn)行的線程與將工作提交到執(zhí)行池并進(jìn)行設(shè)置的線程不同在提交工作之前將值存入TLS,
- 諸如EntityManager 類的資源不是線程安全的 。 這意味著你無(wú)法通過(guò)EntityManager成提交給池的任務(wù),而每個(gè)任務(wù)需要得到它自己的保持EntityManager實(shí)例,而是創(chuàng)建EntityManager取決于TLS(見(jiàn)下文)。
讓我們用以下代碼更詳細(xì)地考慮TLS,該代碼顯示了一種異步服務(wù)方法,該服務(wù)方法試圖做幾件事以測(cè)試允許的操作。
Service5.java
@Stateless public class Service5 {@Resource ManagedExecutorService mes;@Resource EJBContext ctx;@PersistenceContext(name="asdf") EntityManager em;@Asynchronouspublic void foo(CompletableFuture<String> cf, final PrintWriter pw) {//pw.write("<br>inside the service we can rollback, i.e. we have access to the transaction");//ctx.setRollbackOnly();//in EJB we can use EMKeyValuePair kvp = new KeyValuePair("asdf");em.persist(kvp);Future<String> f = mes.submit(new Callable<String>() {@Overridepublic String call() throws Exception {try{ctx.setRollbackOnly();pw.write("<br/>inside executor service, we can rollback the transaction");}catch(Exception e){pw.write("<br/>inside executor service, we CANNOT rollback the transaction: " + e.getMessage());}try{//in task inside executor service we CANNOT use EMKeyValuePair kvp = new KeyValuePair("asdf");em.persist(kvp);pw.write("...inside executor service, we can use the EM");}catch(TransactionRequiredException e){pw.write("...inside executor service, we CANNOT use the EM: " + e.getMessage());} ...第12行沒(méi)有問(wèn)題,您可以回滾在容器調(diào)用EJB方法時(shí)在第9行自動(dòng)啟動(dòng)的事務(wù)。 但是該事務(wù)將不是可能由調(diào)用第9行的代碼啟動(dòng)的全局事務(wù)。第16行也沒(méi)有問(wèn)題,您可以使用EntityManager寫入由第9行開(kāi)始的事務(wù)內(nèi)部的數(shù)據(jù)庫(kù)。顯示了在不同線程上運(yùn)行代碼的另一種方式,即使用Java EE 7中引入的ManagedExecutorService 。但是,這在任何時(shí)候都依賴TLS時(shí)也會(huì)失敗,例如,第22行和第31行會(huì)導(dǎo)致異常,因?yàn)樵诘?行啟動(dòng)的事務(wù)無(wú)法定位,因?yàn)槭褂肨LS來(lái)定位,并且第21-35行中的代碼使用與第19行之前的代碼不同的線程運(yùn)行。
下一個(gè)清單顯示,第11-14行在CompletableFuture上注冊(cè)的完成回調(diào)也與第4-10行運(yùn)行在不同的線程中,因?yàn)樵诘?行的回調(diào)之外啟動(dòng)提交事務(wù)的調(diào)用將在第6行失敗再次參考圖13,因?yàn)榈?3行的調(diào)用在TLS中搜索當(dāng)前事務(wù),并且因?yàn)檫\(yùn)行第13行的線程與運(yùn)行第6行的線程不同,所以找不到事務(wù)。 實(shí)際上,下面的清單實(shí)際上有一個(gè)不同的問(wèn)題:處理對(duì)Web服務(wù)器的GET請(qǐng)求的線程運(yùn)行第JBAS010152: APPLICATION ERROR: transaction still active in request with status 0和11行,然后返回,此時(shí)JBoss日志JBAS010152: APPLICATION ERROR: transaction still active in request with status 0 –即使線程運(yùn)行第13行可以找到該事務(wù),它是否仍處于活動(dòng)狀態(tài)或容器是否已關(guān)閉它也值得懷疑。
AsyncServlet5.java
@Resource UserTransaction ut;@Override protected void doGet(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {ut.begin(); ...CompletableFuture<String> cf = new CompletableFuture<>();service.foo(cf, pw); ...cf.whenCompleteAsync((s, t)->{...ut.commit(); // => exception: "BaseTransaction.commit - ARJUNA016074: no transaction!"}); }事務(wù)顯然依賴于線程和TLS。 但這不僅僅是依賴TLS的事務(wù)。 以JPA為例,該JPA被配置為直接在TLS中存儲(chǔ)會(huì)話(即與數(shù)據(jù)庫(kù)的連接) ,或者被配置為將該會(huì)話的范圍限定為當(dāng)前的JTA事務(wù) ,而該事務(wù)又依賴于TLS。 或以使用從EJBContextImpl.getCallerPrincipal提取的Principal進(jìn)行安全性檢查為例,該P(yáng)rincipal對(duì)AllowedMethodsInformation.checkAllowed進(jìn)行調(diào)用,然后再調(diào)用使用TLS的CurrentInvocationContext并僅返回(如果在TLS中未找到任何上下文),而不是進(jìn)行適當(dāng)?shù)臋?quán)限檢查如第112行所示。
這些對(duì)TLS的依賴意味著,在使用CompletableFuture或Java SE fork-join池或其他線程池(無(wú)論是否由容器管理)時(shí),許多標(biāo)準(zhǔn)Java EE功能將不再起作用。
公平起見(jiàn),對(duì)于Java EE,我在這里所做的事情都是按設(shè)計(jì)的! 規(guī)范實(shí)際上禁止在EJB容器中啟動(dòng)新線程。 我記得十多年前我曾經(jīng)使用過(guò)舊版本的Websphere進(jìn)行過(guò)一次測(cè)試–啟動(dòng)線程會(huì)引發(fā)異常,因?yàn)槿萜鞔_實(shí)嚴(yán)格遵守規(guī)范。 這是有道理的:不僅因?yàn)榫€程數(shù)應(yīng)由容器管理,還因?yàn)镴ava EE對(duì)TLS的依賴意味著使用新線程會(huì)導(dǎo)致問(wèn)題。 從某種意義上講,這意味著使用CompletableFuture是非法的,因?yàn)樗褂昧瞬皇苋萜鞴芾淼木€程池(該池由JVM管理)。 使用Java SE的ExecutorService也是如此。 Java EE 7的ManagedExecutorService是一個(gè)特例-它是規(guī)范的一部分,因此您可以使用它,但是您必須了解這樣做的含義。 EJB上的@Asynchronous批注也是如此。
結(jié)果是可以在Java EE容器中編寫異步非阻塞應(yīng)用程序,但是您確實(shí)必須知道自己在做什么,并且可能必須手動(dòng)處理安全性和事務(wù)之類的事情,這確實(shí)是個(gè)問(wèn)題。首先使用Java EE容器的原因。
那么是否有可能編寫一個(gè)容器來(lái)消除對(duì)TLS的依賴以克服這些限制? 的確如此,但是解決方案不僅僅取決于Java EE。 該解決方案可能需要更改Java語(yǔ)言。 許多年前,在依賴注入之前,我曾經(jīng)寫過(guò)POJO服務(wù),它在方法之間傳遞了JDBC連接,即作為服務(wù)方法的參數(shù)。 我這樣做是為了可以在同一事務(wù)內(nèi)(即在同一連接上)創(chuàng)建新的JDBC語(yǔ)句。 我所做的與JPA或EJB容器所需要做的事情并沒(méi)有什么不同。 但是,現(xiàn)代框架沒(méi)有使用TLS作為顯式傳遞連接或用戶之類的東西的方式,而是使用TLS作為集中存儲(chǔ)“上下文”的位置,例如,連接,事務(wù),安全信息等。 只要您在同一線程上運(yùn)行,TLS就是隱藏此類樣板代碼的好方法。 讓我們假裝TLS從未被發(fā)明過(guò)。 我們?nèi)绾卧诓粡?qiáng)制每個(gè)方法都將其作為參數(shù)的情況下傳遞上下文? Scala的implicit關(guān)鍵字是一種解決方案。 您可以聲明參數(shù)可以隱式定位,這使編譯器將其添加到方法調(diào)用中成為問(wèn)題。 因此,如果Java SE引入了這種機(jī)制,則Java EE不需要依賴TLS,我們可以構(gòu)建真正的異步應(yīng)用程序,在該應(yīng)用程序中,容器可以像今天一樣通過(guò)檢查注釋來(lái)自動(dòng)處理事務(wù)和安全性! 也就是說(shuō),當(dāng)使用同步Java EE時(shí),容器會(huì)知道何時(shí)提交事務(wù)-在啟動(dòng)事務(wù)的方法調(diào)用結(jié)束時(shí)。 如果您異步運(yùn)行,則需要顯式關(guān)閉事務(wù),因?yàn)槿萜鞑辉僦篮螘r(shí)執(zhí)行此操作。
當(dāng)然,保持不阻塞的需要以及因此不依賴TLS的需求在很大程度上取決于當(dāng)前的方案。 我不相信我今天在這里描述的問(wèn)題是當(dāng)今的普遍問(wèn)題,而是它們是處理市場(chǎng)細(xì)分市場(chǎng)的應(yīng)用程序所面臨的問(wèn)題。 只需看一下似乎為優(yōu)秀的Java EE工程師提供的工作數(shù)量,而同步編程就是其中的標(biāo)準(zhǔn)。 但是,我確實(shí)相信,規(guī)模更大的IT軟件系統(tǒng)會(huì)變得越來(lái)越多,它們處理的數(shù)據(jù)越多,阻塞API就會(huì)成為一個(gè)問(wèn)題。 我還認(rèn)為,當(dāng)前硬件增長(zhǎng)速度的下降使這個(gè)問(wèn)題更加復(fù)雜。 有趣的是,Java是否a)是否需要跟上異步處理的趨勢(shì),以及b)Java平臺(tái)是否會(huì)采取行動(dòng)來(lái)固定對(duì)TLS的依賴。
翻譯自: https://www.javacodegeeks.com/2015/08/is-asynchronous-ejb-just-a-gimmick.html
ejb能調(diào)用另一個(gè)ejb嗎
總結(jié)
以上是生活随笔為你收集整理的ejb能调用另一个ejb吗_异步EJB只是一个Gi头吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 门市牌子备案(门市备案表)
- 下一篇: 控制备案管理人员(控制性备案)