0%

这个标题看起有点鸡汤文,不过我还是建议对以下总结出的几点做些深入思考,这些会在今后的工作中越来越多的感受到它的作用。

寻找你行业内的专家

找到你所属行业内的专家,这些人往往做事高效并且很有才华。你要做的是跟随他们所关注的方向,学习他们做事的方法,思考如何应用到你的工作和生活上。找到他们,和他们去交流思考,提出自己的观点和想法。不要仅仅把眼光放到身边的人身上,这样会局限住你的视野。

每天都写新代码

工作重复枯燥?也许有时候我们只是懒得思考,用最顺手的方式把工作做完,容易形成惯性思维。为什么会有很多的复制粘贴?简单的修改来适配当前需求,这里我们更需要的是想想能不能把这段逻辑抽象出来变得更通用,整个模块的设计是否不够合理,多想一想多做一点,下一次再来需求也许可以提升十倍的效率。

底层的原理更重要

客观的说,更快进步的方法之一是忽略掉那些并不能提高技能的东西,比如语言语法和配置工具,这些技能属于“知其然”,而你更需要的是“知其所以然”。有一次去医院科室挂号使用的是先到先叫的模式,而在急诊室挂号是按照轻重缓急分成四个等级的,危重病人优先抢救的模式。这不就和操作系统中的任务调度概念是一样的,优先级调度模式,这些底层的概念才是一通百通真正提高帮助你的东西。我在尝试去找行业经典论文看。

学会调研

作为程序员会比较容易脑子一热,有一个想法很容易趁热着急写代码,但往往缺乏思考写出来的代码不能尽如人意。这时候你更需要的是慢下来,好好思考一下,也许这些别人已经做过,有更好的方案,看看别人是如何做的。先调研再实施,这样会彻底改变你解决问题的思路。

学好英语

真的是这样,如果你英语不好,那么会比别人走更多的弯路,就像走在密林深处看不清路一样。不得不承认很多优秀框架的官方文档还是英文为主,如果再经过翻译里面的很多语义语境会丢失,在项目的社区中,你还能与作者们去交流你学习中遇到的问题。

如何去做

说了这么多,看着就好像道理我都懂,但是我不知道怎么做。我这里先总结几个点,也是自己在不断尝试学习的方法。

  1. 看行业经典论文,比如 mapreduce、raft 这些都是一通百通的底层概念。
  2. 研究优秀框架的源代码,理解核心原理,尝试造轮子。
  3. 每天学英语,尝试在开源社区与作者们进行互动。
  4. 找到一两位行业专家,向他们学习和请教问题。
  5. 坚持以上几点。

end.

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
@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
@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
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

在 mac 机器上可以使用 mweb 来写博客,比较好用的地方就是可以直接把剪贴板的图片粘贴上来,缺点是 mac 键盘超难用并且不支持窗口内开启命令行。平时在家的时候都用 Ubuntu 台式机,博客使用 VS Code 编写,一直以来阻挡我的是图片的粘贴特别费劲,今天发现一个很好用的插件 pasteimage,可以直接将剪贴板图片粘贴到 markdown 使用,并且支持配置保存路径。

然后按照教程配置好参数:

1
2
3
4
5
6
{
"pasteImage.path": "${projectRoot}/source/resource/img",
"pasteImage.basePath": "${projectRoot}/source",
"pasteImage.forceUnixStyleSeparator": true,
"pasteImage.prefix": "/"
}

就可以直接将图片粘贴到 markdown 中,其中遇到个问题就是配置不生效,会导致文件直接保存到当前文件目录,具体配置方法可以参考下面连接。

https://www.crifan.com/vscode_how_to_config_setting_plugin/ 这篇文章写的很详细了。
https://github.com/mushanshitiancai/vscode-paste-image 这篇是配置教程,里面有些地方比较容易被误导。

对于Linux系统需要有 xclip 支持,使用的时候会给提示的。

另外记录一下 Ubuntu 的截屏和粘贴快捷键:

1
2
Ctrl + Shift + Print Screen  // 区域截屏到剪贴板
Ctrl + Alt + s // 在 VS Code 中粘贴

设计原则

如果把设计模式理解为优秀软件系统模块设计的最小抽象,那么设计原则就是这些抽象的指导思想。目的是设计一个易于扩展和维护的系统。设计模式的六大原则有:

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

单一职责原则

应该有且只有一个原因引起类的变化,一个类只负责一个职责。一个功能应该要划分成多少个职责类去实现,并没有明显的限定。举例说明对于用户管理,用户信息管理和用户行为管理可以做初步拆分,用户信息管理又可以拆分成普通信息维护和敏感信息的维护。又比如用户发生一笔支付行为可以初步拆分为交易信息管理和支付信息管理。职责划分的粗细的影响因素有对于业务理解程度、项目开发阶段等,过粗会造成一个处理类包含太多职责,过细又会增加开发维护成本。单一职责是“高内聚低耦合”设计的一种实现形式,单一职责即为同一职责内部的内聚性,降低不同职责之间的耦合性。

里氏替换原则

描述继承关系,子类全部实现或继承父类方法,子类可以扩展父类方法实现,将子类替换父类不会产生异常。在重构角度来说如果多个子类拥有相同的行为,可以将相同行为提取到父类实现,子类调用扩展父类实现。在开发上基于“组合大于继承”的原则,通过定义实现接口的形成被其它类调用。违反这个原则不一定会产生严重后果,但是会对后面的开发维护造成困难。

开闭原则

描述的是对于需求产生变化后,软件实体部分应该进行扩展开发,避免修改。通过扩展实体行为来响应需求变化,而不是通过修改现有软件代码。

迪米特法则

描述的是一个对象应该进行减少对于其它对象的理解。通过封装我们可以屏蔽类内部逻辑,只提供足够用且少量的方法来给外部使用,降低对象之间的耦合性。当一个接口或者一个对象被公开,意味着后面我们进行开发和维护的时候很难再将这个对象收回,重构内部逻辑时也会更加困难。

接口隔离原则

描述的是建立单一的接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量行为统一,避免臃肿。对于支付接口来说,定义类通用支付方法,对于获取分期支付信息也属于支付行为的一个环节,但不是所有实体类必须要实现的,可以拆分出来。

依赖倒置原则

描述的是实现类之间不能直接发生依赖关系,其依赖关系是通过接口或者抽象类产生,即面向接口编程。实现类依赖接口或者抽象类,而接口或者抽象类不依赖实现类。

设计模式

https://design-patterns.readthedocs.io/zh_CN/latest/index.html

  • 六本本职技术书籍/专题
    • effective java done 01.29
    • Java 并发编程实战 done 02.14
    • 深入理解 Java 虚拟机
  • 六本技术扩展书籍/专题
    • 图解密码技术 第三版
    • Linux 内核设计与实现(原书第三版)
    • 现代编译原理
  • 六本人文社科书籍
    • 精进 done 02.11
    • 国富论 杨敬年译本
    • 乌合之众 冯克利译本
    • 黑客与画家
  • 整理输出两篇高质量博客文章