java动态代理

java动态代理作用之拦截器框架2009-10-10 14:28package cj.sjms.DaiLi.MMM;
import java.lang.reflect.*;
/**
* 动态代理作用之一 拦截器框架
* 作用:监视任意接口内的方法,在方法使用时做处理(如报告)
* 动态代理为泛型,可以代理任何类型;监听任意类型方法
* */
//基本接口1
interface Dog{
public void Doginfo();
public void Dogrun();
public double Doggetint(int i,double d);
}
//基本接口2
interface Cat{
public void Catinfo();
public void Catrun();
public double Catgetdouble (int i,double d);
}
//基本实现接口Dog
class AchieveDog implements Dog{
public double Doggetint(int i, double d) {
return i+d;
}
public void Doginfo() {System.out.println("简单实现类狗");}
public void Dogrun() {System.out.println("简单实现类狗的作用");}
}
//基本实现接口Cat
class AchieveCat implements Cat{
public double Catgetdouble(int i, double d) {
return i+d;
}
public void Catinfo() {System.out.println("简单类实现猫");}
public void Catrun() {System.out.println("简单实现类猫的作用");}
}
//监听器
interface Interceptor{
public void before();
public void after();
public void afterThrowing();
public void afterFinally();
}
//监听器实现类
class InterceptorImpl implements Interceptor{
public void after() {System.out.println("1");}
public void afterFinally() {System.out.println("2");}
public void afterThrowing() {System.out.println("3");}
public void before() {System.out.println("4");}
}
//动态代理
class DynamicProxyInvocationHandler implements InvocationHandler{
//实际类
Object o = null;
//监听器类
Interceptor i = null;
public DynamicProxyInvocationHandler(Object o,Interceptor i){
this.o = o;
this.i = i;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = null;
try{
this.i.after();
o = method.invoke(this.o, args);
this.i.before();
return o;
}catch(Exception e){
this.i.afterThrowing();
}
finally{
this.i.afterFinally();
}
return o;
}
}
/**
* 动态代理工厂
* 作用:返回任何类型的代理
* */
class DefaultProxyFactory{
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target,Interceptor itt){
InvocationHandler dph = new DynamicProxyInvocationHandler(target,itt);
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), dph);
}
}
public class M {
public static void main(String[] args) {
//创建需要代理类型对象
AchieveDog ad = new AchieveDog();
//创建监听器
InterceptorImpl ii = new InterceptorImpl();
//创建代理类
Dog d = DefaultProxyFactory.createProxy(ad, ii);
//调用代理类
d.Doginfo();
d.Dogrun();
//创建需要代理的类型对象
AchieveCat ac = new AchieveCat();
//创建监听器
InterceptorImpl ii2 = new InterceptorImpl();
//创建代理类
Cat c = DefaultProxyFactory.createProxy(ac, ii2);
//调用代理类
c.Catinfo();
c.Catrun();
}
}

1
简单实现类狗
4
2
1
简单实现类狗的作用
4
2
1
简单类实现猫
4
2
1
简单实现类猫的作用
4
2


可以实现 AOP 啊,可以运行一下下面的代码,也是原来回复过,粘过来的,呵呵(为了便于调试全部写在一个文件里了)

Java codeimport java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AopTest {

public static void main(String[] args) {

Before before = new Before() {
public void before() {
System.out.println("...before...");
}
};

After after = new After() {
public void after() {
System.out.println("...after...");
}
};

Hello hello = null;

// 普通方法执行
System.out.println("-------------普通执行-------------");
hello = new HelloEnglish();
hello.sayHello("bao110908");
hello.sayHi("bao110908");
System.out.println();

// 切入方法执行前(前置增强)
System.out.println("-------------前置增强-------------");
hello = HelloAopManager.getHelloProxy(new HelloEnglish(), before);
hello.sayHello("bao110908");
hello.sayHi("bao110908"); // sayHi 方法没有标注 @Enhancement 所以不会进行代码切入
System.out.println();

// 切入方法执行后(后置增强)
System.out.println("-------------后置增强-------------");
hello = HelloAopManager.getHelloProxy(new HelloEnglish(), after);
hello.sayHello("bao110908");
hello.sayHi("bao110908");
System.out.println();

// 切入方法执行前和执行后(环绕增强)
System.out.println("-------------环绕增强-------------");
hello = HelloAopManager.getHelloProxy(new HelloEnglish(), before, after);
hello.sayHello("bao110908");
hello.sayHi("bao110908");
System.out.println();
}
}


