知一杂谈

Java 动态代理实现 ORM

字数统计: 1.7k阅读时长: 7 min
2020/04/29

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

原文作者:noogel

原文链接:https://noogel.xyz/2020/04/29/1.html

发表日期:2020-04-29

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 代理
    1. 1.1. 静态代理
    2. 1.2. 动态代理
      1. 1.2.1. InvocationHandler
      2. 1.2.2. Proxy
  2. 2. 实现
    1. 2.1. 注解
    2. 2.2. JDBC OP
    3. 2.3. 动态代理部分
  3. 3. 扩展
  4. 4. 总结
  5. 5. 参考