2011-02-11 40 views
3

我們有一個servlet需要某些變量,如密碼,加密鹽等,不能永久保存在文件系統中。這是我們目前(摘要):如何在Tomcat環境中保存名稱/值對?

在初始化過程中,

  1. 一個Perl腳本設置ReadMode 2掩蓋標準輸出回波,會提示用戶輸入變量,過濾器已知的文件將它們放在並調用的Tomcat/bin中/ startup.sh

  2. 該Servlet init()方法從文件中讀取變量和刪除它(文件)。

問題: 戰的時候被重新編譯,Tomcat的嘗試部署(自動部署= true),這我們想要的。但數據文件不再存在,所以引發FileNotFoundException(正確)。

問:是否有一個屬性或一些HashMap中/提供給servlet的表,其中幾個變量可以手動啓動時儲存在哪裏?這個想法是init()可以在重新部署期間檢查數據文件是否不存在。謝謝, - MS。

回答

1

Tomcat沒有什麼可以做你想做的。如果將設置移動到tomcat JNDI樹中,則必須將用戶/名稱密碼組合放入server.xml或context.xml文件中。以下是您遇到的問題的幾種可能解決方案。

方法1:使用一個Tomcat監聽

如果你看看tomcat的頂部server.xml文件,你會看到幾個聽衆當Tomcat啓動了執行這些Java類。您可以創建自己的Tomcat監聽器,它從文件系統讀取密碼,刪除文件並以應用可訪問的方式存儲用戶名/密碼Comobo。 tomcat監聽器綁定到tomcat服務器的生命週期,所以自動重新部署應用程序不會導致您的tomcat監聽器類被重新加載。你的tomcat監聽器的代碼必須放入一個jar文件並放在CATALINA_HOME \ lib文件夾中。

監聽器可以使用靜態變量來存儲用戶名/密碼,並有一個靜態方法返回它們,這將工作,因爲監聽器將在tomcat應用程序的父類加載器中。這種方法的缺點是你的應用程序代碼依賴於tomcat資源監聽器的實現,在你的單元測試中你可能需要做一些額外的步驟來確保你的代碼可以被單元測試。

監聽器還可以訪問tomcat全局JNDI樹,並將用戶名和密碼組合放在那裏,然後您的應用必須具有context.xml文件才能使用ResourceLink元素使全局jndi條目可用於應用。你還需要做一些額外的工作來使這種方法適用於單元測試,因爲在JNDI中查找東西通常會使單元測試變得複雜。

如果你在這個項目中使用Spring,最好的辦法是在tomcat監聽器中使用靜態變量,然後使用自定義彈簧作用域將數據從tomcat監聽器中取出。這樣,您的應用就可以保持可測試性,您可以將用戶名/密碼組合插入任何需要它們的代碼。

選項2:使用servlet語境收聽

在這個選項中,你寫一個語境收聽,這將使您的應用程序通知每當應用程序啓動和停止。在啓動時使用這種方法,上下文偵聽器將運行並讀取密碼信息並刪除文件。如果啓動時密碼文件不存在,那麼上下文偵聽器必須有一種方法讓管理員重新生成文件。

選項3:使用JMX

創建的JMX MBean與JVM MBeanServer中進行註冊,然後用它來存儲用戶名/密碼組合。如果你從一個tomcat監聽器初始化這個MBean,你可以讓perl腳本遠程調用MBean並傳入用戶名/密碼組合。

希望這會有所幫助。

1

如果你想以編程方式處理它,我想你可能會尋找一個ServletContextListener。創建一個實現接口和代碼在contextInitialized(ServletContextEvent SCE)所需的功能 -method一個類,見here爲簡單的例子。

+0

是的,我們希望以編程方式處理這個問題。現在,ServletContext是在部署過程中創建的 - 這是否也意味着Servlet在重新部署時會被銷燬並重新創建?如果是這樣,使用setAttribute()或setInitParameter()保存的任何內容都將被刪除(不會在新的上下文中被讀取)。 – 2011-02-11 18:18:16

