Java 动态代理实现 ORM

ORM(Object/Relational Mapper),即“对象-关系型数据映射组件”。对于O/R,即 Object(对象)和Relational(关系型数据),表示必须同时使用面向对象和关系型数据进行开发。本文简述通过 Java 动态代理机制实现关系数据与 POJO 对象的映射。

代理

静态代理

静态代理其实就是指设计模式中的代理模式。
代理模式为其他对象提供一种代理以控制对这个对象的访问。

静态代理模式在增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护。

动态代理

为了解决静态代理的问题,引入动态代理的概念,在编译时或者运行时,可以在需要代理的地方动态生成代理,减轻代理类和类在系统中冗余的问题。

Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。

InvocationHandler

InvocationHandler 接口定义:

1
2
3
4
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

每一个动态代理类都必须要实现 InvocationHandler 这个接口,通过代理类的实例调用一个方法时,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。

Proxy

Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法,可以获得一个动态的代理对象:

1
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler h)  throws IllegalArgumentException

实现

参照 mybaits 的用法实现基本的映射能力。

注解

首先定义了三个注解,一个作用在类上 DaoMapper 作用在类上标记这是一个映射类,然后定义注解 Selector 作用在方法上标记查询作用,定义注解 Param 作用在参数上为预编译位的映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DaoMapper {
}

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Selector {
String value();
}

@Documented
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {
String value();
}

定义一个实体类,与数据库的表字段映射上。增强 feature 可以自动做驼峰转换,这里没有实现。

1
2
3
4
5
6
7
8
9
@Data
public class BaseLineModel {
public static final String TABLE = "baseline";

private Integer id;
private String report_name;
private Integer report_period;
private LocalDateTime creation_date;
}

定义dao层接口,加上注解

1
2
3
4
5
6
7
@DaoMapper
public interface BaseLineDao {

@Selector("select * from "+ BaseLineModel.TABLE +" where report_name = #{reportName}")
BaseLineModel select(@Param("reportName") String report_name);
}

JDBC OP

做到一个很简单的 JDBC 操作工具类,字段映射处理也写到了这里。实现了查询操作,将入参 sql template 以及参数按顺序传入,生成 prepareStatement 后执行,再将返回结果映射到 model 对象。这里的连接池管理、自动重连、配置管理等增强 features 非重点,不做实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 查询
* @param clazz model类
* @param sql
* @param params
* @param <T>
* @return
*/
public <T> T query(Class<T> clazz, String sql, Object... params) throws SQLException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Object model = clazz.newInstance();
try (Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cat", "root", "123456")) {
PreparedStatement statement = conn.prepareStatement(sql);
int flag = 1;
for (Object obj : params) {
setValue(statement, flag, obj);
flag++;
}
ResultSet resultSet = statement.executeQuery();
resultSet.afterLast();
resultSet.previous();
fullRes(resultSet, model);
}
return (T) model;
}

映射函数,通过自动寻找 setter 方法填充结果,这里只实现了三种字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void fullRes(ResultSet resultSet, Object model) throws SQLException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Field[] declaredFields = model.getClass().getDeclaredFields();
for (Field field : declaredFields) {
String fieldName = field.getName();
if (fieldName.toUpperCase().equals(fieldName)) {
continue;
}
String setFuncName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
String fieldType = field.getGenericType().toString();

Object object = resultSet.getObject(fieldName);
if (fieldType.equals("class java.lang.String")) {
Method m = model.getClass().getMethod(setFuncName, String.class);
m.invoke(model, object);
} else if (fieldType.equals("class java.lang.Integer")) {
Method m = model.getClass().getMethod(setFuncName, Integer.class);
m.invoke(model, object);
} else if (fieldType.equals("class java.time.LocalDateTime")) {
Method m = model.getClass().getMethod(setFuncName, LocalDateTime.class);
if (object instanceof Timestamp) {
object = ((Timestamp) object).toLocalDateTime();
}
m.invoke(model, object);
}
}
}

动态代理部分

定义一个 MapperMethod 类,实例化的时候提取接口方法的注解信息解析成 JDBC 需要的参数以及记录接口方法的返回对象, execute 执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

public class MapperMethod<T> {
private String sql;
private Class<?> resType;
private int[] paramsIndex;


public MapperMethod(Method method) {
this.resType = method.getReturnType();
String sourceSql = method.getAnnotation(Selector.class).value();
Parameter[] parameters = method.getParameters();
int flag = 0;
this.paramsIndex = new int[parameters.length];
for (Parameter parameter: parameters) {
String paramName = parameter.getAnnotation(Param.class).value();
String paramFullName = String.format("#{%s}", paramName);
int indexOf = sourceSql.indexOf(paramFullName);
this.paramsIndex[flag] = indexOf;
flag++;
this.sql = sourceSql.replace(paramFullName, "?");
}
}

public Object execute(Object[] objects) {
JdbcUtil jdbcUtil = new JdbcUtil();
try {
return jdbcUtil.query(this.resType, this.sql, objects);
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
}

定义动态代理类,在实例化的时候记录代理接口,以及代理方法类缓存,调用接口的时候会被动态代理到 invoke 函数执行,然后交由 MapperMethod 代理方法实例执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;

public class MapperProxy<T> implements InvocationHandler {

private final Class<T> mapperInterface;

private final Map<Method, MapperMethod> methodCache;

public MapperProxy(Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}

@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(objects);
}

private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (Objects.isNull(mapperMethod)) {
mapperMethod = new MapperMethod(method);
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}

最后代理工厂类,接收被 DaoMapper 作用的接口,并通过 newInstance 方法创建代理类实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MapperProxyFactory<T> {

private final Class<T> mapperInterface;

private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

public MapperProxyFactory(Class<T> mapperInterface) {
if (Objects.isNull(mapperInterface.getAnnotation(DaoMapper.class))) {
throw new RuntimeException("缺少注解 DaoMapper");
}
this.mapperInterface = mapperInterface;
}


public T newInstance() {
final MapperProxy<T> mapperProxy = new MapperProxy<>(mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}

执行,创建一个代理工厂,然后创建 BaseLineDao 的代理对象, 调用 select 方法,实际上调用到代理对象的 invoke 方法,然后交由 mapperMethod.execute 方法执行:

1
2
3
4
5
6
public static void main(String[] args) {
MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(BaseLineDao.class);
BaseLineDao baseLineDao = (BaseLineDao) mapperProxyFactory.newInstance();
BaseLineModel test1 = baseLineDao.select("TEST1");
System.out.println(test1);
}

扩展

TODO:

  1. Java动态代理与 cglib 动态代理的异同点。
  2. 动态代理的实现原理。

总结

通过这个个简单的实践,了解了 Java 动态代理的使用方法以及对象关系数据的映射处理。

参考

https://zhuanlan.zhihu.com/p/60805342
https://www.zhihu.com/question/20794107/answer/658139129