深入理解动态代理
说深入理解其实是有些过了,只能算是肤浅的理解,这技术叫代理,似乎跟设计模式扯上点关系,因此显得有些NB,还是动态的,这逼格就更高了。对于逼格高的东西,我一向喜欢扒一扒。
从使用场景开始说起吧,万事需要有引子,技术需要有实践,先说应用,再说实践:
场景:
开发一个项目,直接调用的jdbc操作数据库,有一天数据库的编码突然由英文变为了日文,因此我们面临将所有的jdbc中setString方法进行重新编码的处境,手工操作可以,但是有些恶心,这时候有人就想起来说,我们使用动态代理不行吗?
解决:
方案就是对getString和setString进行接口代理,让所有调用这两处方法的地方都进行编码转换先。
public static ResultSet createResultSetJPWrapper(ResultSet rs) { return (ResultSet)(Proxy.newProxyInstance(ResultSet.class.getClassLoader(), new Class[] {ResultSet.class}, new ResultSetJPWrapper(rs))); } private static class ResultSetJPWrapper implements InvocationHandler { private ResultSet wrappedResultSet; public ResultSetJPWrapper(ResultSet rs) { wrappedResultSet = rs; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(wrappedResultSet, args); if ("getString".equals(method.getName()) && null != result) { result = CommonUtils.convertCharset((String)result, ISAConstants.CHARSET_8859_1, ISAConstants.CHARSET_UTF_8); } return result; } } public static PreparedStatement createPreparedStatementJPWrapper(PreparedStatement pstmt) { return (PreparedStatement)(Proxy.newProxyInstance(PreparedStatement.class.getClassLoader(), new Class[] {PreparedStatement.class}, new PreparedStatementJPWrapper(pstmt))); } private static class PreparedStatementJPWrapper implements InvocationHandler { private PreparedStatement wrappedStatement; public PreparedStatementJPWrapper(PreparedStatement pstmt) { wrappedStatement = pstmt; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("setString".equals(method.getName()) && args.length == 2 && null != args[1] && String.class.equals(args[1].getClass())) { args[1] = CommonUtils.convertCharset((String)args[1], ISAConstants.CHARSET_UTF_8, ISAConstants.CHARSET_8859_1); } return method.invoke(wrappedStatement, args); } }
调用重构:
// 重构前 PreparedStatement stmt = conn.prepareStatement(sql); // 重构后 PreparedStatement stmt = DBUtils.createPreparedStatementJPWrapper(conn.prepareStatement(sql)); // 重构前 ResultSet rs = stmt.executeQuery(); // 重构后 ResultSet rs = DBUtils.createResultSetJPWrapper(stmt.executeQuery());
除此之外,spring 的AOP也大量的使用了动态代理这一思想,但与我们通常使用不同的是,AOP可以针对不同的接口做代理。
脑补一下理论:
动态代理应用了字节码生成技术,它在代码中的体现是java.lang.reflect.proxy和java.lang.reflect.InvocationHandler接口。最终调用的方法时sum.misc.ProxyGenerator.generatorProxyClass()方法生成了一个新类的字节码,该类继承了Porxy类并实现了方法newProxyInstance中传入的接口。
简单来说,我们通过动态代理获取的类实际上是实现了我们需要接口的一个通过字节码生成技术new出来的新类,然后我们就可以像调用其他对象一样使用这个类了。
脑补一下代理模式
代理模式可以看做是在真实对象之外,提供了另外的访问接口,它关注的是代理原对象的服务,如网络对象可以由本地对象代替……,通过该代理对象,实际上加入了访问的间接性,也利于我们在两个对象间加入访问控制等。
相关模式:
抄一下GOF中关于代理模式与其他模式的区别:
Adapter 适配器Adapter为他所适配的对象提供了一个不同的接口,而代理模式提供了与实体相同的接口。
Decorator 装饰模式 与代理模式类似,但目的与proxy不一样,是为了对装饰对象新增特性和功能,而代理则控制对象访问。
结束