@Retention(RetentionPolicy.RUNTIME)
@interface Enhancement {
}

interface Hello {
@Enhancement
public void sayHello(String name);
public void sayHi(String name);
}

class HelloChinese implements Hello {
public void sayHello(String name) {
System.out.println(name + ",您好");
}
public void sayHi(String name) {
System.out.println("哈啰," + name);
}
}

class HelloEnglish implements Hello {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
public void sayHi(String name) {
System.out.println("hi, " + name);
}
}

class HelloAopManager {
private HelloAopManager(){
}

public static Hello getHelloProxy(Hello hello, Before before) {
return getHelloProxy(hello, before, null);
}

public static Hello getHelloProxy(Hello hello, After after) {
return getHelloProxy(hello, null, after);
}

public static Hello getHelloProxy(Hello hello, Before before, After after) {
HelloHandler handler = new HelloHandler();
if(before != null) {
handler.setBefore(before);
}
if(after != null) {
handler.setAfter(after);
}
return handler.bind(hello);
}
}

/**
* 前置增强接口
*/
interface Before {
public void before();
}

/**
* 后置增强接口
*/
interface After {
public void after();
}

class HelloHandler implements InvocationHandler {

/**
* 需要进行代理的实例
*/
private Hello hello = null;

/**
* 前置增强
*/
private Before before = null;

/**
* 后置增强
*/
private After after = null;

/**
* InvocationHandler 接口的实现方法,进行动态代理
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 看看接口中方法是否标注了需要 Enhancement
boolean b = method.isAnnotationPresent(Enhancement.class);
if(!b){
// 没有标注的话,按原方法执行
return method.invoke(hello, args);
}
// 有标注的话,进行方法的前置和后置增强
if(before != null) {
before.before();
}
Object obj = method.invoke(hello, args);
if(after != null) {
after.after();
}
return obj;
}

/**
* 将传入的 Hello 与 InvocationHandler 进行绑定,以获得代理类的实例
* @param hello
* @return
*/
public Hello bind(Hello hello) {
this.hello = hello;
Hello helloProxy = (Hello)Proxy.newProxyInstance(
hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(),
this
);
return helloProxy;
}

public void setAfter(After after) {
this.after = after;
}

public void setBefore(Before before) {
this.before = before;
}
}

所谓Dynamic Proxy是这样一种class:
它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

用一个例子来解释(例子包含三个java文件,已经调试通过):
//ProxyClass.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

public class ProxyClass {
public static void main(String arags[]) throws IllegalArgumentException,
SecurityException, InstantiationException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
InvocationHandler handler = new MyInvocationHandler();
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(),
new Class[] { Foo.class });
Foo f = (Foo) proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
new Object[] { handler });
f.doSomething();

}
}

//Foo.java
public interface Foo {
void doSomething();
}

//MyInvocationHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Helloworld");
return null;
}

}

使用JAVA中的动态代理实现数据库连接池

文档选项
打印本页

将此页作为电子邮件发送




级别: 初级

刘冬 (winter.lau@163.com), 珠海市创我科技发展有限公司软件工程师


2002 年 12 月 05 日

作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池。
数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈。我们可以在互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度。很多的连接池都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前所有的应用服务器取数据库连接的方式都是这种方式实现的。但是另外一个共同的问题是,它们同时不允许使用者显式的调用Connection.close()方法,而需要用其规定的一个方法来关闭连接。这种做法有两个缺点:

第一:改变了用户使用习惯,增加了用户的使用难度。

首先我们来看看一个正常的数据库操作过程:

int executeSQL(String sql) throws SQLException
{
Connection conn = getConnection(); //通过某种方式获取数据库连接
PreparedStatement ps = null;
int res = 0;
try{
ps = conn.prepareStatement(sql);
res = ps.executeUpdate();
}finally{
try{
ps.close();
}catch(Exception e){}
try{
conn.close();//
}catch(Exception e){}
}
return res;
}



使用者在用完数据库连接后通常是直接调用连接的方法close来释放数据库资源,如果用我们前面提到的连接池的实现方法,那语句conn.close()将被某些特定的语句所替代。

