Java 通过资源加载jdbc驱动程序(Tomcat 7)

Java 通过资源加载jdbc驱动程序(Tomcat 7),java,tomcat,jdbc,tomcat7,classloader,Java,Tomcat,Jdbc,Tomcat7,Classloader,我尝试使用tomcat jdbc连接池,并在application context.xml文件中定义它 <Context> <Resource auth="Container" name="jdbc/iup" type="javax.sql.DataSource" maxActive="300" maxIdle="30" maxWait="20000" username="${db.username}" passw

我尝试使用tomcat jdbc连接池,并在application context.xml文件中定义它

<Context>
    <Resource auth="Container" name="jdbc/iup" type="javax.sql.DataSource"
              maxActive="300" maxIdle="30" maxWait="20000"
              username="${db.username}" password="${db.password}" driverClassName="net.sf.log4jdbc.DriverSpy"
              url="jdbc:log4jdbc:sqlserver://${db.server};databaseName=${db.name}"/>
</Context>
drivercassloader
为空,正在尝试通过
class.forName(drivercassname)
加载驱动程序类。据我所知,在本例中,驱动程序类与
BasicDataSource
加载的类加载器实例相同。这是
StandardClassLoader
,如果我的jar在tomcatlibs中,它将加载这个类。在我的例子中,抛出异常并使用
Thread.currentThread().getContextClassLoader()
,它是
WebappClassLoader
实例,可以从webapp lib加载类。所以我很困惑。为什么说,如果我使用容器资源中的数据源,我必须将我的驱动程序类放在tomcat libs中


请解释一下,谢谢

Tomcat会自动将容器管理的连接池添加到
jaxaz.sql.DataSource
类型的每个资源中。提供此池的库(Commons DBCP的包重命名版本)由共享类加载器加载(在默认配置中与公共加载器相同)。池实现需要能够加载已配置的JDBC驱动程序,而共享(和公共)加载程序对web应用程序类加载程序没有可见性。因此,带有JDBC驱动程序的JAR需要位于
$CATALINA_BASE/lib
目录中,以便可以加载它

但是,从开始,如果无法加载指定的驱动程序,DBCP将返回到线程的上下文类加载器。如果线程上下文类加载器设置为web应用程序的类加载器,则可以加载驱动程序。这一变化被包括在
DBCP 1.3
1.4
之后,这意味着它被包括在
5.5.30
之后、
6.0.27
之后和每个
7.0.x
版本中。它也将出现在每个
8.0.x
版本中

MarkMail对查询量进行相当不科学的观察表明,Tomcat用户邮件列表上的
ClassNotFoundException
问题有所减少,但这同样可能是因为人们更加意识到了这个问题

我想最根本的问题是这可靠吗?如果当线程上下文类加载器是web应用程序类加载器时,
DataSource
始终被实例化,那么它将是可靠的。通过
JNDI
访问这些资源,这取决于线程上下文类加载器的设置是否正确。如果不是,JNDI将无法找到web应用程序资源。在这个基础上,这应该是可行的

Global resources(显然)仍然需要
JDBC
驱动程序位于
$CATALINA_HOME/lib

如果
$CATALINA_HOME/lib和WEB-INF/lib
中存在
JDBC
驱动程序
JAR
,则可能会导致问题。如果web应用程序尝试强制转换到特定于数据库的对象,那么事情将失败,因为这将是尝试将共享加载器加载的类强制转换为web应用程序类加载器加载的同名类,这将始终失败

简言之:

  • 不要将JDBC驱动程序放在
    WEB-INF/lib
    $CATALINA|[HOME | BASE]/lib
    中的长期建议仍然有效
  • 6.0.27
    开始,可以将JDBC驱动程序打包到web应用程序中,并且一切都可以正常工作

对于最初错误/不完整的答案表示歉意。这不是我第一次完全忘记我所做的承诺,我怀疑这不会是最后一次。

我知道这一点。从代码中可以看到,当common loader无法加载驱动程序时,context classloader(指webapp classloader)会成功加载驱动程序。这就是我困惑的原因。让我深入研究一下源代码。你确定你在使用Tomcat 7吗?到底是哪个版本?(Tomcat8有上面的代码,但Tomcat7没有——源代码片段来自哪里?)我使用的是7.0.27版本。此外,我已经检查了版本6,并在那里看到了相同的代码部分——找到了。是我改变了DBCP。让我们更新我的答案。谢谢,马克!我检查了另一个版本(6.0.26)并注意到,没有回退到另一个类装入器。现在我明白了:)
if (driverClassName != null) {
            try {
                try {
                    if (driverClassLoader == null) {
                        Class.forName(driverClassName);
                    } else {
                        Class.forName(driverClassName, true, driverClassLoader);
                    }
                } catch (ClassNotFoundException cnfe) {
                    driverFromCCL = Thread.currentThread(
                            ).getContextClassLoader().loadClass(
                                    driverClassName);
                }
            } catch (Throwable t) {
                String message = "Cannot load JDBC driver class '" +
                    driverClassName + "'";
                logWriter.println(message);
                t.printStackTrace(logWriter);
                throw new SQLNestedException(message, t);
            }
        }