高手们:
我想文的是Tomcat在创建数据库连接池时会把DataSource加到Context的java:comp/env/段中,这个地方是如何实现的。我自己可不可以模拟出来。给个思路吧。

解决方案 »

  1.   

    首先在tomcat里面建立连接池,如果期的名字叫"jdbc/oracle",然后再程序里面这样获得
    InitialContext ctx = new InitialContext();
    DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/oracle");
      

  2.   

    应该是向web.xml写入,
    即用java向文件中写入文本,
    你可以用domj来解析xml,然后向web.xml文件中加入一个元素!!
      

  3.   

    同意 一楼,二楼的说法!
    写的web.xml
      

  4.   

    谢谢各位的回答。
    我是想建立一个自己的Context,然后我往这个context中加入服务如一个DataSource。
    用如下方法:
    ctx.bind(DSName,source);
    其中ctx是Context,DSName是一String("jdbc/oracle"),source是一个DataSource。
    然后我通过InitialContext ctx = new InitialContext();
    DataSource ds = (DataSource) ctx.lookup(DSName2);
    方式来取得我的source这个对象。
    这里我的DSName.equals(DSName2)才可以取得我要的对象。
    在tomcat中,如 cuij7718(沸腾的音乐 http://sunfruit.blogchina.com) 所说,
    我设的是"jdbc/oracle"
    而取的时候却要用"java:comp/env/jdbc/oracle"
    我是想问tomcat是如何实现这样的动作的。
    我自己要做的话该在bind前做什么样的动作。
    谢谢。
      

  5.   

    你的意思是自己写连接池??
    也就是不用java:comp/env/?
    这些设计到jndi和datasource的建立相关知识!
    首先建立自己的jndi目录服务,这个主要是实现Context和InitialContextFactory两个接口!
    其次就是Datasource的实现!
    大部分用数据源的连接池都要实现DataSource这个接口!
    具体的你看看这两个方面的知识!
      

  6.   

    在server.xml文件中配置连接池...网上有代码的...
      

  7.   

    首先,我们先定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数,具体定义如下:
    public class ConnectionParam implements Serializable
    {
    private String driver; //数据库驱动程序
    private String url; //数据连接的URL
    private String user; //数据库用户名
    private String password; //数据库密码
    private int minConnection = 0; //初始化连接数
    private int maxConnection = 50; //最大连接数
    private long timeoutValue = 600000;//连接的最大空闲时间
    private long waitTime = 30000; //取连接的时候如果没有可用连接最大的等待时间 
    其次是连接池的工厂类ConnectionFactory,通过该类来将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,具体代码如下:
    /**
     * 连接池类厂,该类常用来保存多个数据源名称合数据库连接池对应的哈希
     * @author liusoft
     */
    public class ConnectionFactory
    {
    //该哈希表用来保存数据源名和连接池对象的关系表
    static Hashtable connectionPools = null;
    static{
    connectionPools = new Hashtable(2,0.75F);

    /**
     * 从连接池工厂中获取指定名称对应的连接池对象
     * @param dataSource 连接池对象对应的名称
     * @return DataSource 返回名称对应的连接池对象
     * @throws NameNotFoundException 无法找到指定的连接池
     */
    public static DataSource lookup(String dataSource) 
    throws NameNotFoundException
    {
    Object ds = null;
    ds = connectionPools.get(dataSource);
    if(ds == null || !(ds instanceof DataSource))
    throw new NameNotFoundException(dataSource);
    return (DataSource)ds;
    } /**
     * 将指定的名字和数据库连接配置绑定在一起并初始化数据库连接池
     * @param name 对应连接池的名称
     * @param param 连接池的配置参数,具体请见类ConnectionParam
     * @return DataSource 如果绑定成功后返回连接池对象
     * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
     * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类
     * @throws IllegalAccessException 连接池配置中的驱动程序类有误
     * @throws InstantiationException 无法实例化驱动程序类
     * @throws SQLException 无法正常连接指定的数据库
     */
    public static DataSource bind(String name, ConnectionParam param)
    throws NameAlreadyBoundException,ClassNotFoundException,
    IllegalAccessException,InstantiationException,SQLException
    {
    DataSourceImpl source = null;
    try{
    lookup(name);
    throw new NameAlreadyBoundException(name);
    }catch(NameNotFoundException e){
    source = new DataSourceImpl(param);
    source.initConnection();
    connectionPools.put(name, source);
    }
    return source;
    }
    /**
     * 重新绑定数据库连接池
     * @param name 对应连接池的名称
     * @param param 连接池的配置参数,具体请见类ConnectionParam
     * @return DataSource 如果绑定成功后返回连接池对象
     * @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
     * @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类
     * @throws IllegalAccessException 连接池配置中的驱动程序类有误
     * @throws InstantiationException 无法实例化驱动程序类
     * @throws SQLException 无法正常连接指定的数据库
     */
    public static DataSource rebind(String name, ConnectionParam param)
    throws NameAlreadyBoundException,ClassNotFoundException,
    IllegalAccessException,InstantiationException,SQLException
    {
    try{
    unbind(name);
    }catch(Exception e){}
    return bind(name, param);
    }
    /**
     * 删除一个数据库连接池对象
     * @param name
     * @throws NameNotFoundException
     */
    public static void unbind(String name) throws NameNotFoundException
    {
    DataSource dataSource = lookup(name);
    if(dataSource instanceof DataSourceImpl){
    DataSourceImpl dsi = (DataSourceImpl)dataSource;
    try{
    dsi.stop();
    dsi.close();
    }catch(Exception e){
    }finally{
    dsi = null;
    }
    }
    connectionPools.remove(name);
    }

      

  8.   

    ConnectionFactory主要提供了用户将将连接池绑定到一个具体的名称上以及取消绑定的操作。使用者只需要关心这两个类即可使用数据库连接池的功能。下面我们给出一段如何使用连接池的代码:
    String name = "pool";
    String driver = " sun.jdbc.odbc.JdbcOdbcDriver ";
    String url = "jdbc:odbc:datasource";
    ConnectionParam param = new ConnectionParam(driver,url,null,null);
    param.setMinConnection(1);
    param.setMaxConnection(5);
    param.setTimeoutValue(20000);
    ConnectionFactory.bind(name, param);
    System.out.println("bind datasource ok.");
    //以上代码是用来登记一个连接池对象,该操作可以在程序初始化只做一次即可
    //以下开始就是使用者真正需要写的代码
    DataSource ds = ConnectionFactory.lookup(name);
    try{
    for(int i=0;i<10;i++){
    Connection conn = ds.getConnection();
    try{
    testSQL(conn, sql);
    }finally{
    try{
    conn.close();
    }catch(Exception e){}
    }
    }
    }catch(Exception e){
    e.printStackTrace();
    }finally{
    ConnectionFactory.unbind(name);
    System.out.println("unbind datasource ok.");
    System.exit(0);

    从使用者的示例代码就可以看出,我们已经解决了常规连接池产生的两个问题。但是我们最最关心的是如何解决接管close方法的办法。接管工作主要在ConnectionFactory中的两句代码:
    source = new DataSourceImpl(param);
    source.initConnection(); 
    DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection
    /**
     * @see javax.sql.DataSource#getConnection(String,String)
     */
    public Connection getConnection(String user, String password) throws SQLException 
    {
    //首先从连接池中找出空闲的对象
    Connection conn = getFreeConnection(0);
    if(conn == null){
    //判断是否超过最大连接数,如果超过最大连接数
    //则等待一定时间查看是否有空闲连接,否则抛出异常告诉用户无可用连接
    if(getConnectionCount() >= connParam.getMaxConnection())
    conn = getFreeConnection(connParam.getWaitTime());
    else{//没有超过连接数,重新获取一个数据库的连接
    connParam.setUser(user);
    connParam.setPassword(password);
    Connection conn2 = DriverManager.getConnection(connParam.getUrl(), 
    user, password);
    //代理将要返回的连接对象
    _Connection _conn = new _Connection(conn2,true);
    synchronized(conns){
    conns.add(_conn);
    }
    conn = _conn.getConnection();
    }
    }
    return conn;
    }
    /**
     * 从连接池中取一个空闲的连接
     * @param nTimeout 如果该参数值为0则没有连接时只是返回一个null
     * 否则的话等待nTimeout毫秒看是否还有空闲连接,如果没有抛出异常
     * @return Connection
     * @throws SQLException
     */
    protected synchronized Connection getFreeConnection(long nTimeout) 
    throws SQLException
    {
    Connection conn = null;
    Iterator iter = conns.iterator();
    while(iter.hasNext()){
    _Connection _conn = (_Connection)iter.next();
    if(!_conn.isInUse()){
    conn = _conn.getConnection();
    _conn.setInUse(true);
    break;
    }
    }
    if(conn == null && nTimeout > 0){
    //等待nTimeout毫秒以便看是否有空闲连接
    try{
    Thread.sleep(nTimeout);
    }catch(Exception e){}
    conn = getFreeConnection(0);
    if(conn == null)
    throw new SQLException("没有可用的数据库连接");
    }
    return conn;

    DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。
    /**
     * 数据连接的自封装,屏蔽了close方法
     * @author Liudong
     */
    class _Connection implements InvocationHandler
    {
    private final static String CLOSE_METHOD_NAME = "close";
    private Connection conn = null;
    //数据库的忙状态
    private boolean inUse = false;
    //用户最后一次访问该连接方法的时间
    private long lastAccessTime = System.currentTimeMillis();

    _Connection(Connection conn, boolean inUse){
    this.conn = conn;
    this.inUse = inUse;
    }
    /**
     * Returns the conn.
     * @return Connection
     */
    public Connection getConnection() {
    //返回数据库连接conn的接管类,以便截住close方法
    Connection conn2 = (Connection)Proxy.newProxyInstance(
    conn.getClass().getClassLoader(),
    conn.getClass().getInterfaces(),this);
    return conn2;
    }
    /**
     * 该方法真正的关闭了数据库的连接
     * @throws SQLException
     */
    void close() throws SQLException{
    //由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接
    conn.close();
    }
    /**
     * Returns the inUse.
     * @return boolean
     */
    public boolean isInUse() {
    return inUse;
    } /**
     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object)
     */
    public Object invoke(Object proxy, Method m, Object[] args) 
    throws Throwable 
    {
    Object obj = null;
    //判断是否调用了close的方法,如果调用close方法则把连接置为无用状态
    if(CLOSE_METHOD_NAME.equals(m.getName()))
    setInUse(false);
    else
    obj = m.invoke(conn, args);
    //设置最后一次访问时间,以便及时清除超时的连接
    lastAccessTime = System.currentTimeMillis();
    return obj;
    }

    /**
     * Returns the lastAccessTime.
     * @return long
     */
    public long getLastAccessTime() {
    return lastAccessTime;
    } /**
     * Sets the inUse.
     * @param inUse The inUse to set
     */
    public void setInUse(boolean inUse) {
    this.inUse = inUse;
    }

    一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:
    /**
     * 关闭该连接池中的所有数据库连接
     * @return int 返回被关闭连接的个数
     * @throws SQLException
     */
    public int close() throws SQLException
    {
    int cc = 0;
    SQLException excp = null;
    Iterator iter = conns.iterator();
    while(iter.hasNext()){
    try{
    ((_Connection)iter.next()).close();
    cc ++;
    }catch(Exception e){
    if(e instanceof SQLException)
    excp = (SQLException)e;
    }
    }
    if(excp != null)
    throw excp;
    return cc;
    }
    该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。
      

  9.   

    谢谢 OnlyFor_love,你的这个数据库连接池实现的很精彩,受益不浅.我的问题是现在的数据库连接池已经有了.如何把他挂到Context中,
    把它挂到Context中还有一个要求就是如何挂到java:comp/env/段中。to zhutouzip(Speak out!-shyboy)
    我做这个的目的是这样的。我想用Junit来测试用于Web Application的代码。
    代码中的数据库连接方式是采用的
    InitialContext ctx = new InitialContext();
    DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/oracle");
    来读取Context中的DataSource的。我用Junit来测试的话我是脱离了Servelet和Jsp容器的。所以我就需要自己建立一个Context
    这个Context我也已经建好了。也把DataSource绑定成功了。但是我在绑定的过程中没有
    把我的DataSource加入到“java:comp/env”段中。所以在测为Tomcat而写的代码就会有问题,
    因为他到“java:comp/env”找不到相应的DataSource。
    我现在想问的问题就是如何把我的DataSource对想加到 jndi服务中的“java:comp/env”
    段中去。
    谢谢了,这个问题困挠我有一段事件了。
    谢谢各位的帮忙。
      

  10.   

    java:comp/env是用来在组件所运行的容器中查找相应的jndi目录名的特定前缀,如果想要以这种方式来查找的话,除了手动配置context,利用context.bind()或context.rebind()方法我没有试过.
      

  11.   

    谢谢 iceandfire,你说的这一部分。我已经实现了。但是我不知道如何context中创建一个目录。
      

  12.   

    目录配置直接在web.xml里面配啊.
      

  13.   

    Tomcat 不是有源码, 可以很难找到你感兴趣的那部分吧?估计你这种需求要仔细阅读关于资源工厂如何创建和绑定的知识 , 我也不明白java:开头的 Context 应该怎么实现, 我猜想跟 InitialContextFactory 和 URLContextFactory??(这个不记得,别认为是真的) 很有关联, 不记得上次看的哪本PDF 上讲到 JNDI Provider 中 关于 new InitialContext().lookup("/fefe/") 和 ctx.lookup("anotherProtocol://sfefef") 中如何实现.
    能不能不用自己写 Context , 让一个 JSP 页面来调用JUnit 测试? 如果能这样就是真实环境了, 这个JSP 如果也能让 Ant 生成那就太好了.或者用 Ant 在 JUnit 运行前批量替换 源码中关于 JNDI URL 那部分常量 成你在 自己的 Context 中绑定的名字 , 运行结束又批量替换回来, 我以前看到过别人怎么在 1.3 环境中源码中用 Class.forName() 而不用 new , 让他的程序在新的JRE 能替换新的类库(哪怕不同的包名,类名,方法 名)来运行,只要重新用 Ant 编译一次,用Ant编译时做手脚而不用自己改写代码, 我想这个做法可能也可以用在你这种情况,