基础概念
关于JNDI
Java命名和目录接口(Java Naming and Directory Interface,缩写JNDI),是Java的一个目录服务应用程序接口(API),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。
JNDI 与 Service Provider
JNDI 是统一抽象出来的接口,Service Provider是对接口的具体实现。如上面提到的默认的 JNDI Service Provider 有 RMI/LDAP 等等。
ObjectFactory
每一个 Service Provider 可能配有多个 Object Factory。Object Factory 用于将 Naming Service(如 RMI [Remote Method Invocation] / LDAP [Light Directory Access Portocol] )中存储的数据转换为 Java 中可表达的数据,如 Java 中的对象或 Java 中的基本数据类型。
开始手写
先启动一个本地RMI
public class MainTest {
public static void main(String[] args) throws Exception {
// 在本机 1999 端口开启 rmi registry,可以通过 JNDI API 来访问此 rmi registry
Registry registry = LocateRegistry.createRegistry(1999);
// 创建一个 Reference,第一个参数无所谓,第二个参数指定 Object Factory 的类名:
// 第三个参数是 codebase,表明如果客户端在 classpath 里面找不到
// jndiinj.EvilObjectFactory,则去 http://localhost:9999/ 下载
// 当然利用的时候这里应该是一个真正的 codebase 的地址
Reference ref = new Reference("test",
"jndiinj.EvilObjectFactory", "http://localhost:9999/");
// 因为只为只有实现 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 参数需要可控
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
这里似乎好像没什么用,只是获取到了他们类名而已,能不能存个对象进去呢?
Reference中有个add方法
public void add(RefAddr addr) {
addrs.addElement(addr);
}
这里添加的是一个RefAddr对象,RefAddr是一个抽象类,所以我们对这个拓展一下。
/**
* @author yhx
* @since 2021/9/10 5:33 下午
*/
public class BeanRef extends RefAddr {
private T t;
public BeanRef(T t) {
super(t.getClass().getName());
this.t = t;
}
@Override
public T getContent() {
return t;
}
}
我们对代码稍微修改一下,首先定义一个要存的对象,User类,这里去要注意的就是一定要实现序列化接口。
/**
* @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方法这里也省略
}
然后就是在创建完Reference对象以后加入下面的代码
BeanRef addr = new BeanRef<>(_getUser());
ref.add(addr);
private static User _getUser() {
User user = new User();
user.setId(0L);
user.setName("小明");
user.setPassword("***");
user.setEmail("[email protected]");
user.setPhone("1821732098");
return user;
}
重新执行LookupTest会得到下面的结果
Reference Class Name: user
Type: jndi.domain.User
Content: User[id=0, name='小明', password='***', email='[email protected]', phone='1821732098']
这样我们就完成了一个依赖查找的小功能,可能你会说:就这???
其实Tomcat容器也提供了JNDI,这样我们也不用手动启动一个RMI。
首先我们先初始化一个web工程,不要引spring的依赖。在webapp下的META-INF新建context.xml文件,模板在Tomcat的example目录下有。
然后我们在web.xml中新增下面的配置。注意名字要和context.xml中的名字一样。
DB Connection
jdbc/MVNT1
javax.sql.DataSource
Container
获取数据源
数据源是获取和数据库的连接,所以一般我们可以创建 ServletContext监听器(implements ServletContextListener)在 ServletContext 创建的时候就去获取数据源。如下:
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中配置的数据源并注入进来
* lookup:指定目录处的名称,此属性是固定的
* name:指定数据源的名称,即数据源处配置的name属性
*/
@Resource(lookup="java:/comp/env", name="jdbc/MVNT1")
/*将找到的数据源保存在此变量中,javax.sql.DataSource*/
private DataSource dataSource;
@Override
public void contextDestroyed(ServletContextEvent event) {
}
@Override
public void contextInitialized(ServletContextEvent event) {
/*测试数据源*/
//System.out.println(dataSource);
/*将数据源注入连接管理*/
ConnectionManager.setDadaSource(dataSource);
}
}
然后再通过数据源获取连接
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
/*连接对象的管理者*/
public final class ConnectionManager {
/*确保在每一个连接里是同一个连接对象,方便以后做事务的管理,针对每个线程创建一个独立的容器*/
/*使用泛型标准*/
private final static ThreadLocal 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;
}
/*通过数据源得到连接,并放入线程中管理,再返回连接对象*/
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来获取数据源(注意抛出异常)
Context cxt=new InitialContext();
//获取与逻辑名相关联的数据源对象
DataSource ds=(DataSource)cxt.lookup("java:comp/env/jdbc/MVNT1");
到此我们就把数据库DataSource对象使用jdni管理起来实现了一个依赖查找。
下篇我们将分析一下JNDI的实现原理。