方面因 ClassCastException 尝试切入 Tomcat DataSource getConnection 而失败
我需要对来自 Tomcat JDBC 池的每个借用连接执行几个初始化语句.我无法使用 JDBCInterceptors,因为池与不需要上述初始化的其他应用程序共享.
I need to execute several initialization statements on each borrowed connection from a Tomcat JDBC Pool. I cannot use JDBCInterceptors because pool is shared with other applications that won't need said initilization.
我正在使用 Spring Boot 1.4.4,部署在 Tomcat 8.5.11 上,它在 context.xml 中有一个 ResourceLink 到 server.xml 中的一个资源,它定义了针对 Oracle 11g 数据库的数据源.我正在通过 JNDI 访问数据源.
I'm using Spring Boot 1.4.4, deploying on Tomcat 8.5.11, which has a ResourceLink in context.xml to a Resource in server.xml that defines the DataSource against a Oracle 11g Database. I'm accessing the DataSource via JNDI.
根据这个答案 https://stackoverflow.com/a/38746398 我想使用 Spring AOP 来实现我的目标.
As per this answer https://stackoverflow.com/a/38746398 I wanted to use Spring AOP to accomplish my goal.
如果 DataSource 直接在 context.xml 中定义,我已经创建了一个完美的方面,但如果通过 ResourceLink 引用到 server.xml 中的相同定义,则会出现 ClassCastException 失败
I have created and Aspect that works perfectly if the DataSource is defined directly in context.xml, but fails with a ClassCastException if referenced via a ResourceLink to the same definition in server.xml
例外是:
java.lang.ClassCastException: org.apache.tomcat.jdbc.pool.DataSource cannot be cast to org.apache.tomcat.jdbc.pool.DataSourceProxy
at org.apache.tomcat.jdbc.pool.DataSourceProxy$$FastClassBySpringCGLIB$$26808f96.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656)
at org.apache.tomcat.jdbc.pool.DataSource$$EnhancerBySpringCGLIB$$17f85659.getConnection(<generated>)
我的方面类:
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
private static final Logger LOG = LoggerFactory.getLogger(DataSourceAspect.class);
@AfterReturning(pointcut = "execution(* org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection())")
public void afterConnectionEstablished() {
LOG.info("Borrowed connection from the pool. Initializing...");
}
}
context.xml 中的ResourceLink 定义:
ResourceLink definition in context.xml:
<ResourceLink name="jdbc/us_j2eeCoreDS"
global="jdbc/us_j2eeCoreDS"
type="javax.sql.DataSource"/>
server.xml 中的数据源定义(更改私有值):
DataSource definition in server.xml (changed private values):
<Resource name="jdbc/us_j2eeCoreDS" type="javax.sql.DataSource"
global="jdbc/us_j2eeCoreDS"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="oracle.jdbc.OracleDriver"
username="xxx" password="xxx"
initialSize="1"
minIdle="1"
maxIdle="4" maxWaitMillis="5000"
maxActive="40"
removeAbandonedOnBorrow="true" removeAbandonedTimeout="300"
testWhileIdle="true"
validationQuery="SELECT 1 FROM DUAL"
timeBetweenEvictionRunsMillis="60000"
url="jdbc:oracle:thin:@host:port:SID"/>
正如我之前所说,如果我在 context.xml 中定义完全相同的 DataSource,它可以完美运行.
As I said before, if I define the exact same DataSource in context.xml, it works flawlessly.
有什么线索吗?
非常感谢.
最终问题与 AOP 无关,而是依赖冲突.
Eventually the problem was not AOP related, but a dependency conflict.
项目基于 Spring Boot 1.4.4.RELEASE.在我的 build.gradle
中,我有以下依赖项:
The project is based on Spring Boot 1.4.4.RELEASE. In my build.gradle
, I had the following dependency:
compile("org.springframework.boot:spring-boot-starter-data-jpa")
作为初学者,这只是一种快速获取更多所需依赖项的方法.提供的 deps 之一是:
Being a starter, it's just a quick way to get more needed dependencies. One of the deps provided is:
org.springframework.boot:spring-boot-starter-jdbc:1.4.4.RELEASE
提供:
org.apache.tomcat:tomcat-jdbc:8.5.11
结果我的war包最终包含tomcat-jdbc-8.5.11.jar
.
So as it turns out my war package ended up containing tomcat-jdbc-8.5.11.jar
.
Tomcat 8.5.11 服务器也包含相同的库,tomcat-jdbc.jar
.
Tomcat 8.5.11 server also contained the same library, tomcat-jdbc.jar
.
由于池是 server.xml
定义的,池使用的库是 tomcat-jdbc.jar
,但是我的应用程序有 tomcat-jdbc-8.5.11.jar
在其WEB-INF/libs
目录中,使用tomcat-jdbc-8.5.11.jar
,导致ClassCastException.如果池是在 context.xml
中定义的,我没有时间挖掘更多并找到它起作用的实际原因,但我想这只是 jar 加载优先级的情况.
As the pool was server.xml
defined, the library used by the pool was tomcat-jdbc.jar
, but my application, having tomcat-jdbc-8.5.11.jar
inside its WEB-INF/libs
directory, used tomcat-jdbc-8.5.11.jar
, leading to the ClassCastException. I didn't have time to dig out more and find the actual reason why it worked if the pool was defined in context.xml
, but I guess it's just a case of jar loading priority.
修复:使用以下gradle增强功能从war包中排除tomcat-jdbc-8.5.11.jar
(只需将其包含在您的build.gradle
配置部分):
The fix: exclude the tomcat-jdbc-8.5.11.jar
from the war package, using the following gradle enchanment (just include it in your build.gradle
configurations section):
configurations {
runtime.exclude module: 'tomcat-jdbc'
}
希望这对其他人有帮助!
Hope this helps somebody else!