+0

是的,但是ServletContextListener接口也有一個方法(contextDestroyed)來監聽上下文的銷燬,並且可以實現將值保存到數據庫或平面文件中,然後偵聽器可以在一旦上下文被重新創建,contextInitialized方法。 ServletContextEvent -object被傳遞到兩個方法中,並且您可以從中訪問實際的ServletContext對象。 – esaj 2011-02-11 18:21:37

+0

是的,這將工作。本質上,您建議將值保存在數據庫或平面文件中,並在下一次調用init()時讀取它。但是,我們正在尋找一種解決方案,可以保留tomcat容器中的值。一種方法是編寫一個全新的Servlet來保存這些值,並使用localhost上的URL按需檢索它們,但這可能是一種矯枉過正。因此,tomcat不能被說服在內存中保存一些字符串以供不同的servlet訪問? – 2011-02-11 18:54:20

2

將您的易失性數據放入JNDI中。重新部署之間沒有清理JNDI。在init期間,您的servlet仍然可以執行相同的操作,以確保JNDI中的數據是最新的。

我不記得JNDI讀/寫是否是線程安全的,但事實並非如此,您可以始終放置一個線程安全的對象(例如ConcurrentHashMap),並且可以毫無問題地使用它。

編輯可通過MS:

========== 
restart.pl: 
========== 
ReadMode 2; print "\nEnter spice: "; chomp ($spice = <STDIN>); print "\n"; 
ReadMode 0; 
open (FP, ">$spicefile") or die "$0: Cannot write file $spicefile: $!\n"; 
print FP "$spice\n"; close (FP); 
system "bin/shutdown.sh";  sleep 8; 
system "bin/startup.sh";  sleep 8;  system "wget $tomcaturl"; 
system '/bin/rm -rf *spicefile*';    # delete wget output file 
foreach $sCount (1..10) {      # give it 10 more secs 
    sleep 1; 
    if (-f $spicefile) { 
     print "$0: Waiting on servlet to delete spicefile [$sCount]\n" 
    } else { 
     print "$0: Successful servlet initialization, no spicefile\n"; 
     exit 0 
}} 
print "\n$0: deleting file $spicefile ...\n"; # Error condition 
system "unlink $spicefile"; 
exit 1; 

=========== 
context.xml 
=========== 
    <Resource name="MyServlet/upsBean" auth="Container" type="packageName.UPSBean" 
       factory="org.apache.naming.factory.BeanFactory" readOnly="false"/> 

=================== 
app/WEB-INF/web.xml 
=================== 
    <listener> 
     <listener-class>packageName.DBInterface</listener-class> 
    </listener> 
    <resource-env-ref> 
    <description>Use spice as transferable during redeployment</description> 
    <resource-env-ref-name>MyServlet/upsBean</resource-env-ref-name> 
    <resource-env-ref-type>packageName.UPSBean</resource-env-ref-type> 
    </resource-env-ref> 

============== 
MyServlet.java: 
============== 
init() { 
     if (new File (spiceFile).exists())  # Same name in restart.pl 
       dbi = new DBInterface (spiceFile); 
     else 
       dbi = new DBInterface();  # Redeployment 
     otherInitializations (dbi.getSpice()); 
} 

================ 
DBInterface.java: 
================ 
public DBInterface() { 
     // Comment out following block if contextInitialized works 
     FileInputStream fin = new FileInputStream (safeDepositBox); 
     ObjectInputStream ois = new ObjectInputStream (fin); 
     UPSBean upsBean = (UPSBean) ois.readObject(); 
     ois.close(); 
     spice = upsBean.getSpice(); 
     dbiIndex = 2; 
     // do stuff with spice 
} 

