Java动态代理之接口实现

动态代理
placeholder image
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)){
               
//去掉getis开头前缀,将剩余部分首字母小写
               
int lens = suffix.length();
                String name = methodName.substring(lens , lens +
1).toLowerCase()
                        + methodName.substring(suffix.length() +
1);
               
return map.get(name);
            }
        }
       
return null;
    }
}

Map测试类

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);
    }

}

RowSet测试类

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不贴出来了,可见后文中的源码。

运行示例

image.png

小总结

(1)该代理接口User中无法重写toString函数,所以无论是debug还是sout直接查看这个类的时候,它的地址将会是null,但它可以直接调用自身的getXXX方法;

(2)本例采用查询MySQL数据库中的user表的用户数据;

源码下载

https://gitee.com/88911006/chendd-examples/tree/master/tips/test/main/java/cn/chendd/tips/examples/proxy


 点赞


 发表评论

当前回复:作者

 评论列表


留言区