源码解析:Tomcat 7 配置 (XA) 数据源 DataSource XADataSource

PS:本文代码来自tomcat7的源码,位于tomcat-jdbc-pool模块。

典型配置:

mysql:

<Resource type="javax.sql.DataSource"  
            name="jdbc/TestDB"  
            factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"  
            driverClassName="com.mysql.jdbc.Driver"  
            url="jdbc:mysql://localhost:3306/mysql"  
            username="mysql_user"  
            password="mypassword123"  
/>  

oracle:
<Resource name="jdbc/TestDB"
     	factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
              type="javax.sql.DataSource"
              username="scott"
              password="scott"
              driverClassName="oracle.jdbc.driver.OracleDriver"
              url="jdbc:oracle:thin:@168.1.51.21:1522:orcl"
    />

tomcat配置数据源会涉及到 server.xml  context.xml 和应用的 web.xml 三个文件,将上面的代码片段复制到tomcat server.xml的 GlobalNamingResources 标签内部,表示声明一个全局JNDI资源,其中factory="org.apache.tomcat.jdbc.pool.DataSourceFactory",不能修改,它用以表示一个JDNI的对象工厂,实现了javax.naming.spi.ObjectFactory,所以才能被JNDI管理,name表示jndi名称,type表示对象的类型,通过jndi查到对象后可以强转为此类型(其值为:javax.sql.DataSource 或者 javax.sql.XADataSource),

不过,这个JNDI的数据源还不能使用,你需要对应用的web.xml创建一个 ResourceLink element 使这个连接池对于web应用是可用的,

如果你想要用同一个名字让连接池对于所有的应用有效,最简单的方法就是对 context.xml 添加一个 ResourceLinkelement ,它的格式如下:

<ResourceLink type="javax.sql.DataSource"  
                    name="jdbc/LocalTestDB"  
                    global="jdbc/TestDB"/>
注意,如果你不想要全局的连接池,可以从 server.xml移除 Resource element到你的web应用的 context.xml 文件。

然后从刚配置好的连接池中获得连接,简单java代码:

Context initContext = new InitialContext();  
Context envContext  = (Context)initContext.lookup("java:/comp/env");  
DataSource datasource = (DataSource)envContext.lookup("jdbc/LocalTestDB");  
Connection con = datasource.getConnection();  
注意:此种 Resource配置方式是不支持XADataSource的,即便设置了type="javax.sql.XADataSource"(这样lookup到的数据源确实是XADataSource的示例(instandeof 可通过)),但是其实是不能使用的,如果调用getXAConnection会报错,源码解析如下:
public XAConnection getXAConnection() throws SQLException {
        Connection con = getConnection();
        if (con instanceof XAConnection) {
            return (XAConnection)con;
        } else {
            try {con.close();} catch (Exception ignore){}
            throw new SQLException("Connection from pool does not implement javax.sql.XAConnection");
        }
    }
public Connection getConnection() throws SQLException {
        //check out a connection
        PooledConnection con = borrowConnection(-1,null,null);
        return setupConnection(con);
    }

<pre name="code" class="java"><pre name="code" class="java">//cache the constructor,通过 con.getXAConnection() != null 来判断 xa 变量是否是 true 
        if (proxyClassConstructor == null ) {
            Class<?> proxyClass = xa ?
                Proxy.getProxyClass(ConnectionPool.class.getClassLoader(), new Class[] {java.sql.Connection.class,javax.sql.PooledConnection.class, javax.sql.XAConnection.class}) :
                Proxy.getProxyClass(ConnectionPool.class.getClassLoader(), new Class[] {java.sql.Connection.class,javax.sql.PooledConnection.class});
            proxyClassConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
        }
 

//省略了一些代码
getProxyConstructor(con.getXAConnection() != null);
            //create the proxy
            //TODO possible optimization, keep track if this connection was returned properly, and don't generate a new facade
            Connection connection = null;
            if (getPoolProperties().getUseDisposableConnectionFacade() ) {
                connection = (Connection)proxyClassConstructor.newInstance(new Object[] { new DisposableConnectionFacade(handler) });
            } else {
                connection = (Connection)proxyClassConstructor.newInstance(new Object[] {handler});
            }
            //return the connection
            return connection;


 
 代码中通过 : 