public DBInterface (String spiceFileName) { 
     File file = new File (spiceFileName); 
     BufferedReader br = new BufferedReader (new FileReader (file)); 
     spice = br.readLine(); 
     br.close(); 
     file.delete(); 
     dbiIndex = 1; 

     // Delete following block if contextInitialized works 
     UPSBean upsBean = new UPSBean(); 
     upsBean.setSpice (spice); 
     FileOutputStream fout = new FileOutputStream (safeDepositBox); 
     ObjectOutputStream oos = new ObjectOutputStream (fout); 
     oos.writeObject (upsBean); 
     oos.flush(); 
     oos.close(); 
     // do stuff with spice and if it works, ... 
     // contextInitialized (null); 
} 

// Above is working currently, would like the following to work 

public void contextDestroyed(ServletContextEvent sce) { 
     System.setProperty ("spice", spice); 
     System.out.println ("[DBInterface" + dbiIndex + 
             "] Spice saved at " + 
         DateFormat.getDateTimeInstance (DateFormat.SHORT, 
             DateFormat.LONG).format (new Date())); 
} 

public void contextInitialized(ServletContextEvent sce) { 
     if (sce != null) { 
       spice = System.getProperty ("spice"); 
       System.out.println ("[DBInterface" + dbiIndex + 
             "] Spice retrieved at " + 
         DateFormat.getDateTimeInstance (DateFormat.SHORT, 
             DateFormat.LONG).format (new Date())); 
     } 
     // do stuff with spice 
} 

============ 
UPSBean.java: 
============ 
public class UPSBean implements Serializable { 
     private String spice = "parsley, sage, rosemary and thyme"; 
     public UPSBean() { } 
     public String getSpice() {  return spice; } 
     public void setSpice (String s) {  spice = s;  } 
} 

我想看看是否GET /工作的setProperty以上。試圖直接使用JNDI,但是當我使用contextDestroyed()中的資源MyServlet/upsBean設置Spice並嘗試在contextInitialized()中讀取它時,我得到一個null(對不起,我已經刪除了該代碼部分)。現在context.xml中的聲明以及resource-env-ref已經變得多餘。目前的解決方法是將序列化的實例保存在數據文件中(不太好)。

4

如何在將它們作爲系統屬性,當你調用startup.sh,即。 'bin/startup.sh -DencryptionSalt = foobar'?然後你可以在JVM期間調用System.getProperty(「encryptionSalt」)。

1

Tomcat在這裏沒有提供任何幫助。

那就是壞消息。

好消息是......這是一個過程問題,它可以使用servlet-API提供的工具,以及一些其他項目的Tomcat能夠提供解決。這裏是成分..

  • Tomcat提供容器啓動的能力,聽衆 - 通過Listener元素。當Tomcat啓動時使用這些跟蹤當容器啓動時,關閉等
  • 在Servlet API提供的ServletContextListener監聽webapp的啓動,關閉
  • - 你可以在系統屬性中的第一個設置JAVA_OPTS通過。但要小心 - 可以使用ps命令發現這些值。
  • 根據您的部署方法 - 您可以通過my-app.xml添加您的配置設置(其中my-app是要部署的應用程序的名稱)
  • 在CATALINA_BASE/conf/context.xml中 - 您可能還爲webapp設置init/env params以供所有webapps使用。
  • 如果您不使用安全管理器 - 您應該能夠設置/清除某些系統屬性。

鑑於以上所有 - 您可以從文件中讀取「受保護」值並將其寫入系統屬性。如果您不希望這些值始終以系統屬性的形式存在,那麼爲了增加保護,您可以使用ServletContextListener讀取系統屬性值並從系統屬性中刪除它們。然後在重新部署時 - 監聽器會在關閉時寫入重置系統屬性 - 所以當webapp重新啓動時 - 它們仍然存在。

所以啓動順序會是這樣

  • 管理員進入PW(現有流程),該文件被保存
  • 在web應用程序啓動 - ServletContextListner查找文件。如果存在 - 使用這些值
  • 在web應用程序停機 - 寫值System.properties
  • 在web應用程序啓動 - ServletContextListner看到文件丟失,並使用System.properties。然後刪除system.properties

通過以上 - 而不是寫密碼盤和臨時存儲作爲system.properties希望儘量減少任何攻擊向量可以重新部署。

祝你好運。

相關問題