第二:使连接池无法对之中的所有连接进行独占控制。由于连接池不允许用户直接调用连接的close方法,一旦使用者在使用的过程中由于习惯问题直接关闭了数据库连接,那么连接池将无法正常维护所有连接的状态,考虑连接池和应用由不同开发人员实现时这种问题更容易出现。

综合上面提到的两个问题,我们来讨论一下如何解决这两个要命的问题。

首先我们先设身处地的考虑一下用户是想怎么样来使用这个数据库连接池的。用户可以通过特定的方法来获取数据库的连接,同时这个连接的类型应该是标准的java.sql.Connection。用户在获取到这个数据库连接后可以对这个连接进行任意的操作,包括关闭连接等。

通过对用户使用的描述,怎样可以接管Connection.close方法就成了我们这篇文章的主题。

为了接管数据库连接的close方法,我们应该有一种类似于钩子的机制。例如在Windows编程中我们可以利用Hook API来实现对某个Windows API的接管。在JAVA中同样也有这样一个机制。JAVA提供了一个Proxy类和一个InvocationHandler,这两个类都在java.lang.reflect包中。我们先来看看SUN公司提供的文档是怎么描述这两个类的。

public interface InvocationHandler
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler.
When a method is invoked on a proxy instance,
the method invocation is encoded and dispatched to the invoke method of its invocation handler.



SUN的API文档中关于Proxy的描述很多,这里就不罗列出来。通过文档对接口InvocationHandler的描述我们可以看到当调用一个Proxy实例的方法时会触发Invocationhanlder的invoke方法。从JAVA的文档中我们也同时了解到这种动态代理机制只能接管接口的方法,而对一般的类无效,考虑到java.sql.Connection本身也是一个接口由此就找到了解决如何接管close方法的出路。

首先,我们先定义一个数据库连接池参数的类,定义了数据库的JDBC驱动程序类名,连接的URL以及用户名口令等等一些信息,该类是用于初始化连接池的参数,具体定义如下:

