原文来自于公众号三不猴
根底概念
对于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的实现原理。