con.getXAConnection() != null,判断是否是xa的连接,如果不是,那么返回的动态对象的类型(proxyClassConstructor)则不包含XAConnection, 

这样,在强转为XADataSource后,调用getXAConnection,机会进入第一块代码的else里面,抛出异常:

throw new SQLException("Connection from pool does not implement javax.sql.XAConnection");

所以:通过 Resource 配置 driverClassName这种方式是不能获取到XAConnection(得到的XADataSource是徒有虚名的),那么如果才能获取到呢?

上面看到 con.getXAConnection() != null 这个是判断的关键,这个什么时候不是null呢?查看 org.apache.tomcat.jdbc.pool.ConnectionPool源码,连接池在每次创建一个连接的时候,都会调用 connect 方法,它的源码包含如下逻辑:

if (poolProperties.getDataSource()!=null) {
            connectUsingDataSource();
        } else {
            connectUsingDriver();
        }
connectUsingDataSource 方法里面则会更加数据源(这个是JNDI数据源,稍后讲到)类型(DataSource/XADataSource/ConnectionPoolDataSource)创建出合适的连接,如果是XADataSource则会创建XAConnection;

connectUsingDriver方法,则只会根据配置的 driverClassName ,将其强制转换为java.sql.Driver,并反射创建一个实例,再调用其connect方法创建一个普通的数据库物理连接。

为题的关键在于:poolProperties.getDataSource() 这个什么时候不是null?查看 org.apache.tomcat.jdbc.pool.DataSourceFactory源码,可知其只有在配置了JNDI数据源的时候不是null,代码:

public DataSource createDataSource(Properties properties,Context context, boolean XA) throws Exception {
        PoolConfiguration poolProperties = DataSourceFactory.parsePoolProperties(properties);
        if (poolProperties.getDataSourceJNDI()!=null && poolProperties.getDataSource()==null) {
            performJNDILookup(context, poolProperties);
        }
        org.apache.tomcat.jdbc.pool.DataSource dataSource = XA?
                new org.apache.tomcat.jdbc.pool.XADataSource(poolProperties) :
                new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
        //initialise the pool itself
        dataSource.createPool();
        // Return the configured DataSource instance
        return dataSource;
    }
public void performJNDILookup(Context context, PoolConfiguration poolProperties) {
        Object jndiDS = null;
        try {
            if (context!=null) {
                jndiDS = context.lookup(poolProperties.getDataSourceJNDI());
            } else {
                log.warn("dataSourceJNDI property is configued, but local JNDI context is null.");
            }
        } catch (NamingException e) {
            log.debug("The name \""+poolProperties.getDataSourceJNDI()+"\" can not be found in the local context.");
        }
        if (jndiDS==null) {
            try {
                context = new InitialContext();
                jndiDS = context.lookup(poolProperties.getDataSourceJNDI());
            } catch (NamingException e) {
                log.warn("The name \""+poolProperties.getDataSourceJNDI()+"\" can not be found in the InitialContext.");
            }
        }
        if (jndiDS!=null) {
            poolProperties.setDataSource(jndiDS);
        }
    }
即,只要Resource配置文件配置的JNDI数据源可以找到,就会调用 poolProperties.setDataSource(jndiDS); 将其设置进来。

这个乍看起来似乎是个死套啊:像创建一个XA的数据源,必需先有一个XA的数据源,其实不然,这个死套是 org.apache.tomcat.jdbc.pool.DataSourceFactory 自己搞出来的,只要另外一个JNDI工厂不是这个类型就没有问题,tomcat还有另外一个对象工厂:org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory,这个工厂的逻辑很简单就是根据指定的type,直接反射生成对象,并将Resource设置的属性设置到生成的对象上就ok了,代码如下:

