Java动态代理之接口实现
动态代理admin 发布于:2019-09-02 22:11:47
阅读:loading
在此之前个人技术的渣渣限制了我的想象,我所知道的接口在使用的时候,我们都是使用它的实现类,虽然有接触到许许多多的$Proxy$代理实现类,但从来没去想过这个破玩意到底是个什么,知道前段时间别的同事使用Spring data jpa查询返回List结果集的时候,泛型是个接口类型,也就听着他们随口的几句话让我觉得眼前亮了一下。
将SQL查询出来的结果集映射为一个Javabean对象,我们肯定都是有许许多多的实现版本了,使用反射将数据库查询出来的column名称与属性名称进行转换(再有就是匈牙利转驼峰),总的来说还是比较简单的,但这里的实现是将这个结果集转换为一个接口类,其中接口类提供多个get函数,也就是说将各个字段的返回值映射到这个具体的get方法上,说的也许抽象了,查看下列接口类的定义,实现将数据库查询出来的每条数据都映射到该接口的各个方法中:
package cn.chendd.tips.examples.proxy.vo;
import java.util.Date;
/**
* @author chendd
* @since 2019-09-01 09:03
*/
public interface User {
public String getUser();//获取用户
public Long getFlag();//获取状态
public String getAccountLocked();//获取账户类型
public Date getPasswordLastChanged();//获取日期
}
代码说明:此处定义了User接口,对外最终提供List<User>结果集,接口中定义了不同种类的类型定义,分别是String、Long(其实在sql中返回的结果集类型为Boolean类型,MySQL自动将其转换为0和1)、Date等类型,到这里也许你也不能理解竟然还可以这么cao作吧。
回到这个需求中,正常我想到了代理的两种实现方式,第一种是先查询出来List<Map<String , Object>>类型的结果集,再循环这个结果集将其中的Map键值对结果转换为List<User>类型(其中键值与User接口中去掉get前缀且get后的首字母小写相匹配),即实现是需要多循环一个集合后将数据返回;第二种方式则是直接封装JDBC对象中的CacheRowSet(离线结果集,它允许cao作一个已经关闭Connection对象中的结果集)对象,后来经过尝试感觉这种实现对于JDBC的嵌套的太深了,最终采取第一种方式实现,个人也推荐第一种。在上文实现的基础上看本篇其实代码比较简单,主要在于有此认知,相关的代码如下:
package cn.chendd.tips.examples.proxy;
import ...
/**
* @author chendd
* @since 2019-08-30 23:27
* 数据库查询结果ResultSet结果集代理
*/
public class MapProxyHandler implements InvocationHandler {
private Map<String , Object> map;
public MapProxyHandler(Map<String , Object> map){
this.map = map;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
String suffixs[] = {"get" , "is"};
for (String suffix : suffixs) {
if(methodName.startsWith(suffix)){
//去掉get或is开头前缀,将剩余部分首字母小写
int lens = suffix.length();
String name = methodName.substring(lens , lens + 1).toLowerCase()
+ methodName.substring(suffix.length() + 1);
return map.get(name);
}
}
return null;
}
}
package cn.chendd.tips.examples.proxy;
import ...
/**
* @author chendd
* @since 2019-08-31 23:28
*/
public class MapProxyTest {
@Test
public void test() {
List<Map<String, Object>> dataList = this.getDataList();
//转换数据,将map转换为bean
List<User> userList = new ArrayList<>();
for (Map<String, Object> map : dataList) {
User user = this.newInstance(User.class , map);
userList.add(user);
}
//打印结果
for (User user : userList) {
System.out.println(user.getUser() + "," + user.getAccountLocked() + "," +
user.getPasswordLastChanged() + "," + user.getPasswordLastChanged());
}
}
private <T> T newInstance(Class<T> clazz , Map<String, Object> map){
MapProxyHandler proxyHandler = new MapProxyHandler(map);
T t = (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class[]{clazz} , proxyHandler);
return t;
}
/**
* 根据sql返回对应的结果集List<Map<String,Object>>
*
* @return
*/
private List<Map<String, Object>> getDataList() {
String sql = "select " +
"user as user, " +
"case when 1=2 then false else true end as flag, " +
"account_locked as accountLocked, " +
"password_last_changed passwordLastChanged from user";
return DBManager.getDataList(sql);
}
}
package cn.chendd.tips.examples.proxy;
import ...
/**
* @author chendd
* @since 2019-08-31 23:28
*/
public class RowSetProxyTest {
@Test
public void test() {
String sql = "select " +
"user as user, " +
"case when 1=2 then false else true end as flag, " +
"account_locked as accountLocked, " +
"password_last_changed passwordLastChanged from user";
List<User> dataList = this.getDataList(sql, User.class);
//打印结果
for (User user : dataList) {
System.out.println(user.getUser() + "," + user.getAccountLocked() + "," +
user.getPasswordLastChanged() + "," + user.getPasswordLastChanged());
}
}
private <T> T newInstance(Class<T> clazz , Map<String , Object> map){
MapProxyHandler proxyHandler = new MapProxyHandler(map);
T t = (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader() , new Class[]{clazz} , proxyHandler);
return t;
}
/**
* 根据sql返回对应的结果集List<Map<String,Object>>
*
* @return
*/
private <T> List<T> getDataList(String sql , Class<T> clazz) {
RowSet rowSet = DBManager.getResultSet(sql);
List<T> dataList = new ArrayList<>();
try {
ResultSetMetaData rsmd = rowSet.getMetaData();
int columnCount = rsmd.getColumnCount();
while(rowSet.next()){
Map<String , Object> columnMap = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnLabel(i);
Object value = rowSet.getObject(i);
columnMap.put(columnName , value);
}
T t = this.newInstance(clazz , columnMap);
dataList.add(t);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(rowSet != null) {
try {rowSet.close();} catch (SQLException e) {}
}
}
return dataList;
}
}
其它非关键类DBManager不贴出来了,可见后文中的源码。
(1)该代理接口User中无法重写toString函数,所以无论是debug还是sout直接查看这个类的时候,它的地址将会是null,但它可以直接调用自身的getXXX方法;
(2)本例采用查询MySQL数据库中的user表的用户数据;
点赞