基礎(chǔ)概念
關(guān)于JNDI
Java命名和目錄接口(Java Naming and Directory Interface声诸,縮寫JNDI),是Java的一個目錄服務應用程序接口(API)夜矗,它提供一個目錄系統(tǒng)沈条,并將服務名稱與對象關(guān)聯(lián)起來,從而使得開發(fā)人員在開發(fā)過程中可以使用名稱來訪問對象拖叙。
JNDI 與 Service Provider
JNDI 是統(tǒng)一抽象出來的接口氓润,Service Provider是對接口的具體實現(xiàn)。如上面提到的默認的 JNDI Service Provider 有 RMI/LDAP 等等薯鳍。
ObjectFactory
每一個 Service Provider 可能配有多個 Object Factory旺芽。Object Factory 用于將 Naming Service(如 RMI [Remote Method Invocation] / LDAP [Light Directory Access Portocol] )中存儲的數(shù)據(jù)轉(zhuǎn)換為 Java 中可表達的數(shù)據(jù),如 Java 中的對象或 Java 中的基本數(shù)據(jù)類型辐啄。
開始手寫
- 先啟動一個本地RMI
public class MainTest {
public static void main(String[] args) throws Exception {
// 在本機 1999 端口開啟 rmi registry,可以通過 JNDI API 來訪問此 rmi registry
Registry registry = LocateRegistry.createRegistry(1999);
// 創(chuàng)建一個 Reference运嗜,第一個參數(shù)無所謂壶辜,第二個參數(shù)指定 Object Factory 的類名:
// 第三個參數(shù)是 codebase,表明如果客戶端在 classpath 里面找不到
// jndiinj.EvilObjectFactory担租,則去 http://localhost:9999/ 下載
// 當然利用的時候這里應該是一個真正的 codebase 的地址
Reference ref = new Reference("test",
"jndiinj.EvilObjectFactory", "http://localhost:9999/");
// 因為只為只有實現(xiàn) Remote 接口的對象才能綁定到 rmi registry 里面去
ReferenceWrapper wrapper = new ReferenceWrapper(ref);
registry.bind("evil", wrapper);
}
}
- 連接本地客戶端
public class LookupTest {
public static void main(String[] args) throws NamingException {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Context ctx = new InitialContext();
// ctx.lookup 參數(shù)需要可控
Object lookup = ctx.lookup("rmi://localhost:1999/evil");
System.out.println(lookup);
}
}
我們先啟動MainTest類然后啟動LookupTest類最后看看輸出
- Connected to the target VM, address: '127.0.0.1:49254', transport: 'socket'
Reference Class Name: test
這里似乎好像沒什么用砸民,只是獲取到了他們類名而已,能不能存?zhèn)€對象進去呢奋救?
Reference中有個add方法
public void add(RefAddr addr) {
addrs.addElement(addr);
}
這里添加的是一個RefAddr對象岭参,RefAddr是一個抽象類,所以我們對這個拓展一下尝艘。
/**
* @author yhx
* @since 2021/9/10 5:33 下午
*/
public class BeanRef<T> extends RefAddr {
private T t;
public BeanRef(T t) {
super(t.getClass().getName());
this.t = t;
}
@Override
public T getContent() {
return t;
}
}
我們對代碼稍微修改一下演侯,首先定義一個要存的對象,User類背亥,這里去要注意的就是一定要實現(xiàn)序列化接口秒际。
/**
* @author yhx
* @since 2021/9/10 3:21 下午
*/
public class User implements Serializable {
private Long id;
private String name;
private String password;
private String email;
private String phone;
// 省略set get方法,為了方便觀察我們再定義一個String方法這里也省略
}
然后就是在創(chuàng)建完Reference對象以后加入下面的代碼
BeanRef<User> addr = new BeanRef<>(_getUser());
ref.add(addr);
private static User _getUser() {
User user = new User();
user.setId(0L);
user.setName("小明");
user.setPassword("***");
user.setEmail("123@qq.com");
user.setPhone("1821732098");
return user;
}
重新執(zhí)行LookupTest會得到下面的結(jié)果
Reference Class Name: user
Type: jndi.domain.User
Content: User[id=0, name='小明', password='***', email='123@qq.com', phone='1821732098']
這樣我們就完成了一個依賴查找的小功能狡汉,可能你會說:就這娄徊??盾戴?
其實Tomcat容器也提供了JNDI寄锐,這樣我們也不用手動啟動一個RMI。
首先我們先初始化一個web工程尖啡,不要引spring的依賴橄仆。在webapp下的META-INF新建context.xml文件,模板在Tomcat的example目錄下有可婶。
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
auth="Container"
driverClassName="com.mysql.jdbc.Driver"
maxIdle="30"
maxTotal="50"
maxWaitMillis="-1"
name="jdbc/MVNT1"
username="root"
password="yhx"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/learning?useSSL=true"/>
</Context>
然后我們在web.xml中新增下面的配置沿癞。注意名字要和context.xml中的名字一樣。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
'-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
'http://java.sun.com/j2ee/dtds/web-app_2_3.dtd'>
<web-app>
<resource-ref>
<description>DB Connection</description>
<!-- 這個名字必須和數(shù)據(jù)源名字一致 -->
<res-ref-name>jdbc/MVNT1</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
獲取數(shù)據(jù)源
數(shù)據(jù)源是獲取和數(shù)據(jù)庫的連接矛渴,所以一般我們可以創(chuàng)建 ServletContext監(jiān)聽器(implements ServletContextListener)在 ServletContext 創(chuàng)建的時候就去獲取數(shù)據(jù)源椎扬。如下:
import javax.annotation.Resource;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;
@WebListener
public class WebContextListener implements ServletContextListener {
/*
* 使用Resource注解成員變量惫搏,通過名字查找server.xml中配置的數(shù)據(jù)源并注入進來
* lookup:指定目錄處的名稱,此屬性是固定的
* name:指定數(shù)據(jù)源的名稱蚕涤,即數(shù)據(jù)源處配置的name屬性
*/
@Resource(lookup="java:/comp/env", name="jdbc/MVNT1")
/*將找到的數(shù)據(jù)源保存在此變量中筐赔,javax.sql.DataSource*/
private DataSource dataSource;
@Override
public void contextDestroyed(ServletContextEvent event) {
}
@Override
public void contextInitialized(ServletContextEvent event) {
/*測試數(shù)據(jù)源*/
//System.out.println(dataSource);
/*將數(shù)據(jù)源注入連接管理*/
ConnectionManager.setDadaSource(dataSource);
}
}
然后再通過數(shù)據(jù)源獲取連接
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
/*連接對象的管理者*/
public final class ConnectionManager {
/*確保在每一個連接里是同一個連接對象,方便以后做事務的管理揖铜,針對每個線程創(chuàng)建一個獨立的容器*/
/*使用泛型標準*/
private final static ThreadLocal<Connection> LOCAL=new ThreadLocal();
private static DataSource dataSource;
public static void setDadaSource(DataSource dataSource) {
/*不能使用this*/
ConnectionManager.dataSource=dataSource;
}
/*返回連接對象*/
public static Connection getConnection() throws SQLException {
/*獲取連接對象*/
Connection conn=LOCAL.get();
if(null != conn) {
return conn;
}
/*通過數(shù)據(jù)源得到連接茴丰,并放入線程中管理,再返回連接對象*/
conn=dataSource.getConnection();
LOCAL.set(conn);
return conn;
}
/*釋放連接對象*/
public static void release() {
Connection conn=LOCAL.get();
if(null != conn) {
DBUtil.release(conn);
LOCAL.remove();
}
}
}
也可以直接使用Context來獲取數(shù)據(jù)源(注意拋出異常)
Context cxt=new InitialContext();
//獲取與邏輯名相關(guān)聯(lián)的數(shù)據(jù)源對象
DataSource ds=(DataSource)cxt.lookup("java:comp/env/jdbc/MVNT1");
到此我們就把數(shù)據(jù)庫DataSource對象使用jdni管理起來實現(xiàn)了一個依賴查找天吓。