乐趣区

关于spring:手写篇JNDI-实现依赖查找实现一个最简单spring

原文来自于公众号三不猴

根底概念

对于 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 的实现原理。

退出移动版