基于JDK Proxy代理扩展实现JFinal的SQL拦截

使用过MyBatis的小伙伴们应该知道,MyBatis支持SQL拦截的功能,如果项目使用JFinal,也想使用SQL拦截功能,读JFinal源码发现,JFinal目前还不支持此功能,仅仅只是在Config类中调用SqlReporter在控制台输出SQL。于是,参考这个SqlReporter简单实现了SQL拦截的功能,供有需要的小伙伴参考。

SQL拦截代理类:

/**
 * 基于JDK Proxy实现SQL拦截
 * @param <T>
 */
public class JdkProxy<T> implements InvocationHandler {

    private final Set<String> methods = new HashSet<>(Arrays.asList("getDataSource", "getConnection", "prepareStatement"));
    private T target;

    public JdkProxy(T target) {
        this.target = target;
    }

    @SuppressWarnings("unchecked")
	public T newProxyInstance() {
        Class<?> clazz = target.getClass();
        Class<?>[] classes;
        if (clazz.isInterface()) {
            classes = new Class[]{clazz};
        } else {
            classes = clazz.getInterfaces();
        }
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), classes, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (methods.contains(method.getName())) {
                if (("getDataSource".equals(method.getName()) || "getConnection".equals(method.getName())) && (args == null || args.length == 0)) {
                    return new JdkProxy<Object>(method.invoke(target, args)).newProxyInstance();
                }

                if ("prepareStatement".equals(method.getName()) && args != null && args.length > 0) {
                    // 拦截变更sql
                	args[0] = modifySql(String.valueOf(args[0]));
                }
            }
            return method.invoke(target, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    
    /**
     * 修改sql
     * @param originSql
     * @return
     */
    private String modifySql(String originSql) {
    	originSql = originSql.trim();
    	// SQL语句类型:SELECT、INSERT、UPDATE、DELETE、TRUNCATE、ALTER、CREATE、。。。
        String sqlType = originSql.substring(0, originSql.indexOf(" ")).toUpperCase();
        String sql = simplifySql(originSql);
        String datetime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(Instant.now());
        switch(sqlType) {
        case "DELETE":
        	// 【拦截删除操作,禁止任何物理删除操作】
        	sql = "UPDATE" + sql.substring(11, sql.length());
            sql = sql.substring(0, sql.toUpperCase().indexOf(" WHERE ")) + " SET deleteFlag = '1', updateAt='" + datetime + "' " + sql.substring(sql.toUpperCase().indexOf(" WHERE "), sql.length());
            break;
        case "UPDATE":
        	sql = sql.substring(0, sql.toUpperCase().indexOf(" WHERE ")) + ", updateAt='" + datetime + "' " + sql.substring(sql.toUpperCase().indexOf("WHERE "), sql.length());
        	break;
        case "INSERT":
        	String upperSql = sql.toUpperCase();
            if (upperSql.contains("VALUES")) {
                String[] valuesStrArray = {") VALUES(", ") VALUES (", ")VALUES(", ")VALUES ("};
                int index = -1;
                for (String str : valuesStrArray) {
                    index = upperSql.indexOf(str);
                    if (index > 0) {
                        // TODO 获取当前用户ID
                        String deptId = "";
                        sql = sql.substring(0, index) + ",statusCode,updateAt,createBy,createByDeptId) " + sql.substring(index + 1, sql.length());
                        sql = sql.substring(0, sql.length() - 1) + ",'1','" + datetime + "','" + getCurrentUser() + "','" + deptId + "')";
                        break;
                    }
                }
                if (index <= 0) {
                    System.err.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>非法的INSERT SQL语句:" + sql.replaceAll("(?m)^\\s*$(\\n|\\r\\n)", ""));
                }
            }
            break;
        default:
        	return sql;
        }
        System.out.println("变更前sql:" + originSql + "\n变更后sql:" + sql);
        return sql;
    }
    
    /**
     * 获取当前用户ID
     * @return
     */
    private String getCurrentUser() {
    	return "74897C57832941ABBB2E596017504C334R92R1SH";
    }
    
    /**
     * 将SQL语句变成一条语句,并且每个单词的间隔都是1个空格
     * @param sql
     * @return
     */
    private String simplifySql(String sql) {
        return sql.replaceAll("[\r\n]", " ").replaceAll("\\s{2,}", " ");
    }
}

JFinalConfig中使用:

public class Config extends JFinalConfig {

    @Override
    public void configConstant(Constants me) {}

    @Override
    public void configEngine(Engine me) {}

    @Override
    public void configRoute(Routes me) {}

    @Override
    public void configHandler(Handlers me) {}

    @Override
    public void configInterceptor(Interceptors me) {}
    
    @Override
    public void configPlugin(Plugins me) {
        IDataSourceProvider dbPlugin = addDbPlugin(me);
        // 配置ActiveRecord插件
        ActiveRecordPlugin arp = new ActiveRecordPlugin(dbPlugin);
        me.add(arp);
    }
    
    /**
     * 创建DruidPlugin
     * @param me
     * @return
     */
    private IDataSourceProvider addDbPlugin(Plugins me) {
        DruidPlugin druidPlugin = new DruidPlugin(jdbcUrl, jdbcUser, jdbcPassword, jdbcDriver);
        me.add(new JdkProxy<>(druidPlugin).newProxyInstance());
        // 【注意】不能直接返回druidPlugin
        return (IDataSourceProvider) me.getPluginList().get(me.getPluginList().size() - 1);
    }

    @Override
    public void onStart() {}

    @Override
    public void onStop() {} 
}

结束。

评论区

JFinal

2019-07-09 21:20

通过 JDK 代理 druidPlugin 这个思路第一次见,挺有新意,谢谢分享

pipi

2019-07-12 13:53

给力