public class ConnectionParam implements Serializable
{
private String driver; //数据库驱动程序
private String url; //数据连接的URL
private String user; //数据库用户名
private String password; //数据库密码
private int minConnection = 0; //初始化连接数
private int maxConnection = 50; //最大连接数
private long timeoutValue = 600000;//连接的最大空闲时间
private long waitTime = 30000; //取连接的时候如果没有可用连接最大的等待时间



其次是连接池的工厂类ConnectionFactory,通过该类来将一个连接池对象与一个名称对应起来,使用者通过该名称就可以获取指定的连接池对象,具体代码如下:

/**
* 连接池类厂,该类常用来保存多个数据源名称合数据库连接池对应的哈希
* @author liusoft
*/
public class ConnectionFactory
{
//该哈希表用来保存数据源名和连接池对象的关系表
static Hashtable connectionPools = null;
static{
connectionPools = new Hashtable(2,0.75F);
}
/**
* 从连接池工厂中获取指定名称对应的连接池对象
* @param dataSource 连接池对象对应的名称
* @return DataSource 返回名称对应的连接池对象
* @throws NameNotFoundException 无法找到指定的连接池
*/
public static DataSource lookup(String dataSource)
throws NameNotFoundException
{
Object ds = null;
ds = connectionPools.get(dataSource);
if(ds == null || !(ds instanceof DataSource))
throw new NameNotFoundException(dataSource);
return (DataSource)ds;
}
/**
* 将指定的名字和数据库连接配置绑定在一起并初始化数据库连接池
* @param name 对应连接池的名称
* @param param 连接池的配置参数,具体请见类ConnectionParam
* @return DataSource 如果绑定成功后返回连接池对象
* @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
* @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类
* @throws IllegalAccessException 连接池配置中的驱动程序类有误
* @throws InstantiationException 无法实例化驱动程序类
* @throws SQLException 无法正常连接指定的数据库
*/
public static DataSource bind(String name, ConnectionParam param)
throws NameAlreadyBoundException,ClassNotFoundException,
IllegalAccessException,InstantiationException,SQLException
{
DataSourceImpl source = null;
try{
lookup(name);
throw new NameAlreadyBoundException(name);
}catch(NameNotFoundException e){
source = new DataSourceImpl(param);
source.initConnection();
connectionPools.put(name, source);
}
return source;
}
/**
* 重新绑定数据库连接池
* @param name 对应连接池的名称
* @param param 连接池的配置参数,具体请见类ConnectionParam
* @return DataSource 如果绑定成功后返回连接池对象
* @throws NameAlreadyBoundException 一定名字name已经绑定则抛出该异常
* @throws ClassNotFoundException 无法找到连接池的配置中的驱动程序类
* @throws IllegalAccessException 连接池配置中的驱动程序类有误
* @throws InstantiationException 无法实例化驱动程序类
* @throws SQLException 无法正常连接指定的数据库
*/
public static DataSource rebind(String name, ConnectionParam param)
throws NameAlreadyBoundException,ClassNotFoundException,
IllegalAccessException,InstantiationException,SQLException
{
try{
unbind(name);
}catch(Exception e){}
return bind(name, param);
}
/**
* 删除一个数据库连接池对象
* @param name
* @throws NameNotFoundException
*/
public static void unbind(String name) throws NameNotFoundException
{
DataSource dataSource = lookup(name);
if(dataSource instanceof DataSourceImpl){
DataSourceImpl dsi = (DataSourceImpl)dataSource;
try{
dsi.stop();
dsi.close();
}catch(Exception e){
}finally{
dsi = null;
}
}
connectionPools.remove(name);
}

}



ConnectionFactory主要提供了用户将将连接池绑定到一个具体的名称上以及取消绑定的操作。使用者只需要关心这两个类即可使用数据库连接池的功能。下面我们给出一段如何使用连接池的代码:

String name = "pool";
String driver = " sun.jdbc.odbc.JdbcOdbcDriver ";
String url = "jdbc:odbc:datasource";
ConnectionParam param = new ConnectionParam(driver,url,null,null);
param.setMinConnection(1);
param.setMaxConnection(5);
param.setTimeoutValue(20000);
ConnectionFactory.bind(name, param);
System.out.println("bind datasource ok.");
//以上代码是用来登记一个连接池对象,该操作可以在程序初始化只做一次即可
//以下开始就是使用者真正需要写的代码
DataSource ds = ConnectionFactory.lookup(name);
try{
for(int i=0;i<10;i++){
Connection conn = ds.getConnection();
try{
testSQL(conn, sql);
}finally{
try{
conn.close();
}catch(Exception e){}
}
}
}catch(Exception e){
e.printStackTrace();
}finally{
ConnectionFactory.unbind(name);
System.out.println("unbind datasource ok.");
System.exit(0);
}



从使用者的示例代码就可以看出,我们已经解决了常规连接池产生的两个问题。但是我们最最关心的是如何解决接管close方法的办法。接管工作主要在ConnectionFactory中的两句代码:

source = new DataSourceImpl(param);
source.initConnection();



DataSourceImpl是一个实现了接口javax.sql.DataSource的类,该类维护着一个连接池的对象。由于该类是一个受保护的类,因此它暴露给使用者的方法只有接口DataSource中定义的方法,其他的所有方法对使用者来说都是不可视的。我们先来关心用户可访问的一个方法getConnection

/**
* @see javax.sql.DataSource#getConnection(String,String)
*/
public Connection getConnection(String user, String password) throws SQLException
{
//首先从连接池中找出空闲的对象
Connection conn = getFreeConnection(0);
if(conn == null){
//判断是否超过最大连接数,如果超过最大连接数
//则等待一定时间查看是否有空闲连接,否则抛出异常告诉用户无可用连接
if(getConnectionCount() >= connParam.getMaxConnection())
conn = getFreeConnection(connParam.getWaitTime());
else{//没有超过连接数,重新获取一个数据库的连接
connParam.setUser(user);
connParam.setPassword(password);
Connection conn2 = DriverManager.getConnection(connParam.getUrl(),
user, password);
//代理将要返回的连接对象
_Connection _conn = new _Connection(conn2,true);
synchronized(conns){
conns.add(_conn);
}
conn = _conn.getConnection();
}
}
return conn;
}
/**
* 从连接池中取一个空闲的连接
* @param nTimeout 如果该参数值为0则没有连接时只是返回一个null
* 否则的话等待nTimeout毫秒看是否还有空闲连接,如果没有抛出异常
* @return Connection
* @throws SQLException
*/
protected synchronized Connection getFreeConnection(long nTimeout)
throws SQLException
{
Connection conn = null;
Iterator iter = conns.iterator();
while(iter.hasNext()){
_Connection _conn = (_Connection)iter.next();
if(!_conn.isInUse()){
conn = _conn.getConnection();
_conn.setInUse(true);
break;
}
}
if(conn == null && nTimeout > 0){
//等待nTimeout毫秒以便看是否有空闲连接
try{
Thread.sleep(nTimeout);
}catch(Exception e){}
conn = getFreeConnection(0);
if(conn == null)
throw new SQLException("没有可用的数据库连接");
}
return conn;
}



DataSourceImpl类中实现getConnection方法的跟正常的数据库连接池的逻辑是一致的,首先判断是否有空闲的连接,如果没有的话判断连接数是否已经超过最大连接数等等的一些逻辑。但是有一点不同的是通过DriverManager得到的数据库连接并不是及时返回的,而是通过一个叫_Connection的类中介一下,然后调用_Connection.getConnection返回的。如果我们没有通过一个中介也就是JAVA中的Proxy来接管要返回的接口对象,那么我们就没有办法截住Connection.close方法。

终于到了核心所在,我们先来看看_Connection是如何实现的,然后再介绍是客户端调用Connection.close方法时走的是怎样一个流程,为什么并没有真正的关闭连接。

/**
* 数据连接的自封装,屏蔽了close方法
* @author Liudong
*/
class _Connection implements InvocationHandler
{
private final static String CLOSE_METHOD_NAME = "close";
private Connection conn = null;
//数据库的忙状态
private boolean inUse = false;
//用户最后一次访问该连接方法的时间
private long lastAccessTime = System.currentTimeMillis();

_Connection(Connection conn, boolean inUse){
this.conn = conn;
this.inUse = inUse;
}
/**
* Returns the conn.
* @return Connection
*/
public Connection getConnection() {
//返回数据库连接conn的接管类,以便截住close方法
Connection conn2 = (Connection)Proxy.newProxyInstance(
conn.getClass().getClassLoader(),
conn.getClass().getInterfaces(),this);
return conn2;
}
/**
* 该方法真正的关闭了数据库的连接
* @throws SQLException
*/
void close() throws SQLException{
//由于类属性conn是没有被接管的连接,因此一旦调用close方法后就直接关闭连接
conn.close();
}
/**
* Returns the inUse.
* @return boolean
*/
public boolean isInUse() {
return inUse;
}
/**
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object)
*/
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Object obj = null;
//判断是否调用了close的方法,如果调用close方法则把连接置为无用状态
if(CLOSE_METHOD_NAME.equals(m.getName()))
setInUse(false);
else
obj = m.invoke(conn, args);
//设置最后一次访问时间,以便及时清除超时的连接
lastAccessTime = System.currentTimeMillis();
return obj;
}

/**
* Returns the lastAccessTime.
* @return long
*/
public long getLastAccessTime() {
return lastAccessTime;
}
/**
* Sets the inUse.
* @param inUse The inUse to set
*/
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
}