public class GenericNamingResourcesFactory implements ObjectFactory {
    private static final Log log = LogFactory.getLog(GenericNamingResourcesFactory.class);

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        if ((obj == null) || !(obj instanceof Reference)) {
            return null;
        }
        Reference ref = (Reference) obj;
        Enumeration<RefAddr> refs = ref.getAll();

        String type = ref.getClassName();
        Object o = Class.forName(type).newInstance();

        while (refs.hasMoreElements()) {
            RefAddr addr = refs.nextElement();
            String param = addr.getType();
            String value = null;
            if (addr.getContent()!=null) {
                value = addr.getContent().toString();
            }
            if (setProperty(o, param, value,false)) {

            } else {
                log.debug("Property not configured["+param+"]. No setter found on["+o+"].");
            }
        }
        return o;
    }

好了,原理清楚了(tomcat设计的真是巧妙,简单几个类就实现了一个连接池,据说性能高于dbcp和c3p0,建议大家多学习),那么最终的配置是这样的:

server.xml:

     <Resource factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
            name="jdbc/MyXA1"
            type="oracle.jdbc.xa.client.OracleXADataSource"
            URL="jdbc:oracle:thin:@168.1.50.20:1522:orcl"
            username="scott"
            password="scott"/>
            
     <Resource name="jdbc/TestDB"
     		  factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
              type="javax.sql.XADataSource"
              dataSourceJNDI="MyXA1"
              username="scott"
              password="scott"/>
其它的都和配置普通driver类型的数据源一样,这样我们通过代码获取到数据源,强转为XADataSource,就可以成功调用到getXAConnection获取到XAConnection,

这里必须说明一下,基于tomcat的巧夺天工的设计,即使我们直接调用getConnection得到的对象虽然表象是Connection但其实也是XAConnection的实例,不信的话就 instanceof一下哦,它的奥妙就在于前面介绍的:proxyClassConstructor,说到最好还少了一个非常关键的类:org.apache.tomcat.jdbc.pool.ProxyConnection,在他是一个jdk动态代理的handler,通过数据源说到的Connection/Connection表象都只是一个句柄而已,通过句柄调入过来,最终都会被ProxyConnection拦截执行,来看看其拦截的庐山真面目吧:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (compare(ISCLOSED_VAL,method)) {
            return Boolean.valueOf(isClosed());
        }
        if (compare(CLOSE_VAL,method)) {
            if (connection==null) return null; //noop for already closed.
            PooledConnection poolc = this.connection;
            this.connection = null;
            pool.returnConnection(poolc);
            return null;
        } else if (compare(TOSTRING_VAL,method)) {
            return this.toString();
        } else if (compare(GETCONNECTION_VAL,method) && connection!=null) {
            return connection.getConnection();
        } else if (method.getDeclaringClass().equals(XAConnection.class)) {
            try {
                return method.invoke(connection.getXAConnection(),args);
            }catch (Throwable t) {
                if (t instanceof InvocationTargetException) {
                    throw t.getCause() != null ? t.getCause() : t;
                } else {
                    throw t;
                }
            }
        }
        if (isClosed()) throw new SQLException("Connection has already been closed.");
        if (compare(UNWRAP_VAL,method)) {
            return unwrap((Class<?>)args[0]);
        } else if (compare(ISWRAPPERFOR_VAL,method)) {
            return Boolean.valueOf(this.isWrapperFor((Class<?>)args[0]));
        }
        try {
            PooledConnection poolc = connection;
            if (poolc!=null) {
                return method.invoke(poolc.getConnection(),args);
            } else {
                throw new SQLException("Connection has already been closed.");
            }
        }catch (Throwable t) {
            if (t instanceof InvocationTargetException) {
                throw t.getCause() != null ? t.getCause() : t;
            } else {
                throw t;
            }
        }
    }

简单介绍:当调用句柄的close方法时,其实并没有关闭连接,而是归还到了连接池,当调用的方法是XAConnection声明的时就使用持有的真实的XAConnection对象来执行,这是两个比较关键的点,其他的自行体会吧,

相关参考:

Java 数据源 DataSource 架构分析 (jdk7/tomcat7)


其它可参考:http://blog.csdn.net/yp5185423/article/details/8299800

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值