工作3年会遇到什么技术

工程师与程序录入员的区别就是工程师不仅仅只做编码工作。当然编码是最基础的,除此外还有诸如部署、Support、解决方案提供等「杂活」。曾经我觉得除了编码都是不务正业浪费时间,现在我开始慢慢的去改变这一观点并慢慢去做好很多细节。举个例子,我刚来工作的时候,连Eclipse也用不好,毕竟学生时代用Vim什么的就足够了。

我在刚刚工作时一直希望能够得到一篇弥补象牙塔与真正工业界之间GAP的指导,奈何都不是我特别需要的,所以这里撰文一篇,记录我自己的一些技术所需。因为业务涉密,所以不列举具体业务内容。

Web项目

文档,环境

  1. 文档阅读。业务和工程架构都在这里面了,看着眼花的UML图,多多少少是抗拒的。而且行文质量不高的文档很难给予更多的帮助
  2. 环境部署。Java的部署比较麻烦,也可能仅仅是学生项目的部署简单

基础技术细节

  1. 我自己也做过Web项目,我写的项目前端和后端是在一起的。然后现代工业界是彻底分离的,不仅仅是编码层面的分离。根本就是两个服务器,前端启一个,后端启一个。然后设计成RESTful的API。
  2. 知道了单元测试(测方法)、集成测试(测功能)和端对端测试(前端到后端的贯穿)是什么,也是工作中才接触这些的。
  3. 发邮件有主动请求的,有很多库可以render邮件的内容。另一种是AOP,切面编程。可以用Java的动态代理实现,也可以借助Spring这些框架的功能配置一下。
  4. 工作流JBPM,我们可以先配置一个Workflow,然后用Java编写ActionHandler,其实我刚工作前3个月都不是特别清楚这个有什么用。其实Workflow比较复杂的时候JBPM可以帮助我们更标准化的管理工作流,就是按照一定程序执行业务
  5. 写过Hibernate、JPA、JDBC,最后都是用JDBC自己设计的。之所以用JDBC是为了支持的更General一些,这是因为Hibernate每个表都有一个对应的Class,ORM的初衷也是如此。这样添加新的表就必须有新的Class,不利于扩展。用JDBC实现Common方法后就可以避免这些问题
  6. Java的包管理Maven是用XML配置的,其实当下更通用的是Json
  7. Angular JS是HTML与JS(TS)混编,Ext JS则是纯JS。HTML做数据绑定和布局,JS写逻辑
  8. 多个包(独立项目)在Java中可以相互依赖,或者说相互依赖功能都是独立的Jar包
  9. Eclipse的快捷键需要记住常用的
  10. javaDoc如果有什么问题,可以用参数Skip掉,不过这个仅适用于Maven项目
  11. JVM不负责回收直接内存;非堆内存:有很大的数据要存,生命周期又很长。适合频繁的IO操作,比如网络并发。(ByteBuffer,allocateDirect)
  12. 数据库分页很重要
  13. Eclipse直接启动有时候不容易成功,需要update一下,或者做Clean Project,对于Tomcat Server也要clean
  14. 针对项目,tomcat可能要修改Augument
  15. Java项目中会有类似名字为app.properties保存数据库连接
  16. Websphere和Jenkins:
    1. Jenkins中对要Deploy的Project进行Build然后下载
    2. 在WebSphere中Applications->All Applications -> Manage Modules -> update WAR包
    3. 一路点next
    4. 在WebSphere中start并且refresh
  17. JS有Strict mode,比如说只有声明后的变量才能够使用
  18. JPA创建时需要强制类型转换(泛型)
  19. java.lang.NoClassDefFoundError一般可能是jar包结构产生的变化
  20. JPA用的是建造者模式
  21. type.equals(“one”)要写成 “one”.equals(type)
  22. SLF4J是日志专用API
  23. 遇到java.lang.ClassNotFoundException:
    1. properties
    2. Deployment Assembly
    3. Add - java BuildPath entities
    4. Maven Dependencies
  24. Java的PDF generation:
    1. Apache FOP: standard XSL-FO file format
    2. JAXB: Java Architecture for XML Binding
    3. XSLT transform
    4. Marshalling - Convert a Java object into a XML file
    5. Unmarshalling - Convert XML content into a Java Object
    6. JxlsHelper.getInstance().processTemplate(is, os, context)
  25. 利用Java的反射可以实现一种test方式专门test POJO,不需要再手动去写很多test cases,而是根据方法的入参类型全自动测试
  26. 写unit test的时候,为了提高private方法的覆盖率,写了反射:用字符串获取方法
  27. JDBC相关:
    1. 组件:
      1. 创建连接:Driven Manage / Java.sql.DriveManager
      2. JDBC数据源:javax.sql.DataSource
      3. java.sql.Connection,执行CRUD(增删改查),基于java.sql.Statement;java.sql.PreparedStatement(多次执行后者高效)。查询返回多行数据 ResultSet
    2. 数据库连接:Class.forName(“com.mysql.jdbc.Driver”);
      connection con = DriverManager.get(“jdbc: mysql://localhost/“ + ”user=&pass=“);
    3. 类型:用封装后的
    4. 驱动程序:
      DriverManage.registerDriver(new org.hsqldb.jdbc.JDBCDriver());
      Class.forName("org.hsqldb.jdbc.JDBCDriver”)
  28. 存储过程,不同的数据库有点小区别,比较像多条SQL放在一起
  29. 多个Bean对应一个Class时,在XML文件中要用primary key指定一个具体的
  30. Service Integration: managing multiple suppliers of services AND integrating them to provide a SINGLE business-facing IT organization
  31. 如何check DB连接:
    1. 在SSH上检查Log,定时执行一些命令运行server
    2. 看DB中的数据,有没有新的数据生成:有下游tracking表,对应也有上游
    3. 看GUI

业务理解

熟悉业务通常是先看代码,如果完全没有文档也缺乏日志,那么就需要老人带着讲解,不然复杂的逻辑很难看明白。
数据库的字段也是需要熟悉的,因为这些字段都是根据业务需要设定的。
剩下的就是要充分体会Business到Engineering的转化,无他,唯手熟尔。

Java学习

machine mode (solution space) <- mapping -> model of problem(problem space)

  1. Everything is an object
  2. A program is a bunch of objects telling each other what to do by sending messages
  3. Each object has its own memory made up of other objects
  4. Every object has a type
  5. All objects of a particular type can receive the same messages
  6. An object has an interface
  7. Is-a VS is-like-a relationships

JMS相关

  1. JMS provider: 实现了JMS接口并提供管理和控制功能
    JMS Client: 用 java 写的一些程序和组件,它们产生和使用消息
    Messages: JMS client 之间传递的消息的对象
    Administered objects: 由使用JMS clients的人生成的预先设置好的JMS对象。有两种这样的对象:destination和connection factories
  2. P2P,每个message只有一个使用者sender和receiver没有时间依赖。每个消息必须由一个使用者成功处理发布、订阅:每个message可以有多个使用者publisher和subscribers在时间上有依赖关系。一个订阅了某一topic的客户,只能使用在它生产订阅之后发布的message,并且subscriber必须一致保持活动状态。
  3. Messaging本身异步:
    1. 同步 sub/rec 可以通过调用rec方法实时地从destination提取message
    2. 异步:客户可以为某一个使用者注册一个listener,message listener和event listener
  4. ConnectionFactory: 连接工厂,用来创建连接
  5. Connection: JMS client到JMS provider连接
  6. Destination: 消息目的地
  7. Session: 一个发\收线程
  8. MessageProducer: 由Session对象创建的用来发送消息的对象
  9. JMS模型:
    1. 消息头、属性、消息体
    2. 消息头:消息识别、路由
    3. 属性:
      1. 应用自己的属性
      2. 消息头中原有一些可选的属性
      3. JMS Provider需要用到的属性
    4. 消息体
  10. 同步是client主动收消息
  11. 异步是消息到达时,主动通知客户端。用MessageListener
  12. P2P模型基于队列;PUB/SUB模型:如何向一个内容节点发布和订阅消息。

Java Web后台基本设计模式

名词解释

  1. JavaBeans: 《Thinking in Java》中指出:JavaBeans本质是一种命名规则,比如 (1) getXxx(), setXxx(),如果是Boolean那么还可以是 isXxx()。(2) Bean中的普通方法不必遵循以上命名规则,不过必须是public的。应用:MVC中的M;《Thinking in Java》:IDE构建工具能够使用反射机制来动态地向组件查询,以找出组件具有的属性和支持事件。组件要有“能够被IDE构建工具侦测其属性和事件”的能力。Java加入反射机制的原因之一就是为了支持JavaBean,Bean可以打包、导入。

  2. AOP:面向切面编程

    1
    2
    3
    4
    5
    <aop:config>
    <aop:advisor pointcut="execution(public* *ServiceImpl.*(..))"
    advice-ref="xxxAdvice"
    order="10" />
    </aop:config>

    可以指定在探测到满足pointcut条件时,同时要做出什么样的动作。

  3. PO(Persistent object): 就是在持久层中直接映射column,@SqlTable(name = “xxx”)

  4. DAO(Data Access Object): CRUD

  5. VO(Value Object): 通常就是前端显示的Model,比如xxxEntity

  6. DTO(Data transfer object): 负责数据传递作用,比如一张表有100个字段,我仅仅需要10个,用DTO而不是PO

  7. O/R Mapping: Object Relational Mapping

  8. POJO: Spring提出来的简单的java对象

  9. Daemon Thread: A daemon thread is a thread that does not prevent the JVM from exiting when the program finishes but the thread is still running. e.g. Garbage Collection

模式分析

  1. Facade模式:
    • 首先,Web后台的入口,一般作为Controller部分,通常都是绑定GET、POST方法实现的。可以用Spring之类的Framework亦或是Servlet直接实现。
    • 这里,即使是后台也有一个类似于“界面”的对象。可以用FACADE(外观)模式(DP 4.5)来实现,其意图即:为子系统中一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
    • 在AbstractFacade中负责生成静态的工厂,工厂提供DAO和Service的构建功能。
  2. Abstract Factory模式:
    • 意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
    • 适用性:
      • 一个系统要独立于它的产品的创建、组合和表示时
      • 一个系统要由多个产品系列中的一个来配置时
      • 当你要强调一系列相关的产品对象的设计以便进行联合使用时
      • 当你提供一个产品类库,而只想显示它们的接口而不是实现时
    • 实现:
      1. 将工厂作为Singleton,多个工厂用一个静态的HashMap保存。
      2. 每个Kit仅声明一个创建产品的Interface,真正创建产品的是由ConcreteProduct子类实现的。最常用的是为每一个产品定义一个工厂方法。
  3. Dynamic Proxy:
    • 工厂可能有多个,每一个工厂的都有很多getXxxService()等功能。我们如果想实现扩展性,比如指定怎样初始化,需不需要做缓存等等,就不能对抽象工厂Hard code。这个时候我们可以交给代理完成,而如果所有的工厂中的产品都满足一致性(初始化都一样),那么我们可以实现动态代理,这样的话当抽象工厂扩展的时候,我们就不必要去将每一个新加入的产品都做很多重复的操作(比如指定初始化方式)
    • 核心代码:
1
2
3
4
5
6
7
8
9
T factory = (T) Proxy.newProxyInstanc
(ClassUtil.getDefaultClassLoader(),
new Class[] { factoryInterface }, //Class<T> factoryInterface
new InvocationHandler());
/*
* FactoryInvocationHandler中要实现 InvocationHandler,最重要的就是设定
* getXxxService()的invoke模式。InvocationHandler实现invoke方法,主要是具体的代理过程
* 比如Factory Method的具体代理操作
*/

动态代理是一种设计模式,主要用来解决任务分派(对象加载)问题,Java自身支持这种机制,使用反射实现。AOP可以用动态代理实现。

Class加载顺序

  1. Bootstrap Class Loader: 根,C++实现。JVM启动时 $JAVA_HOME中JRE所有的class
  2. Extension Class Loader: 加载一些扩展功能
  3. System Class Loader: 启动参数中指定的jar包,也叫AppClassLoader
  4. User-Defined Class Loader: 用户继承java的Class Loader后自定义的Loader,在动态代理中可能会自定义Loader

Spring构建Workflow框架

关键字:Spring IoC\DI,消息队列,有限状态自动机

我们在基本的业务逻辑中会处理一些Workflow,用Spring和消息队列可以实现异步的Workflow。Spring框架的核心是IoC容器。

基本概念

  • 控制反转(IoC)一种思想,的目的是解耦合(很多设计模式都是这个目的)。基本思想是借助“第三方”实现具有依赖关系的对象之间的解耦。

也就是说假设有四个对象,A B C D,原本他们是自己控制相互之间的耦合。现在借助IoC,之前的glue code都不需要写了,而是全部将对象之间的控制权交给IoC。

会有什么变化?

  1. 没有IoC之前,如果A依赖B,那么A在运行在某一点的时候,必须主动创建对象B,或者调用已经创建的B。这时候B的控制权都在B自己手上而A没有,会导致耦合。
  2. 引入IoC之后,这种情况就改变了,当A运行到需要B的时候,因为少了之前的直接联系,IoC容器会主动创建一个对象B注入到对象A需要的地方。

对象A获得对象B的过程由主动行为变为了被动行为,控制权颠倒,因此成为Inverting of Control。

  • 依赖注入(DI)一种设计模式,是将实例变量传入到一个对象中去。
1
2
3
4
5
6
7
8
public class Language {

Haskell haskell;

public Language() {
haskell = new Haskell();
}
}

Language类中用的了Haskell对象,Language类对Haskell有一个依赖。

  1. 如果Haskell的生成方式有变化我们需要修改Haskell的代码。new Haskell(String name)
  2. 测试的时候也比较麻烦,比如mock

改成:

1
2
3
public Language(Haskell haskell){
this.haskell = haskell;
}

这样就可以做依赖注入了。

Spring中IoC框架使用依赖注入作为实现控制反转的方式,但是控制反转的实现方法不单一种,还有比如说:ServiceLocator。因此这两类东西不能够等同。

Workflow的实现

Workflow的本质是控制流,对应于计算机科学里面的有限状态自动机。其实Workflow本身的实现就可以借助Spring依赖注入,参考javaworld - use spring to create a simple workflow engine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<bean id  = '' class = ''>
<property name = "name" value = "xxx">
<property name = "activities">
<list>
<ref bean = "activity1"/>
<ref bean = "activity2"/>
<ref bean = "activity3"/>
<ref bean = "activity4"/>
</list>
</property>
<property name = "defaultErrorHandler">
<ref bean = "defaultErrorHandler"/>
</property>
<property name = "processContextClass">
<value></value>
</property>
</bean>
1
2
3
4
5
6
7
public interface Processor{
public boolean supports(Activity activity);
public void doActivities(); //这里处理workflow
public void doActivities(Object seedData);
public void setActivities(List activities); //这里会被依赖注入上面四个activity
public setDefaultErrorHandler(…);
}

消息队列通信

使用消息队列进行通信可以极大的扩充workflow的能力。同样通过Spring的IoC容器:

1
2
3
4
5
6
7
8
<list>

<bean class = "…">
<property name = "name" value = "…"/>
<property name = "queueName" value = "…" />
</bean>

</list>

也就是每一个activity都是通过queue去进行通信。可以设计为当前的activity执行完毕后才进入下一个,然后开启多条线程等待message的到来后进入下一个处理阶段。

消息队列通信实现

有一个入口启动startProcessing(),startProcessing的功能是初始化,和启动监听入口。初始化主要功能就是注册消息队列。

监听入口的启动,通常是一个无限循环,这个循环不断的轮询消息队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private volatile boolean stop;

//field for multi-threading
private boolean keepAlive = true;

private void listener() {
while(!stop){
Message message = null;
try{
message = processor.getMessage();
if(message != null){
processor.processMessage(message);
} else {
//sleep for a second
} catch(…){
} finally {
if(!keepAlive){
break;
}
}
}
}
}

多线程监听

这个跟IoC的关系就不是特别大了,主要是消息队列通信多线程的优化。

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
private void multiThreadProcessing() {

executor = Executors.newScheduledThreadPool(threadCount, new MessageProcessorThreadFactory());

for(int i = 0; i < threadCount; i++) {
executor.scheduleAtFixedRate(
new MessageProcessThread(),
initialDelay,
schedulePeriodInSeconds,
TimeUnit.SECONDS);
try{
Thread.sleep(100);
} catch(...)
}
}

private class MessageProcessThread implements Runnable {
@Override
public void run(){
listener();
}
}

private class MessageProcessorThreadFactory implements ThreadFactory {

private AtomicInteger currentThreadCount = new AtomicInteger(0);

@Override
public Thread newThread(Runnable command){
return new Thread(command, getName() + "-" + currentThreadCount.addAndGet(1));
}
}

其他更多的架构相关内容,参考这里

编译器相关

文档,环境

国内做编译器的相比于Web要匮乏太多,没有很多现成的内容。尤其是华为的编译器又非常的特殊,有一套自己的中间表达。源语言也不是通用的高级语言。

  1. 论文、官方接口文档、Mailing list。多看一手资料
  2. C++项目的环境一般都是本地编译并本地执行,部署方式不同于Web项目

技术细节

这里能说的要说多非常多,因为编译器的规模都巨大,也需要大量造轮子。但是呢要说少也少,因为很多技术太过于专了,说不说也罢。主要工作语言从Java转到了C++,从API工程师变成了从零开始徒手撸。

  1. 设计模式与Web项目会有很大不同,这里访问者模式、建造者模式更常用
  2. 因为属于Infrastructure领域,很多特性是从零自主实现的,没有现成的第三方类库。实现一个新的特性就是从原始论文翻译成代码的。项目已经开源了,可以去下载来看看
  3. 比Web的业务逻辑更纯粹,面临的问题更偏向纯技术纯理论,束手束脚的条条框框少
  4. 编译器领域的很多开发与学术界联系密切,一般一个成熟的小组的主力军都是博士、教授、资深技术专家组成
  5. 研究的内容更多,所以会有很多理论计算机的内容,这与Web开发有比较大的差异。相比于我上一份工作,这里更强调的是核心算法实现、解决痛点问题、顶层的设计等,做的东西更大
  6. Citi的技术分享更多的是员工自己分享经验,能在工程细节上得到不错的启发;在华为更多请行业专家和教授讲解学术性的内容,离工程细节远
  7. 对比Web开发,能感觉到在国内这个领域远不如Web开发成熟。相当的内容都是摸着石头过河,没有特别繁琐的框架规则,都是直击问题要害,也容易造成代码不讲究,到处乱飞的现象,不知道是工作领域的原因还是国内外公司的差异。感觉代码的正规化不如上一家公司,但是技术含量要高很多
  8. 前中后端、解释器、Runtime之间的技术差异很大。当前我是在前端工作,对于语法、类型分析和IR的接触较多,对于垃圾回收、优化和后端的接触较少
  9. 闭包、lambda、匿名内部类的概念会有运用;Java 8中含有Single Abstract Method的lambda语法糖
  10. Java 8之后,引入Stream API,list comprehension实现在stream抽象内
  11. 工作涉密,不方便在公共场所交流细节
  12. 会用到诸如编辑距离、最长公共子序列、最长递增子序列等动态规划算法
  13. 编译器高手 == 字符串拼接大师(哈哈,开个玩笑)

技术转型与差异

在Citi开发Web项目的时候,重在业务逻辑,技术上参与过NLP的实验项目,也实现过较为通用的模块,不过大致上都没有逃离CRUD和状态机系统的思路。总的来说,套用现成的库和接口比较多,因此也存在对很多现有架构和技术进行复制粘贴,可以对外分享的内容也多。举个例子就是:提到两阶段提交或者Paxos,做Java后端的都明白我在讲什么。

在华为做编译器则进行了一次技术转型。我对自己的评价是属于技术比较的全面型的,所以上能做应用,下能做系统。编译器的技术就非常专了,华为的编译器是有自己设计的IR的,自成体系。同时为了避免法务上的问题,也为了完全自主掌控,基本不太使用现有框架,包括前端。所以更多的在造轮子,可以对外分享的内容也少。举个例子就是:invoke-custom静态化,基本不做编译器的都不清楚这是在干什么。