反射示例及利用javassist提升反射效率

478 阅读2分钟

开场白

反射虽然在我们平时写业务代码的时候用的不多,但在写框架或架构的时候用的还是挺多的。下面用查询数据库数据的示例来说明一下javassist的应用。

远古未优化的代码

public class UserEntity {
    public int userId;
    public String name;
    public String password;
}

public class App000 {

    public static void main(String[] args) throws Exception {
        (new App000()).start();
    }

    private void start() throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");

        String dbConnStr = "jdbc:mysql://localhost:3306/user?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&user=hao123&password=hao123";
        Connection conn = DriverManager.getConnection((dbConnStr));
        Statement stmt = conn.createStatement();

        String sql = "select * from user limit 200000";

        ResultSet rs = stmt.executeQuery(sql);
        long t0 = System.currentTimeMillis();
        while (rs.next()) {
            UserEntity ue = new UserEntity();
            ue.userId = rs.getInt("id");
            ue.name = rs.getString("name");
            ue.password = rs.getString("password");
        }
        long t1 = System.currentTimeMillis();
        System.out.println("实例化花费时间="+(t1-t0)+"ms");
        stmt.close();
        conn.close();
    }
}

这基本就是一个最简单的通过jdbc查询数据的方法。

通过反射实现的较为通用的方法

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {

    /**
     * 列名称
     * @return
     */
    String name();
}


public class UserEntity {

    /**
     * 用户id
     */
    @Column(name = "id")
    public long userId;
    @Column(name = "name")
    public String name;
    @Column(name = "password")
    public String password;
}


public class XxxEntity_Helper {

    public <TEntity> TEntity create(Class<TEntity> entityClazz, ResultSet rs) throws Exception {

        if (entityClazz == null || null == rs) {
            return null;
        }

        Object newEntiry = entityClazz.newInstance();

        Field[] fields = entityClazz.getFields();
        for (Field field : fields) {
            Column annoColumn = field.getAnnotation(Column.class);
            if (annoColumn == null) {
                continue;
            }

            String columnName = annoColumn.name();
            Object columnVal = rs.getObject(columnName);
            if (columnVal == null) {
                continue;
            }

            field.set(newEntiry, columnVal);
        }

        return (TEntity) newEntiry;
    }
}

Column注解可以添加到Entity类的字段上,和我们平时用的JPA注解很类似。XxxEntity_Helper类会通过反射来处理注解,把ResultSet转换成实体类,不需要我们再手动设置了。这样有个问题就是反射的效率,我这本机跑是比手动设置慢1倍多。

通过javassist提升反射效率

public abstract class AbstractEntityHelper {
    public abstract Object create(ResultSet rs) throws Exception;
}

public final class EntityHelperFactory {

    private EntityHelperFactory(){}

    public static AbstractEntityHelper getEntityHelper(Class<?> entityClazz) throws Exception {
        if (entityClazz == null) {
            return null;
        }

        ClassPool pool = ClassPool.getDefault();
        pool.appendSystemPath();

        // import java.sql.ResultSet;
        // import com.ormtest.step020.entity.UserEntity;
        pool.importPackage(ResultSet.class.getName());
        pool.importPackage(entityClazz.getName());
        // 拿到 AbstractEntityHelper 类
        CtClass clazzHelper = pool.getCtClass(AbstractEntityHelper.class.getName());
        // 要创建的助手类的名称
        String helperClazzName = entityClazz.getName()+"_Helper";

        // 创建 XxxEntity_Helper extends AbstractEntityHelper
        CtClass cc = pool.makeClass(helperClazzName, clazzHelper);

        // 创建构造器
        // 生成如下代码 :
        // public XxxEntity_Helper(){}
        CtConstructor constructor = new CtConstructor(new CtClass[0], cc);
        constructor.setBody("{}");

        cc.addConstructor(constructor);

        StringBuffer sb = new StringBuffer();
        sb.append("public Object create(java.sql.ResultSet rs) throws Exception{\n");

        sb.append(entityClazz.getName())
                .append(" obj = new ")
                .append(entityClazz.getName())
                .append("();\n");
        Field[] fields = entityClazz.getFields();
        for (Field field : fields) {
            Column annoColumn = field.getAnnotation(Column.class);
            if (annoColumn == null) {
                continue;
            }

            String columnName = annoColumn.name();

            if (field.getType() == Integer.TYPE) {
                sb.append("obj.")
                .append(field.getName())
                .append(" = rs.getInt("")
                        .append(columnName)
                .append("");\n");
            } else if (field.getType() == String.class) {
                sb.append("obj.")
                        .append(field.getName())
                        .append(" = rs.getString("")
                        .append(columnName)
                        .append("");\n");
            }
        }

        sb.append("return obj;");
        sb.append("}");

        // 创建方法
        CtMethod cm = CtNewMethod.make(sb.toString(), cc);
        cc.addMethod(cm);

        //cc.writeFile("/Users/telnet/debug_java");

        Class javaClazz = cc.toClass();
        Object helperImpl = javaClazz.newInstance();

        return (AbstractEntityHelper) helperImpl;
    }
}

EntityHelperFactory相当于通过javassist直接生成我们手动设置数据的代码。我们可以通过writeFile把生成的字节码输出看看。 通过javassist提升后效率和手动设置已经相差无几了。这其实就算是一个简单的ORM了。