一旦使用者调用所得到连接的close方法,由于用户的连接对象是经过接管后的对象,因此JAVA虚拟机会首先调用_Connection.invoke方法,在该方法中首先判断是否为close方法,如果不是则将代码转给真正的没有被接管的连接对象conn。否则的话只是简单的将该连接的状态设置为可用。到此您可能就明白了整个接管的过程,但是同时也有一个疑问:这样的话是不是这些已建立的连接就始终没有办法真正关闭?答案是可以的。我们来看看ConnectionFactory.unbind方法,该方法首先找到名字对应的连接池对象,然后关闭该连接池中的所有连接并删除掉连接池。在DataSourceImpl类中定义了一个close方法用来关闭所有的连接,详细代码如下:

/**
* 关闭该连接池中的所有数据库连接
* @return int 返回被关闭连接的个数
* @throws SQLException
*/
public int close() throws SQLException
{
int cc = 0;
SQLException excp = null;
Iterator iter = conns.iterator();
while(iter.hasNext()){
try{
((_Connection)iter.next()).close();
cc ++;
}catch(Exception e){
if(e instanceof SQLException)
excp = (SQLException)e;
}
}
if(excp != null)
throw excp;
return cc;
}



该方法一一调用连接池中每个对象的close方法,这个close方法对应的是_Connection中对close的实现,在_Connection定义中关闭数据库连接的时候是直接调用没有经过接管的对象的关闭方法,因此该close方法真正的释放了数据库资源。

以上文字只是描述了接口方法的接管,具体一个实用的连接池模块还需要对空闲连接的监控并及时释放连接,详细的代码请参照附件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值