最近,我着手维护一个项目,在原来代码的基础上去做开发、维护的工作。这个项目曾经承载着日活几十万,月流水一个多亿的业务,所以它里面一定会有很多的功能和优秀的思想。
我拿到源代码的时候给我的是一个zip包,它就像一个黑盒子,承载着你所有的期待和不确定性,没有任何人去告诉我这个项目里都有什么业务,它是什么样子的一个架构,它应该怎么启动,启动之后应该怎么访问,都有哪些接口,更没有任何项目文档,连数据库里的表结构都没有注释。
当我把它解压之后,我发现这是一个单体maven项目,看起来应该不错,至少不是一个老古董(还记得在lib目录下手动添加三方依赖jar包解决冲突的时代吗?)。
我需要快速去熟悉它,并在此基础上开发新的功能。
实际工作中,你们遇到过这种项目吗?你是如何进入并逐步熟悉它呢?以下分享我的一些方法。
1、项目概览
当我用idea打开它的时候,光是依赖我就下载了一个多小时,可能是我的网络不好,但至少可以确定的是,内网的maven私服仓库里没有这些版本的jar包。
我在下载依赖的过程中,扫了一眼pom.xml文件,竟然发现了struts2的依赖,是被注释掉的:
还记得这些struts2的依赖包都是干嘛的吗?
继续去查看关于spring框架的依赖,我想它一定是有的,否则离开了spring框架,我肯定玩不转它,看到了spring的依赖,是基于4.2.1.RELEASE版本的:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.2.1.RELEASE</spring.version>
</properties>
可以知道的是,这不是一个springboot项目,更不可能是springcloud项目了,这个项目曾经来自于SSM或者SSH的架构,这里的第一个S可不是SpringMVC,是Struts2,相信没有五年工作经验的人,大多数没有用过这个框架,除非你和我一样接手了一个古董项目。
终于下载完项目依赖,可以看到这个项目的全貌了。
它是一个标准的java web项目,有src/main/java、resources、webapp,webapp下面还有WEB-INF目录,以及web.xml。
大家都知道,一个标准的基于springboot前后端分离的项目是没有webapp和web.xml文件的,这些都被springboot通过注解给干掉了。
还记得web的三大组件吗?
Servlet、Listener、Filter,这些都属于web基础,一定要会的。
用springboot的同学也要知道springboot里是怎么玩这三大组件的。
到这里我开始感到惊讶了,一个月流水过亿、日活几十万、研发团队几十号人,项目竟会如此老旧?还是传统的单体应用,以war包方式部署?你们的技术追求呢?
在微服务满天飞的今天,大家是不是觉得不可思议?
是的,可是他就这样存在着,而无视单体应用带来的部署风险、代码冲突导致的效率低下。
2、先把它运行起来
显然它不是一个springboot项目,也没有看到启动类,所以我需要一个web容器来把这个war包部署运行起来。
如果他能够在本地tomcat下运行起来,说明他是完整的、健康的,反之我需要解决导致无法启动的问题,在一切还没有熟悉的时候。
显然,我没有把它运行起来,根据启动日志发现是无法连接memcached,可是我并不准备使用memcached,这就需要我去了解是否能够使用redis去替换掉它。从技术角度来看是可以的,比较memcached只有字符串类型,只要保证序列化和反序列化工具不变就行。
开始进入这个项目的代码世界了,果不其然,代码是15年的代码,有点老旧,和我的工作年限差不多。
发现memcached的使用被封装成了一个类,这还不错,至少不用到处修改,此时我只需要搞清楚一个问题,有没有一些初始化的数据放在memcached中,如果没有就可以放心的替换掉memcached了,如果有,那么就需要拿过来放到redis一份。
但是这个问题是没有人告诉我的,也没有任何文档,原来的阿里云memcached服务器也被释放了,所以忽略这个问题。
开始更改这个类,使用RedisTemplate来替换原来的MemcachedClient,因为这个类里的方法都是static修饰的,所以我想使用@Resource的方式注入RedisTemplate行不通,只能定义一个全局静态变量,来拿到RedisTemplate这个bean。
所以我不得不写一个工具类来获取自己想要的任何bean:
还记得Spring中的接口类ApplicationContextAware的作用吗?
XxxAware就是对xxx感知的意思,这里就是对ApplicationContext感知的意思,实现这个接口可以拿到ApplicationContext,然后通过它可以获取到很多我们想要的东西,比如容器中加载的bean,environment等。
当然这个类我还需要加载到容器中去,因为框架的原因,我不得不使用xml的bean定义方式:
<bean id="springUtil" class="com.xxx.SpringUtil" lazy-init="false"/>
知道这里为什么是lazy-init=false吗?
这个时候我就可以通过这个SpringUtil工具类来获取我们想要的redisTemplate实例bean对象了:
private static RedisTemplate redisTemplate = SpringUtil.get("redisTemplate",RedisTemplate.class);
第一个麻烦解决了,继续使用tomcat来运行它,这个时候发现了一个诡异的问题,日志没有任何问题,就是运行到一半卡住不动了,我以为是我刚刚修改的代码有问题,会不会是SpringUtil这个类加载的时候有死循环之类的问题,使用top命令查看,没有吃cpu很厉害的线程,java相关的线程cpu也很稳定,那就一定是阻塞在什么地方了。
重新启动一下试试,还是这个问题,卡住不动,但是我发现了一个问题,每次日志都在一个地方卡住就不再输出了,找到这行日志在代码中的位置,发现是阿里云的OSSClient连接问题,进而发现开发环境连接地址竟然是阿里云oss的内网地址。
换成外网地址继续启动,这次终于启动成功。
3、去找一个业务点作为突破口
当项目成功启动之后,我试图去找一个controller作为入口逐步跟进去,看都做了什么事情以及怎么做的,可是我并没有找到这个项目的任何Controller,而是发现了一个servlet的package,里面有大量的子package和servlet,我开始有预感,这是一个用servlet来开发API层的项目,而不是@Controller、@RestController类型的,当我打开其中一个HomeServlet,我看到代码注释说这是一个商城首页的Servlet,并且开发时间是15年3月:
看这段代码,是基于@WebServlet注解来实现的,注入的方式是用servlet的init()方法来注入相关的service,数据传参处理也没有SpringMVC的@RequestParam、@RequestBody这样的注解来封装,所以传参、参数的校验、数据的返回处理都需要自己写代码来处理。
继续跟进到service层,发现了一个巨大的方法,244行代码:
且返回也没用对象封装,而是一个万能的HashMap<String, Object>,哎,一群没有技术追求的同学。。。
dao层发现用的是ibatis,dao层就不用跟了,没有什么业务,无非就是一些sql,能支持事务就行。
项目的整体脉络已经摸的差不多了,接下来就是梳理这里的业务,将项目整体分成几大模块,不求甚解,整体把握即可。
4、后面要做的事情
基于这样的一个项目,大家有没有一些改造的想法?如果给你这个项目,在保证业务ok的情况下,你有哪些想做的事情?
秉持做事的态度,为了以后开发维护方便,我觉得我还是需要去做一些事情的,例如这些:
- 将SpringMVC集成进来,使得SpringMVC和当前的Servlet两种可以共存,新的用SpringMVC开发,旧的逐步改造过来,集成的时候考虑版本的兼容问题;
- 抽一个公共返回类,比如ResultResp<T>,并提供一些success(),success(T data),fail(),fail(int code, String msg)方法,用于接口统一返回;
- 统一的异常拦截处理,不吐服务端异常信息给前端;
- 重构一些业务模块,使得每个方法不超过80行,方法的复杂度不超过18,多加注释;
- 一些重复的代码功能采用注解的形式抽取出来;
- 将service层的sql语句下沉到dao层;
- 后期择机将一些相对独立的模块抽取出来形成特定的服务;
- 不再以war包的形式部署,整包部署风险太高,降低粒度到class文件,采用Git版本控制增量部署、指定tag回滚;
- 因为是单体应用,目前还不需要链路跟踪,但是类似于ELK的日志收集、监控工具还是需要的;
- 写一个alarm报警微服务,一旦产生error级别的日志,或者产生RuntimeException则实时报警到钉钉群,谁让我维护的是一个日活几十万、月流水过亿的项目呢?
暂且就这些吧,后续根据项目的进展情况再逐步迭代。
本文暂时没有评论,来添加一个吧(●'◡'●)