参考大神文章:什么是动态代理
动态代理(以下称代理),利用Java的反射技术(Java Reflection),在运行时创建一个实现某些给定接口的新类(也称“动态代理类”)及其实例(对象)
(Using Java Reflection to create dynamic implementations of interfaces at runtime)。
代理的是接口(Interfaces),不是类(Class),更不是抽象类。
使用代理,可以在不实现接口的情况,对接口的方法进行扩展,添加额外的用户需要的业务逻辑!
使用动态代理,可以监测接口中方法的执行!
利用Java的Proxy类,调用Proxy.newProxyInstance(),创建动态对象十分简单。
//Foo是一个接口
Foo f = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class[] { Foo.class },
handler);
Proxy.newProxyInstance()方法有三个参数:
1. 类加载器(Class Loader)
2. 需要实现的接口数组
3. InvocationHandler接口。所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在。
接口里有一个invoke()方法。基本的做法是,创建一个类,实现这个方法,利用反射在invoke()方法里实现需求:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(
Object proxy,
Method method,
Object[] args)throws Throwable {
//do something "dynamic"
}
}
invoke()方法同样有三个参数:
1. 动态代理类的引用,通常情况下不需要它。但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,如方法列表,annotation等。
2. 方法对象的引用,代表被动态代理类调用的方法。从中可得到方法名,参数类型,返回类型等等
3. args对象数组,代表被调用方法的参数。注意基本类型(int,long)会被装箱成对象类型(Interger,Long)
要求:
当关闭连接的时候connection.close(),初始化时的连接放回到连接池中,即触发pool.addLast(connection)。在方法原有功能的基础上,增加一些额外的功能。
对接口中的某个指定的方法功能进行扩展,可以使用(动态)代理模式
实现代码:
package 自定义连接池;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyPool {
int init_count = 3; // 初始化连接数目
int max_count = 6; // 最大连接数
int current_count = 0; // 记录当前使用连接数
// 连接池 (存放所有的初始化连接)
LinkedList pool = new LinkedList();
// 1. 构造函数中,把初始化连接放入连接池
public MyPool() {
// 初始化连接
for (int i = 0; i < init_count; i++) {
// 记录当前连接数目
current_count++;
// 创建原始的连接对象
Connection con = createConnection();
// 把连接加入连接池
pool.addLast(con);
}
}
private Connection createConnection() {
try {
Class.forName("com.mysql.jdbc.Driver");
// 原始的目标对象 如果想自己写一个连接池 当然要用DriverManagerle
final Connection con = DriverManager.getConnection(
"jdbc:mysql:///shop", "root", "root");
/** ********对con对象代理************* */
// 对con创建其代理对象
Connection proxy = (Connection) Proxy.newProxyInstance(con
.getClass().getClassLoader(), // 类加载器
// con.getClass().getInterfaces(), // 当目标对象是一个具体的类的时候
new Class[] { Connection.class }, // 目标对象实现的接口
new InvocationHandler() { // 当调用con对象方法的时候, 自动触发事务处理器
public Object invoke(Object proxy, Method method,
Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
// 方法返回值
Object result = null;
// 当前执行的方法的方法名
String methodName = method.getName();
// 判断当执行了close方法的时候,把连接放入连接池
if ("close".equals(methodName) && current_count<=init_count) {
System.out.println("begin:当前执行close方法开始!");
// 连接放入连接池
pool.addLast(con);
current_count--;
System.out.println("end: 当前连接已经放入连接池了!");
}else if("close".equals(methodName)&¤t_count>init_count){
//销毁connection对象
if(con!=null){
try {
System.out.println("销毁当前连接对象");
con.close();
current_count--;
} catch (SQLException e) {
e.printStackTrace();
}
}
}else {
// 调用目标对象方法
result = method.invoke(con, args);
}
return result;
}
});
return proxy;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//3. 获取连接
public Connection getConnection(){
// 3.1 判断连接池中是否有连接, 如果有连接,就直接从连接池取出
if (pool.size() > 0){
return pool.removeFirst();
}
// 3.2 连接池中没有连接: 判断,如果没有达到最大连接数,创建;
if (current_count < max_count) {
// 记录当前使用的连接数
current_count++;
// 创建连接
return createConnection();
}
// 3.3 如果当前已经达到最大连接数,抛出异常
throw new RuntimeException("当前连接已经达到最大连接数目 !");
}
}
用JUnit测试一下
@Test
public void test7() throws SQLException{
Connection con1 = myPool.getConnection();
Connection con2 = myPool.getConnection();
Connection con3 = myPool.getConnection();
Connection con4 = myPool.getConnection();
System.out.println("连接池:" + myPool.pool.size()); //
System.out.println("当前连接: " + myPool.current_count); //
con1.close();
con2.close();
con3.close();
con4.close();
System.out.println("连接池:" + myPool.pool.size()); //
System.out.println("当前连接: " + myPool.current_count); //
}
//OUTPUT
// 连接池:0
// 当前连接: 4
// 销毁当前连接对象
// begin:当前执行close方法开始!
// end: 当前连接已经放入连接池了!
// begin:当前执行close方法开始!
// end: 当前连接已经放入连接池了!
// begin:当前执行close方法开始!
// end: 当前连接已经放入连接池了!
// 连接池:3
// 当前连接: 0
一道面试题:
如果让你设计一个数据库连接池,你可能会考虑到哪些方面的问题?
答案:
1. 连接池需要实现javax.sql.DataSource接口,以适合于不同的场合。
2. Connection close问题。使用者使用连接池与不使用连接池,除了从哪里获得connection对象不一样之外,其他的JDBC的代码是完全相同的,并不能因为使用连接池而改变既有的JDBC代码。如果不能改变JDBC代码,就带来了一个Connection close的问题,大家都知道这个调用是关闭数据库连接,如果在连接池中这么做的话就会关闭连接,使得连接得不到重用。
3. 连接被动关闭问题。为了保证连接的复用性,将连接一直保存在池中。有些数据库服务器会将已经连接很久的客户端连接主动踢掉,如果碰到这种情况,在池中的这个连接就会变为不可用状态,如果被客户端使用的话将会抛出连接被关闭的SQLException。
4. 连接回收问题。假如我们的连接池最大设为50个,在某一并发很高的时段达到了50个,但是过后并发率就降下去了,对于连接池来说池中还是50个连接,实际上后面根本不需要这么多连接。这时连接池白白地浪费了几十个数据库宝贵的连接(数据库对于客户端的连接数是有限制的),如果连接池占用了很多的连接,那么可能会导致其他应用程序因为数据库客户端的连接数到了限制而无法再获得连接。我们应该及时地将不需要使用的连接关闭,保留基本连接数。
5. 网络中断重连问题。连接池中的连接在网络中断时,池中连接会全部断开,数据库服务端也会回收断开的连接。但是网络中断后,过了一些时间又连上了,这时池中的连接依然是断开的,如果取出来用的话会抛出异常。一个可用的连接需要有实现自动重连功能,否则就没有可用的价值。
总结来说:两点非常重要,连接回收以及中断重连。中断重连在上面的Demo中并没有实现。