原文来自于公众号三不猴
根底概念
对于 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<T> 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<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;
}
从新执行 LookupTest 会失去上面的后果
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>
<!-- 这个名字必须和数据源名字统一 -->
<res-ref-name>jdbc/MVNT1</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
获取数据源
数据源是获取和数据库的连贯,所以个别咱们能够创立 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<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;}
/* 通过数据源失去连贯,并放入线程中治理,再返回连贯对象 */
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 的实现原理。