什么是装饰模式
定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
单单看上面的定义太抽象了,我们来举个栗子。
我们先定义一个Animal接口,里面有一个eat和call方法
public interface Animal { public void eat(); public void call(); }
再创建一个Dog类,实现Animal接口
public class Dog implements Animal{ public void eat(){ System.out.println("狗在嘚嘚的吃"); } public void call(){ System.out.println("狗在旺旺的叫"); } }
现在我们不喜欢狗的叫声,想让狗哈哈哈地叫,该怎么办呢。可以继承Dog类,然后重写call方法;也可以使用装饰模式。下面我们使用装饰模式来实现一下。
我们创建一个ZhuangshiDog类
/** * 装饰类 要实现和被装饰类一样的接口 * @author wangchaoyouziying * */ public class ZhuangshiDog implements Animal { private Animal ani; public ZhuangshiDog(Animal ani) { this.ani = ani; } @Override public void eat() { ani.eat(); } @Override public void call() { System.out.println("狗在哈哈哈地叫"); } }
测试一下
public class Test { public static void main(String[] args) { Animal dog = new Dog();//这里Dog是被装饰者 //实例化装饰类 Animal zsDog = new ZhuangshiDog(dog); zsDog.call(); zsDog.eat(); } }输出结果:
狗在哈哈哈地叫 狗在嘚嘚的吃
装饰模式的优点:可以不改变被装饰者和继承关系的情况下,拓展被装饰者的功能。
缺点:需要实现和被装饰者相同的接口,那么如果该接口有一百个方法,而我们只想装饰其中的一个方法。此时我们不得不另外实现其余的99个方法,很明显增加了代码冗余量。
总结:装饰模式适用于被装饰者实现的接口中方法数较少的情况。
那么对于方法数较多的情况下该怎么办呢。此时可以使用动态代理模式。
什么是动态代理模式?
定义:用来修改已经具有的对象的方法,控制方法是否执行,或在方法执行之前和执行之后做一些额外的操作。
我们同样来举个栗子。
我们先定义一个Animal接口,里面有一个eat和call方法
package cn.chao.zhuangshiProxy; public interface Animal { public void eat(); public void call(); }
创建一个Dog类,实现Animal接口
public class Dog implements Animal{ public void eat(){ System.out.println("狗在嘚嘚的吃"); } public void call(){ System.out.println("狗在旺旺的叫"); } }
再创建一个代理类ProxyDog
/** * 动态代理类 给所有的Animal创建代理对象 代理对象,不需要实现接口 * * @author wangchaoyouziying * */ public class ProxyDog { private Object target; public ProxyDog(Object target) { this.target = target; } /** * 给被代理对象生成代理对象 * * @return */ public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 被代理对象的类加载器 target.getClass().getInterfaces(), // 被代理对象所实现的所有接口组成的数组 new InvocationHandler() { /** * proxy 代理对象 * method 被代理对象的方法 * args 被代理对象的方法中的参数 * 返回值 被代理对象的方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("call".equals(method.getName())) {// 对被代理的Dog类的call方法进行改造 System.out.println("狗在嘻嘻嘻地笑"); return null; //被代理对象的call方法没有返回值,所以这里返回null } else {//其他方法不改造,则调用method的invoke方法 return method.invoke(target, args);//target-->被代理对象 args-->被代理对象的方法的参数 } } }); } }
测试一下:
public class Test { public static void main(String[] args) { Animal dog = new Dog();//这里的dog是被代理对象 //实例化装饰类 // Animal zsDog = new ZhuangshiDog(dog); // zsDog.call(); // zsDog.eat(); Animal proxyDog = (Animal) new ProxyDog(dog).getProxyInstance(); proxyDog.call(); proxyDog.eat(); } }
输出结果:
狗在嘻嘻嘻地笑
狗在嘚嘚的吃
说了那么多,动态代理究竟有什么用呢?
比如我们有一个需求,手写一个数据库连接池,我们执行完sql语句查询出结果后要将当前数据库的连接还回到连接池吧,我们只需调用自定义的连接池MyPool类的returnConn方法就可以了。但是万一程序员忘了调用returnConn方法了,而是调用了Connection的close方法,那当前连接就回不到连接池了。这时我们就需要通过动态代理,改造一下close方法。
首先,新建一个自定义数据库连接池类MyPool,实现javax.sql.DataSource接口
import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import javax.sql.DataSource; public class MyPool implements DataSource { // LinkList底层是链表,方便增删,不方便查找 private static List<Connection> pool = new LinkedList<Connection>(); static { try { // 注册mysql驱动 Class.forName("com.mysql.jdbc.Driver"); for (int i = 0; i < 5; i++) {// 向数据库连接池中创建5个连接 // 创建连接 Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456"); pool.add(conn); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(); } } @Override public Connection getConnection() throws SQLException { if (pool.size() == 0) {// 用户获取连接的时候,如果连接池中没有连接可用,则创建3条连接 for (int i = 0; i < 3; i++) { Connection conn = DriverManager.getConnection("jdbc:mysql:///ccm", "root", "123456"); pool.add(conn); } } Connection conn = pool.remove(0);// 从数据库连接池中取一个连接 // 利用动态代理改造Connection类的close方法,这样我们只要调用Connection的close方法就会将连接还回到连接池中 Connection proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("close".equals(method.getName())) { // 对于想要改造的close方法,我们自己写 returnConn(conn); return null; } else { // 对于不想改造的方法,调用被代理对象原来的方法 return method.invoke(conn, args); } } }); System.out.println("获取了一个连接,现在池里还有" + pool.size() + "个连接"); return proxyConn; } /** * 将数据库连接还回到连接池中 */ protected void returnConn(Connection conn) { try { if (conn != null && !conn.isClosed()) { pool.add(conn); System.out.println("还回了一个连接,现在池里还有" + pool.size() + "个连接"); } } catch (Exception e) { e.printStackTrace(); } } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; } }
此时我们在jdbc中调用Connection改造过后的close方法就会帮我们将当前连接还回到连接池了。
jdbc的实现类:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class JdbcTest { public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; MyPool pool = new MyPool(); try{ conn = pool.getConnection(); ps = conn.prepareStatement("select * from users"); rs = ps.executeQuery(); while(rs.next()){ String username = rs.getString("username"); System.out.println(username); } }catch (Exception e) { e.printStackTrace(); }finally{ if(conn != null){ try{ conn.close();//调用close方法时,会将连接还回到连接池中,而不会将连接关闭 }catch (Exception e) { e.printStackTrace(); }finally{ conn = null; } } if(ps != null){ try{ ps.close(); }catch (Exception e) { e.printStackTrace(); }finally{ ps = null; } } if(rs != null){ try{ rs.close(); }catch(Exception e){ e.printStackTrace(); }finally{ rs = null; } } } } }
运行后的结果:
获取了一个连接,现在池里还有4个连接 川哥 晴姐 小红 还回了一个连接,现在池里还有5个连接
动态代理总结:动态代理不需要实现接口,但是被代理对象一定要实现至少一个接口,否则不能用动态代理。
源码:点击打开链接