Tomcat 為每個在其上運行的 Web 應用都提供了一個 JNDI 的 InitialContext 實現實例,它與Java 企業版應用服務器所提供的對應類完全兼容。Java EE 標準在 /WEB-INF/web.xml 文件中提供了一系列標準元素,用來引用或定義資源。
可通過下列規范了解如何編寫針對 JNDI 的 API 以及 Java 企業版(Java EE)服務器所支持的功能,這也是 Tomcat 針對其所提供的服務而仿效的功能。
可在 Web 應用的部署描述符文件(/WEB-INF/web.xml)中使用下列元素來定義資源:
有了這些,Tomcat 就能利用適宜的資源工廠來創建資源,再也不需要其他配置信息了。Tomcat 將使用 /WEB-INF/web.xml 中的信息來創建資源。
另外,Tomcat 還提供了一些用于 JNDI 的特殊選項,它們沒有指定在 web.xml 中。比如,其中包括的 closeMethod 能在 Web 應用停止時,迅速清除 JNDI 資源;singleton 控制是否會在每次 JNDI 查找時創建資源的新實例。要想使用這些配置選項,資源必須指定在 Web 應用的 元素內,或者位于 $CATALINA_BASE/conf/server.xml 的 元素中。
如果 Tomcat 無法確定合適的資源工廠,并且/或者需要額外的配置信息,就必須在 Tomcat 創建資源之前指定好額外的具體配置。Tomcat 特定資源配置應位于 元素內,它可以指定在 $CATALINA_BASE/conf/server.xml,或者,最好放在每個 Web 應用的上下文 XML 文件中(META-INF/context.xml)。
要想完成 Tomcat 的特定資源配置,需要使用 元素中的下列元素:
以上這些元素內嵌于 元素中,而且是與特定應用相關聯的。
如果資源已經定義在 元素中,那就不必再在部署描述符文件中定義它了。但建議在部署描述符文件中保留相關項,以便記錄應用資源需求。
加入同樣一個資源名稱既被定義在 Web 應用部署描述符文件的 元素中,又被定義在 Web 應用的 元素的 元素內,那么只有當相應的 元素允許時(將其中的 override 屬性設為 true),部署描述符文件中的值才會優先對待。
Tomcat 為整個服務器維護著一個全局資源的獨立命名空間。這些全局資源配置在 $CATALINA_BASE/conf/server.xml 的 元素內。可以使用 將這些資源暴露給 Web 應用,以便在每一應用上下文中將其包含進來。
如果資源已經定義在 元素中,那就不必再在部署描述符文件中定義它了。但建議在部署描述符文件中保留相關項,以便記錄應用資源需求。
當 Web 應用最初部署時,就配置 InitialContext,使其可被 Web 應用的各組件所使用(只讀訪問)。JNDI 命名空間的 java:comp/env 部分中包含著所有的配置項與資源,所以訪問資源(在下例中,就是一個 JDBC 數據源)應按如下形式進行:
// 獲取環境命名上下文
Context initCtx = new?InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
// 查找數據源
DataSource ds = (DataSource)
??envCtx.lookup("jdbc/EmployeeDB");
// 分配并使用池中的連接
Connection conn = ds.getConnection();
... use?this?connection?to?access?the?database?...conn.close();
Tomcat 包含一系列資源工廠,能為 Web 應用提供各種服務,而且無需修改 Web 應用或部署描述符文件即能靈活配置(通過 元素)。下面所列出的每一小節都詳細介紹了標準資源工廠的配置與用途。
要想了解如何創建、安裝、配置和使用你自己的自定義資源工廠類,請參看添加自定義資源工廠。
注意:在標準資源工廠中,只有“JDBC DataSource”和“User Transaction”工廠可適用于其他平臺,而且這些平臺必須實現了 Java EE 規范。而其他所有標準資源工廠,以及你自己編寫的自定義資源工廠,則都是 Tomcat 所專屬的,不適用于其他容器。
該資源工廠能創建出任何符合標準 JavaBean 命名規范1的 Java 類的對象。如果工廠的 singleton 屬性被設為 false,那么每當對該項進行 lookup 時,資源工廠將會創建出適合的 bean 類的新實例。
1. 標準的 JavaBean 命名規范,比如:構造函數沒有任何參數,屬性設置器遵守 setFoo() 命名模式,等等。
使用該功能所需的步驟將在下文介紹。
創建 JavaBean 類
創建一個 JavaBean 類,在每次查找資源工廠時,就創建它的實例。比如,假設你創建了一個名叫 com.mycompany.MyBean 的 JavaBean 類,如下所示:
package com.mycompany;
public class MyBean {
private String foo = "Default Foo";
public String getFoo() {
return (this.foo);
}
public void setFoo(String foo) {
this.foo = foo;
}
private int bar = 0;
public int getBar() {
return (this.bar);
}
public void setBar(int bar) {
this.bar = bar;
}
}
接下來,修改 Web 應用部署描述符文件(/WEB-INF/web.xml),聲明 JNDI 名稱,并據此請求該 Bean 類的新實例。最簡單的方法是使用 元素,如下所示:
??Object factory for MyBean instances.
????bean/MyBeanFactory
? ? com.mycompany.MyBean
警告:一定要遵從 Web 應用部署描述符文件中 DTD 所需要的元素順序。關于這點,可參看Servlet 規范中的解釋。
資源引用的典型用例如下所示:
Context initCtx = new?InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
MyBean bean = (MyBean) envCtx.lookup("bean/MyBeanFactory");
writer.println("foo = "?+ bean.getFoo() + ", bar = "?+
???????????????bean.getBar());
為了配置 Tomcat 資源工廠,為 Web 應用的 元素添加下列元素:
?<Context ...>
...
<Resource name="bean/MyBeanFactory" auth="Container"
type="com.mycompany.MyBean"
factory="org.apache.naming.factory.BeanFactory"
bar="23"/>
...</Context>
注意這里的資源名稱,這里 bean/MyBeanFactory 必須跟部署描述符文件中所指定的值完全一樣。這里還初始化了 bar 屬性值,從而當返回新的 bean 時,setBar(23) 就會被調用。由于我們沒有初始化 foo 屬性(雖然我們完全可以這么做),所以 bean 依然采用構造函數中設置的默認值。
假設我們的 Bean 如下所示:
package?com.mycompany;
import?java.net.InetAddress;import?java.net.UnknownHostException;
public?class MyBean2
??private?InetAddress local = null;
??public InetAddress getLocal() {
????return?local;
??}
?public void setLocal(InetAddress ip) {
????local = ip;
??}
??public void setLocal(String localHost) {
????try?{
??????local = InetAddress.getByName(localHost);
????} catch?(UnknownHostException ex) {
????}
??}
?private?InetAddress remote = null;
?public InetAddress getRemote() {
????return?remote;
??}
?public void setRemote(InetAddress ip) {
????remote = ip;
??}
public void host(String remoteHost) {
????try?{
??????remote = InetAddress.getByName(remoteHost);
????} catch?(UnknownHostException ex) {
????}
??}
}
該 Bean 有兩個 InetAddress 類型的屬性。第一個屬性 local 還有第二種 setter 方法,傳入的是一個字符串參數。默認 Tomcat BeanFactory 會使用自動偵測到的 setter 方法,并將其參數類型作為屬性類型,然后拋出一個 NamingException(命名異常),因為它還沒有準備好將給定字符串值轉化為 InetAddress。我們可以讓 Tomcat BeanFactory 使用其他的 setter 方法,如下所示:
<Context ...>
...
<Resource name="bean/MyBeanFactory" auth="Container"
type="com.mycompany.MyBean2"
factory="org.apache.naming.factory.BeanFactory"
forceString="local"
local="localhost"/>
...</Context>
bean 屬性 remote 也可以從字符串中設置,但必須使用非標準方法 host。如下設置 local 和 remote:
<Context ...>
...
<Resource name="bean/MyBeanFactory" auth="Container"
type="com.mycompany.MyBean2"
factory="org.apache.naming.factory.BeanFactory"
forceString="local,remote=host"
local="localhost"
remote="tomcat.apache.org"/>
...</Context>
如上所示,可以利用逗號作分隔符,將多個屬性描述串聯在一起放在 forceString 中。每一屬性描述要么只包含屬性名,要么由 name = method 的結構所組成。對于前者的情況,BeanFactory 會直接調用屬性名的 setter 方法;而對于后者,則通過調用方法 method 來設置屬性名 name。對于 String 或基本類型,或者相應的基本包裝器類的屬性,不必使用 forceString。會自動偵測正確的 setter 并實施參數類型轉換。
UserDatabase 資源
UserDatabase 資源通常被配置成通過 UserDataBase Realm 所使用的全局資源。Tomcat 包含一個 UserDatabaseFactoory,能夠創建基于 XML 文件(通常是 tomcat-users.xml)的 UserDatabase 資源。
建立全局的 UserDataBase 資源的步驟如下。
XML 文件通常位于 $CATALINA_BASE/conf/tomcat-users.xml,但也可以放在文件系統中的任何位置。我們建議把該文件放在 $CATALINA_BASE/conf。典型的 XML 應如下所示:
<?xml version='1.0' encoding='utf-8'?><tomcat-users>
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/></tomcat-users>
聲明資源
接下來,修改 $CATALINA_BASE/conf/server.xml 來創建基于此文件的 UserDataBase 資源。如下所示:
<Resource name="UserDatabase"
auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"
readonly="false" />
屬性 pathname 可以采用絕對路徑或相對路徑。相對路徑意味著是相對于 $CATALINA_BASE。
readonly 屬性是可選屬性,如果不采用,則默認為 true。如果該 XML 文件可寫,那么當 Tomcat 開啟時,就會被修改。警告:當該文件被修改后,它會繼承 Tomcat 目前運行用戶的默認文件權限。所以要確保這樣做是否能保持應用的安全性。
配置 UserDatabase Realm 以便使用該資源,詳情可參看 Realm 配置文檔
很多 Web 應用都會把發送電子郵件作為系統的必備功能。JavaMail API 可以讓這一過程變得相對簡單些,但需要很多的配置細節,客戶端應用必須知道的(包括用于發送消息的 SMTP 主機的名稱)。
Tomcat 所包含的標準資源工廠可以為你創建 javax.mail.Session 會話實例,并且已經配置好連接到 SMTP 服務器上,從而使應用完全與電子郵件配置環境相隔離,不受后者變更的影響,無論何時,只需請求并接受預配置的會話即可。
所需步驟如下所示。
首先應該做的是修改 Web 應用的部署描述符文件(/WEB-INF/web.xml),聲明 JNDI 名稱以便借此查找預配置會話。按照慣例,所有這樣的名字都應該解析到 mail 子上下文(相對于標準的 java:comp/env 命名上下文而言的,這個命名上下文是所有資源工廠的基準。)典型的 web.xml 項應該如下所示:
????Resource reference to a factory for javax.mail.Session
????instances that may be used for sending electronic mail
????messages, preconfigured to connect to the appropriate
????SMTP server.
mail/Session
????javax.mail.Session
????Container
??
警告:一定要遵從 Web 應用部署描述符文件中 DTD 所需要的元素順序。關于這點,可參看Servlet 規范中的解釋。
資源引用的典型用例如下所示:
Context initCtx = new?InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
Session session = (Session) envCtx.lookup("mail/Session");
Message message = new?MimeMessage(session);
message.setFrom(new?InternetAddress(request.getParameter("from")));
InternetAddress to[] = new?InternetAddress[1];
to[0] = new?InternetAddress(request.getParameter("to"));
message.setRecipients(Message.RecipientType.TO, to);
message.setSubject(request.getParameter("subject"));
message.setContent(request.getParameter("content"), "text/plain");
Transport.send(message);
注意,該應用所用的資源引用名與 Web 應用部署符中聲明的完全相同。這是與下文會講到的 元素里所配置的資源工廠相匹配的。
為了配置 Tomcat 的資源工廠,在 元素中添加以下元素:
<Context ...>
...
<Resource name="mail/Session" auth="Container"
type="javax.mail.Session"
mail.smtp.host="localhost"/>
...</Context>
注意,資源名(在這里,是 mail/Session)必須與 Web 應用部署描述符文件中所指定的值相匹配。對于 mail.smtp.host 參數值,可以用為你的網絡提供 SMTP 服務的服務器來自定義。
額外的資源屬性與值將轉換成相關的屬性及值,并被傳入 javax.mail.Session.getInstance(java.util.Properties),作為參數集 java.util.Properties 中的一部分。除了 JavaMail 規范附件A中所定義的屬性之外,個別的提供者可能還支持額外的屬性。
如果資源配置中包含 password 屬性,以及 mail.smtp.user 或 mail.user 屬性,那么 Tomcat 資源工廠將配置并添加 javax.mail.Authenticator 到郵件會話中。
下載 JavaMail API
解壓縮文件分發包,將 mail.jar 放到 $CATALINA_HOME/lib 中,從而使 Tomcat 能在郵件會話資源初始化期間能夠使用它。注意:不能將這一文件同時放在 $CATALINA_HOME/lib 和 Web 應用的 /lib 文件夾中,否則就會出錯,只能將其放在 $CATALINA_HOME/lib 中。
為了能讓 Tomcat 使用這個額外的 jar 文件,必須重啟 Tomcat 實例。
Tomcat 中的 /examples 應用中帶有一個使用該資源工廠的范例。可以通過“JSP 范例”的鏈接來訪問它。實際發送郵件的 servlet 的源代碼則位于 /WEB-INF/classes/SendMailServlet.java 中。
警告:默認配置在 localhost 的 端口 25 上的 SMTP 服務器。如果實際情況不符,則需要編輯該 Web 應用的 元素,將 mail.smtp.host 參數的值修改為你的網絡上的 SMTP 服務器的主機名。
許多 Web 應用都需要 JDBC 驅動來訪問數據庫,以便能夠支持該應用所需要的功能。Java EE 平臺規范要求 Java EE 應用服務器針對該需求提供一個 DataSource 實現(也就是說,用于 JDBC 連接的連接池)。Tomcat 就能提供同樣的支持,因此在 Tomcat 上,由于使用了這種服務,基于數據庫的應用可以不用修改就能移植到任何 Java EE 服務器上運行。
注意:Tomcat 默認所支持的數據源是基于 Commons 項目 的 DBCP 連接池。但也可以通過編寫自定義的資源工廠,使用其他實現了 javax.sql.DataSource 的連接池,詳見下文。
使用 JDBC 數據源的 JNDI 資源工廠需要一個適合的 JDBC 驅動,要求它既能被 Tomcat 內部類所使用,也能被你的 Web 應用所使用。這很容易實現,只需將驅動的 JAR 文件(或多個文件)安裝到 $CATALINA_HOME/lib 目錄中即可,這樣資源工廠和應用就都能使用了這一驅動了。
下一步,修改 Web 應用的部署描述符文件(/WEB-INF/web.xml),聲明 JNDI 名稱以便借此查找預配置的數據源。按照慣例,所有這樣的名稱都應該在jdbc 子上下文中聲明(這個“子”是相對于標準的 java:comp/env 環境命名上下文而言的。java:comp/env 環境命名上下文是所有資源工廠的根引用)。典型的 web.xml 文件應如下所示:
????Resource reference to a factory for java.sql.Connection
????instances that may be used for talking to a particular
????database that is configured in the
????configurartion for the web application.
????jdbc/EmployeeDB
????javax.sql.DataSource
????Container
警告:一定要遵從 Web 應用部署描述符文件中 DTD 所需要的元素順序。關于這點,可參看Servlet 規范中的解釋。
資源引用的典型用例如下所示:
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
DataSource ds = (DataSource)
??envCtx.lookup("jdbc/EmployeeDB");
Connection conn = ds.getConnection();
... use?this connection?to?access?the database?...
conn.close();
注意,該應用所用的資源引用名與 Web 應用部署符中聲明的完全相同。這是與下文會講到的 元素里所配置的資源工廠相匹配的。
配置 Tomcat 資源工廠
為了配置 Tomcat 的資源工廠,在 元素中添加以下元素:
<Context ...>
...
<Resource name="jdbc/EmployeeDB"
auth="Container"
type="javax.sql.DataSource"
username="dbusername"
password="dbpassword"
driverClassName="org.hsql.jdbcDriver"
url="jdbc:HypersonicSQL:database"
maxTotal="8"
maxIdle="4"/>
...</Context>
注意上述代碼中的資源名(這里是 jdbc/EmployeeDB)必須跟 Web 應用部署描述符文件中指定的值相同。
該例假定使用的是 HypersonicSQL 數據庫 JDBC 驅動。可自定義 driverClassName 和 driverName 參數,使其匹配實際數據庫的 JDBC 驅動與連接 URL。
Tomcat 標準數據源資源工廠
(org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory)的配置屬性如下:
還有一些額外的用來驗證連接的屬性,如下所示:
可選的 evictor thread 會清除空閑較長時間的連接,從而縮小連接池。evictor thread 不受 minIdle 屬性值的空閑。注意,如果你只想通過配置的 minIdle 屬性來縮小連接池,那么不需要使用 evictor thread。
默認 evictor 是禁用的,另外,可以使用下列屬性來配置它:
另一個可選特性是對廢棄連接的移除。如果應用很久都不把某個連接返回給連接池,那么該連接就被稱為廢棄連接。連接池就會自動關閉這樣的連接,并將其從池中移除。這么做是為了防止應用泄露連接。
默認是禁止廢棄連接的,可以通過下列屬性來配置:
最后再介紹一些可以對連接池行為進行進一步微調的屬性:
要想更詳細地了解這些屬性,請參閱 commons-dbcp 文檔。
如果標準資源工廠無法滿足你的需求,你還可以自己編寫資源工廠,然后將其集成到 Tomcat 中,在 Web 應用的 元素中配置該工廠的使用方式。在下面的范例中,我們將創建一個資源工廠,只懂得如何 com.mycompany.MyBean bean
你必須編寫一個類來實現 JNDI 服務提供者 javax.naming.spi.ObjectFactory 接口。每次 Web 應用在綁定到該工廠(假設該工廠配置中,singleton = "false")的上下文項上調用 lookup() 時,就會調用 getObjectInstance() 方法,該方法有如下這些參數:
package?com.mycompany;
import?java.util.Enumeration;import?java.util.Hashtable;import?javax.naming.Context;import?javax.naming.Name;import?javax.naming.NamingException;import?javax.naming.RefAddr;import?javax.naming.Reference;import?javax.naming.spi.ObjectFactory;
public?class MyBeanFactory implements ObjectFactory {
??public Object getObjectInstance(Object obj,
??????Name name, Context nameCtx, Hashtable environment)
??????throws NamingException {
??????// Acquire an instance of our specified bean class
??????MyBean bean = new?MyBean();
??????// Customize the bean properties from our attributes
??????Reference ref = (Reference) obj;
??????Enumeration addrs = ref.getAll();
??????while?(addrs.hasMoreElements()) {
??????????RefAddr addr = (RefAddr) addrs.nextElement();
??????????String name = addr.getType();
??????????String value = (String) addr.getContent();
??????????if?(name.equals("foo")) {
??????????????bean.setFoo(value);
??????????} else?if?(name.equals("bar")) {
??????????????try?{
??????????????????bean.setBar(Integer.parseInt(value));
??????????????} catch?(NumberFormatException e) {
??????????????????throw?new?NamingException("Invalid 'bar' value "?+ value);
??????????????}
??????????}
??????}
??????// Return the customized instance
??????return?(bean);
??}
}
// Acquire an instance of our specified bean class 需要我們所指定的bean 類的一個實例
// Customize the bean properties from our attributes 從屬性中自定義 bean 屬性。
// Return the customized instance 返回自定義實例
在上例中,無條件地創建了 com.mycompany.MyBean 類的一個新實例, 并根據工廠配置中的 元素(下文詳述)包括的參數來填充這一實例。你應該記住,必須忽略任何名為 factory 的參數——參數應該用來指定工廠類自身的名字(com.mycompany.MyBeanFactory),而不是配置的 bean 屬性。
關于 ObjectFactory 的更多信息,可參見 JNDI 服務提供者接口(SPI)規范。
首先參照一個 $CATALINA_HOME/lib 目錄中包含所有 JAR 文件的類路徑來編譯該類。完成之后,將這個工廠類以及相應的 Bean 類解壓縮到 $CATALINA_HOME/lib,或者 $CATALINA_HOME/lib 內的一個 JAR 文件中。這樣,所需的類文件就能被 Catalina 內部資源與 Web 應用看到了。
下一步,修改 Web 應用的部署描述符文件(/WEB-INF/web.xml),聲明 JNDI 名稱以便借此請求該 bean 的新實例。最簡單的方法是使用 元素,如下所示:
?Object factory for MyBean instances.
????bean/MyBeanFactory
????com.mycompany.MyBean
警告:一定要遵從 Web 應用部署描述符文件中 DTD 所需要的元素順序。關于這點,可參看Servlet 規范中的解釋。
使用資源
資源引用的典型用例如下所示:
Context initCtx = new?InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
MyBean bean = (MyBean) envCtx.lookup("bean/MyBeanFactory");
writer.println("foo = "?+ bean.getFoo() + ", bar = "?+
???????????????bean.getBar());
為了配置 Tomcat 的資源工廠,在 元素中添加以下元素:
?<Context ...>
...
<Resource name="bean/MyBeanFactory" auth="Container"
type="com.mycompany.MyBean"
factory="com.mycompany.MyBeanFactory"
singleton="false"
bar="23"/>
...</Context>
注意上述代碼中的資源名(這里是 bean/MyBeanFactory)必須跟 Web 應用部署描述符文件中指定的值相同。另外,我們還初始化了 bar 屬性值,從而在返回新 bean 時,導致 setBar(23) 被調用。由于我們沒有初始化 foo 屬性(雖然完全可以這樣做),所以 bean 將含有構造函數所定義的各種默認值。
另外,你肯定能注意到,從應用開發者的角度來看,資源環境引用的聲明,以及請求新實例的編程方式,都跟通用 JavaBean 資源(Generic JavaBean Resources)范例所用方式如出一轍。這揭示了使用 JNDI 資源封裝功能的一個優點:只要維持兼容的 API,無需修改使用資源的應用,只需改變底層實現。