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

时间:2013-10-17 15:35:20

标签: java tomcat jdbc tomcat7 classloader

我正在尝试使用tomcat jdbc连接池,并在我的应用程序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>

net.sf.log4jdbc.DriverSpylog4jdbc4-1.2.jar中定义,它位于我的应用程序库文件夹中。它对我来说很好。但是here说,带有驱动程序类的jar应该只放在tomcat lib文件夹中。

Tomcat使用它的BasicDataSource类来加载驱动程序:

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);
            }
        }

driverClassLoader为null,并且尝试通过Class.forName(driverClassName)加载驱动程序类。据我所知,在这种情况下,驱动程序类正在加载与BasicDataSource相同的类加载器实例。这是StandardClassLoader,如果我的jar在tomcat库中,它将加载该类。在我的情况下抛出异常并使用Thread.currentThread().getContextClassLoader(),这是WebappClassLoader实例,可以从webapp lib加载类,它可以。所以我很困惑。为什么说,如果我使用容器资源中的数据源,我必须将我的驱动程序类放在tomcat库中。

请解释,谢谢

1 个答案:

答案 0 :(得分:5)

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

但是,从r754776开始,如果DBCP无法加载指定的驱动程序,则它会回退到Thread的上下文类加载器。如果线程上下文类加载器设置为Web应用程序的类加载器,则可以加载驱动程序。此更改已包含在DBCP 1.31.4之后,这意味着它已包含在5.5.30以后,6.0.27以及每7.0.x个版本中。它也将出现在每个8.0.x版本中。

使用 MarkMail 对查询量进行相当不科学的观察表明,Tomcat用户邮件列表中的ClassNotFoundException问题有所减少,但同样可能会让人们更加意识到问题。

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

全球资源(显然)仍然需要将JDBC驱动程序放在$CATALINA_HOME/lib

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

简而言之:

  • WEB-INF/lib$CATALINA_[HOME|BASE]/lib中没有JDBC驱动程序的长期建议
  • 6.0.27开始,可以将JDBC驱动程序打包到Web应用程序中,一切都可以正常工作。

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