开场白
反射虽然在我们平时写业务代码的时候用的不多,但在写框架或架构的时候用的还是挺多的。下面用查询数据库数据的示例来说明一下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了。