P6Spy是记录JDBC调用日志信息的一个工具,既然记录了JDBC调用,当然就可以监听到SQL,是开发人员必备的开发利器.可以让开发人员非常方便的知道当前应用程序执行了那些sql
P6Spy官方网站http://www.p6spy.com/index.html
在介绍P6Spy工作原理之前先回忆下传统jdbc的取得连接的方法
Class.forName("oracle.jdbc.driver.OracleDriver");//动态加载oracle.jdbc.driver.OracleDriver类,有关类加载这里就不做介绍了.
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);//加载驱动后就可以取得连接了
多么经典的一段代码啊,通俗的说sun定义了一套jdbc接口,由各个数据库厂商去实现,其中java.sql.Driver是一个核心接口,该接口核心方法 boolean acceptsURL(String url) ,以OracleDriver为例,oracle.jdbc.driver.OracleDriver类实现了Driver接口,当显示的调用 Class.forName("oracle.jdbc.driver.OracleDriver");时,OracleDriver会被加载,执行static{}块内的代码,做些初始化工作.当然一个应用往往有时会连接多个数据库,所以衍生了DriverManager类,这个类负责管理多个数据库驱动,也就是多个driver对象,这也是为什么Driver接口有个方法叫acceptsURL的原因,因为当DriverManager管理多个driver对象的时候,调用DriverManager.getConnection(URL, USERNAME, PASSWORD);时,DriverManager必须知道当前Url请求的是那个数据库,最直接的实现想法就是循环遍历每个驱动调用驱动的acceptsURL方法,如果返回true的话,则返回该驱动的连接,所以每个数据库驱动定义的URL格式都不同,因为必须每个驱动都能识别多自己的URL. 同时DriverManager还提供registerDriver和deregisterDriver方法,前者将驱动实例添加到DriverManager类的容器中,后者将驱动从容器中删除,就是所谓的注册驱动和反注册驱动.
上面介绍了JDBC的基本工作原理,下面介绍下P6Spy的配置,首先要改驱动,例如把应用中写com.microsoft.jdbc.sqlserver.SQLServerDriver的地方改为com.p6spy.engine.spy.P6SpyDriver,无论是连接池还是经典的jdbc.然后在spy.properties驱动中配置realDriver,这里是真正的驱动com.microsoft.jdbc.sqlserver.SQLServerDriver,然后将p6spy.jar放到classpath中即可.至于其他输出形式的配置就不介绍了,例如sqlprofiler等.
了解了P6Spy的配置,下面来看P6Spy的源码,首先声明源码已经被我改过了,只保留了最最核心的东西,所以下面的源码和真实的P6Spy源码有些不同,但核心的方法和思想都是一样的,原来的方法中大量的辅助功能和配置参数判断,代码过长不便于贴出来分析. 由于本人文采不佳所以决定通过在代码上面添加注释的方式来讲解代码.
package
com.p6spy.engine.spy;
import
java.util.ArrayList;
import
java.util.Enumeration;
import
java.util.List;
import
java.util.ResourceBundle;
public
class
P6SpyDriver
extends
P6SpyDriverCore {
static
{
//
Class.forName("com.p6spy.engine.spy.P6SpyDriver");当加载这个类时会触发此程序块
init();
}
/**
* 初始化过程,本方法原名initMethod,由于父类也有此名称static方法不能重载为了避免引起奇异改为init
*/
public
static
void
init() {
List driverNames
=
new
ArrayList();
//
存放真实驱动的驱动名容器
try
{
loadDriverNames(driverNames);
//
从配置文件加载真实驱动的驱动名
if
(driverNames
==
null
||
driverNames.size()
==
0
)
return
;
P6SpyDriverCore.initMethod(driverNames);
//
核心方法
}
catch
(Throwable e) {
e.printStackTrace();
}
}
private
static
void
loadDriverNames(List driverNames) {
try
{
ResourceBundle resources
=
ResourceBundle.getBundle(
"
spy-drivers
"
);
Enumeration keys
=
resources.getKeys();
while
(keys.hasMoreElements()) {
driverNames.add(resources.getString(keys.nextElement()
.toString().trim()));
}
}
catch
(Throwable e) {
e.printStackTrace();
driverNames.add(
"
oracle.jdbc.driver.OracleDriver
"
);
driverNames.add(
"
com.microsoft.jdbc.sqlserver.SQLServerDriver
"
);
driverNames.add(
"
com.mysql.jdbc.Driver
"
);
driverNames.add(
"
COM.ibm.db2.jdbc.net.DB2Driver
"
);
driverNames.add(
"
com.informix.jdbc.IfxDriver
"
);
driverNames.add(
"
org.hsqldb.jdbcDriver
"
);
}
}
}
package
com.p6spy.engine.spy;
import
java.sql.
*
;
import
java.util.
*
;
import
com.p6spy.engine.common.
*
;
import
com.p6spy.engine.logging.P6LogFactory;
public
abstract
class
P6SpyDriverCore
implements
Driver {
/**
* 真正的驱动对象实例
*/
protected
Driver passthru
=
null
;
/**
* P6核心工厂
*/
protected
static
P6Factory p6Factory;
/**
* 初始化工作执行完毕的标识
*/
protected
static
boolean
initialized
=
false
;
/**
* 存放被P6托管的数据库驱动容器
*/
protected
static
ArrayList realDrivers
=
new
ArrayList();
/**
* 保证初始化工作的线程安全
*
*
@param
driverNames
*/
public
synchronized
static
void
initMethod(List driverNames) {
if
(initialized)
//
标识初始化工作只执行一次
return
;
String className
=
null
;
try
{
for
(
int
i
=
0
; i
<
driverNames.size(); i
++
) {
P6SpyDriver spy
=
new
P6SpyDriver();
//
创建一个P6驱动实例
DriverManager.registerDriver(spy);
//
注册P6驱动实例
className
=
(String) driverNames.get(i);
deregister(className);
//
处理真正的驱动抢在p6前注册的情况,进行反注册
try
{
Driver realDriver
=
(Driver) forName(className)
.newInstance();
//
加载真正的驱动
spy.setPassthru(realDriver);
//
将真正的驱动注入到P6驱动中,做为P6驱动对象的一个属性
realDrivers.add(realDriver);
//
将真正的驱动添加到P6驱动实例共享的容器中
}
catch
(ClassNotFoundException e) {
DriverManager.deregisterDriver(spy);
//
找不到真正的驱动类时,反注册掉P6驱动实例,因为这个实例不具有存在的价值了.
continue
;
}
}
p6Factory
=
new
P6LogFactory();
//
监听到的SQL输出工厂,原来这里是根据配置文件动态加载的,有两种输出模式
registeredDrivers();
//
输出已经注册的驱动信息到控制台
}
catch
(Exception e) {
String err
=
"
Error registering [
"
+
className
+
"
] Caused By:
"
+
e.toString();
P6LogQuery.logError(err);
throw
new
P6DriverNotFoundError(err);
}
finally
{
initialized
=
true
;
}
}
/**
* 打印当前已经注册的驱动
*/
static
void
registeredDrivers() {
for
(Enumeration e
=
DriverManager.getDrivers(); e.hasMoreElements();) {
Object dr
=
e.nextElement();
String msg
=
dr.toString();
if
(dr
instanceof
P6SpyDriver) {
msg
=
"
P6SpyDriver =>
"
+
((P6SpyDriver) dr).getPassthru().getClass().getName()
.toString();
}
P6LogQuery.logDebug(
"
Driver manager reporting driver registered:
"
+
msg);
}
}
/**
* 使用当前的类加载器加载目标类
*
*
@param
name
*
@return
*
@throws
ClassNotFoundException
*/
static
Class forName(String name)
throws
ClassNotFoundException {
ClassLoader ctxLoader
=
null
;
try
{
ctxLoader
=
Thread.currentThread().getContextClassLoader();
return
Class.forName(name,
true
, ctxLoader);
}
catch
(ClassNotFoundException ex) {
System.out.println(
"
警告: ClassNotFound
"
+
name);
}
catch
(SecurityException ex) {
ex.printStackTrace();
}
return
Class.forName(name);
}
/**
* 查找抢在p6前注册的驱动进行反注册
*
*
@param
className
*
@throws
SQLException
*/
static
void
deregister(String className)
throws
SQLException {
ArrayList dereg
=
new
ArrayList();
for
(Enumeration e
=
DriverManager.getDrivers(); e.hasMoreElements();) {
Driver driver
=
(Driver) e.nextElement();
if
(driver
instanceof
P6SpyDriver) {
break
;
}
//
now you have to be careful of concurrent update
//
exceptions here, so save the drivers for later
//
deregistration
if
(driver.getClass().getName().equals(className)) {
dereg.add(driver);
}
}
//
if you found any drivers let's dereg them now
int
size
=
dereg.size();
if
(size
>
0
) {
for
(
int
i
=
0
; i
<
size; i
++
) {
Driver driver
=
(Driver) dereg.get(i);
DriverManager.deregisterDriver(driver);
}
}
}
/**
* 从这里可以明显看到连接被工厂包装了
*
*
@param
realConnection
*
@return
*
@throws
SQLException
*/
public
static
Connection wrapConnection(Connection realConnection)
throws
SQLException {
return
p6Factory.getConnection(realConnection);
}
public
Driver getPassthru() {
return
passthru;
}
public
void
setPassthru(Driver inVar) {
passthru
=
inVar;
}
//
the remaining methods are for the Driver interface
public
Connection connect(String realUrl, java.util.Properties p1)
throws
SQLException {
//
if there is no url, we have problems
if
(realUrl
==
null
) {
throw
new
SQLException(
"
realURL is null, needs the p6spy prefix:
"
+
realUrl);
}
//
lets try to find the driver from the multiple divers in
//
spy.properties
findPassthru(realUrl);
//
if we can't find one, it may not be defined
if
(passthru
==
null
) {
throw
new
SQLException(
"
Unable to find a driver that accepts
"
+
realUrl);
}
P6LogQuery.logDebug(
"
this is
"
+
this
+
"
and passthru is
"
+
passthru);
if
(passthru
==
null
) {
findPassthru(realUrl);
}
Connection conn
=
passthru.connect(realUrl, p1);
if
(conn
!=
null
) {
conn
=
wrapConnection(conn);
}
return
conn;
}
/**
* 根据实际URL寻找真实驱动
*
*
@param
url
*/
protected
void
findPassthru(String url) {
Iterator i
=
realDrivers.iterator();
while
(i.hasNext()) {
Driver driver
=
(Driver) i.next();
try
{
if
(driver.acceptsURL(url)) {
passthru
=
driver;
P6LogQuery.logDebug(
"
found new driver
"
+
driver);
break
;
}
}
catch
(SQLException e) {
}
}
}
/**
* for some reason the passthru is null, go create one
*/
public
boolean
acceptsURL(String realUrl)
throws
SQLException {
boolean
accepts
=
false
;
//
somehow we get initilized but no driver is created,
//
lets try findPassthru
if
(passthru
==
null
&&
initialized) {
//
we should have some drivers
if
(realDrivers.size()
==
0
) {
throw
new
SQLException(
"
P6 has no drivers registered
"
);
}
else
{
findPassthru(realUrl);
//
if we are still null, we have issues
if
(passthru
==
null
)
throw
new
SQLException(
"
P6 can't find a driver to accept url (
"
+
realUrl
+
"
) from the
"
+
realDrivers.size()
+
"
drivers P6 knows about. The current driver is null
"
);
}
}
if
(realUrl
!=
null
) {
accepts
=
passthru.acceptsURL(realUrl);
}
return
accepts;
}
public
DriverPropertyInfo[] getPropertyInfo(String p0,
java.util.Properties p1)
throws
SQLException {
return
(passthru.getPropertyInfo(p0, p1));
}
public
int
getMajorVersion() {
return
(passthru.getMajorVersion());
}
public
int
getMinorVersion() {
return
(passthru.getMinorVersion());
}
public
boolean
jdbcCompliant() {
return
(passthru.jdbcCompliant());
}
}
其他代码就不一一贴出了,可见p6spy实际上和其他数据库驱动一样实现了jdbc接口,不过p6spy的每个具体实现都是调用了真正的驱动对象的方法,这部分代码实际上非常简单不贴出来,大家可以自己去官方下源码,如果觉得官方源码复杂的话可以在我的发布的资源里面去下载我自己修改的简版p6spy