原文来自于公众号三不猴

根底概念

对于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: userType: jndi.domain.UserContent: 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;@WebListenerpublic 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的实现原理。