1 调试技巧
一般的IDE工具都有以下调试功能,本文以 IntelliJ IDEA 为例

1.1 计算表达式
以下是实际开发中很容易遇到的一种场景:调试下面的代码时,validate() 返回值为true, 正常的逻辑是进入function1,而我们想要查看的结果是function2的返回值

boolean validate = validate();
String s = null;
if(validate) {s = function1();
} else {s = function2();
}
logger.info(s);

部分不熟悉计算表达式的同事在遇到以下代码时, 可能会直接修改代码如下:

boolean validate = false;
String s = null;
if(validate) {s = function1();
} else {s = function2();
}
logger.info(s);

这种调试方式虽能满足需求, 但容易忘记还原代码,把调试代码一并提交,从而产生问题。如果这段代码是在源码中,无法直接修改,部分同事还会追本溯源,最终通过修改配置文件等方式, 使 validate() 返回false, 浪费了较多时间。

利用IDE工具的 计算表达式功能, 可直接查看function2的执行结果, 而无需修改代码:

计算表达式支持多行模式,可用于合成参数:

利用计算表达式,可直接测试Spring容器中任意单例bean的方法,无需进入相应的业务模块进行调试:

注:上面的示例中需保证appContext已实例化,可通过@DependsOn 指定优先实例化appContext

1.2 条件断点
开发时使用断点进行调试是开发中最基础的技能,通过对断点设置条件,最快的定位到目标代码,同样也是开发人员的必备技能。

条件断点最经典的应用是对循环中的代码进行调试,如:

List集合可以直接通过下标设置条件:

foreach循环:

对于无法取下标的集合,可转为数组后再取下标,如:

1.3 异常断点
异常断点可以认为对条件断点功能的一种扩展,虽然应用不如条件断点那么广泛,但在一些场景下可以更快速的定位到问题代码,常见的使用场景如下:

注:上面的场景也可使用条件断点来进行定位, 如下:

但在更复杂的场景中,比如同一行会有多个变量可能报空指针,或是报一些我们平时不是很常见的异常,

使用异常断点的优势就会比较明显。

1.4 远程调试配置
在生产环境遇到问题时,远程调试无疑是定位问题的最佳手段。 IntelliJ IDEA 配置远程调试的步骤如下:

1.点击Edit Configurations,添加remote配置

2.修改ip和端口为目标服务的ip和预计监听端口, 修改好之后把 jvm启动参数 添加到目标服务的启动脚本中,如:

3.重启服务,待服务监听配置的端口后,启动remote,即可开始远程调试:

Debug生效:

需要慎重使用的是 Build Project功能,一些文件改动后能够直接替换到虚拟机内存,但不会改动jar包中的class,重启项目后就会失效

1.5 调试"回退"【袁】
有时候在调试复杂的方法嵌套方法的逻辑代码时候,好不容易进到一个断点,一不小心手一抖或者按错了快捷键,断点就过去了,想回过头看看刚才的变量值,只能再跑一遍。

(注意:可能发生的副作用,已经对全局状态进行的更改(如静态变量,对域值的更改等)不会被撤销,只会重置局部变量。网络流量,文件操作,控制台输出等都不能倒回。)

1.6 动态修改变量【袁】
调试时,动态修改变量的值。在变量上右击,然后选择Set Value。

1.7 并发测试rest接口【袁】

Apache ab: Apache提供的一款小巧的压力测试工具,用来测试服务能力很方便。

使用教程:https://www.jianshu.com/p/e3793ae91a62

2 问题排查
2.1 jar包冲突处理
jar包冲突是maven项目最常见的问题之一,jar包冲突可能引起的问题有很多,最经典的是下面的异常:

java.lang.NoSuchMethodError
当出现NoSuchMethodError异常时,首先应该怀疑到jar包冲突上。确定是否有jar包冲突比较简单,直接查看目标类是否存在多个版本的jar包中即可。

当确定是jar包冲突引起的问题后,还有以下问题需要解决:

    1. 如何确定在报错环境下使用的jar包版本,从而进行排除?2. 确定需要排除的jar包后,如何定位jar包的引入路径?

对于第1个问题,可在调试环境下调用以下代码,查看出现错误的目标类从那个jar包中加载:

Target.class.getProtectionDomain().getCodeSource().getLocation().getPath()

如:

当定位到需排除的jar包后,接下来就是分析jar包的引入路径。

常规的做法是使用mvn指令打印依赖树,再通过文件编辑工具进行查找:

mvn dependency:tree>D:/tmp/tree.txt

本文推荐的做法是使用 Maven Helper插件,在IntelliJ IDEA中集成该插件后,可以直接在pom中搜索每个jar包的依赖路径,使用起来十分方便:

2.2 死锁检测
死锁只有在并发环境下才可能出现,在日常开发中出现较少,一般暴露问题的阶段都是在现场部署之后。

而在代码审核或者自检阶段,多关注以下两类情况,能够一定程度上预防死锁的发生:

    1.Spring容器中两个或多个bean之前相互依赖;2. 相互依赖的bean调用对方的加锁方法;

下面的一段代码是根据一个老系统中使用的实际代码简化而成,并通过多线程模拟并发环境,以达到满足死锁的条件。

@Component
public class DeadLock {

private static Logger logger = LoggerFactory.getLogger(DeadLock.class);private class UserService {private RoleService roleService;public synchronized void boundRole() throws InterruptedException {Thread.sleep(1000L);logger.info("boundRole");roleService.updateRole();}public synchronized void updateUser() {logger.info("updateUser");}public void setRoleService(RoleService roleService) {this.roleService = roleService;}
}private class RoleService {private UserService userService;public synchronized void boundUser() throws InterruptedException {Thread.sleep(1000L);logger.info("boundUser");userService.updateUser();}public synchronized void updateRole() {logger.info("updateRole");}public void setUserService(UserService userService) {this.userService = userService;}
}@PostConstruct
public void DeadLockTest() {UserService userService = new UserService();RoleService roleService = new RoleService();userService.setRoleService(roleService);roleService.setUserService(userService);new Thread(() -> {try {userService.boundRole();} catch (InterruptedException e) {logger.error("", e);}}).start();new Thread(() -> {try {roleService.boundUser();} catch (InterruptedException e) {logger.error("", e);}}).start();
}

}
项目启动后,执行结果如下: boundRole方法和 boundUser 方法中的语句被打印,但 updateRole和updateUser 中的语句却一直没有执行,在现场环境下,则是相应的请求一直阻塞,页面无法得到返回结果。

定位死锁比较简单,一般直接使用jstack -l pid 即可,如

执行结果如下:

考虑到现场环境不一定安装jdk, 对于系统自启动的服务而言,直接使用jstack也可能会有权限问题。在大多数情况下,定位死锁的方式会类似于之前的远程调试,这里介绍两款jdk自带的支持远程分析的图形工具:Jconsole 和 JVisualVM。之所以同时介绍这两种工具,是因为这两种不仅都可以定位死锁, 同时对于内存分析等功能可以起到互补作用, 更重要的是这两种图形工具的远程配置是通用的,相当于添加一遍配置,可同时使用这两种工具。

Jconsole/JVisualVM的远程配置虚拟机参数如下:

-Djava.rmi.server.hostname=10.16.81.35
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.port=22222
跟之前的远程调试一样,把这段配置作为虚拟机的启动参数即可,需要额外注意的有以下两点:

   1. -Djava.rmi.server.hostname=10.16.81.35 这个参数不是必加的,大多数参考资料中也没有提及,但遇到服务器有多个ip的情况下,不加这一句远程工具可能无法连接,笔者之前也在这个点上走过一些弯路,安全起见建议是把这句加上。2.在服务器防火墙启用的情况下,之前的远程调试只需要开放remote端口即可, 但Jconsole/JVisualVM 的远程连接不止需要开放指定的监听端口号,同时还需要开放JMXserver随机监听的端口号。

linux下可以通过命令lsof -i | grep 来查看当前java进程需要监听的随机端口号,如:

netstat -tupln | grep 8093
lsof -i | grep 6032

当然,如果能临时关闭防火墙的话,也可以不用考虑端口的问题。

把远程配置的虚拟机参数加入启动脚本后,重启服务:

选择远程进程,输入ip和连接端口,连接jconsole(同一台机器上没有权限问题时可直接使用本地进程连接):

连接好之后,在线程窗口可以检测死锁:

检测结果如下:

连接JVisualVM:

在线程页面查看是否检测到死锁,如有死锁可点击线程Dump:

远程分析的结果与jstack一致:

2.3 内存分析
很多同事都在开发阶段或者现场反馈的问题中遇到过jvm内存不足的问题。

内存不足的主要表现有:

  1. 系统卡顿、几乎所有的请求响应时间都很长、甚至超时。(这种情况一般是由于频繁的Full GC导致所有线程被挂起)2. 抛出相应的内存溢出异常,如 java.lang.OutOfMemoryError: Java heap space

而导致内存不足的原因一般有以下两种:

   1. 内存泄露,无用的对象无法回收,随着系统不断运行,累计占用的空间越来越多,导致内存不足2. 程序单次运行需要占有大量的内存空间,分配的空间不足

分配空间不足导致问题的情况相对较少,处理也比较方便,在硬件环境允许的前提下适当加大虚拟机内存即可。而内存泄露无法靠单纯的加大虚拟机内存来处理,必须找出内存泄露的源头,并加以处理,否则无论多大的内存,都会在系统不断运行的环境下耗尽。(在跟一些同事的沟通过程中,有一位同事处理这类问题的方式是编写脚本定时重启系统,从而解决内存泄露导致的问题,在无法定位到内存泄露源头的情况下,也算是一种不太妥当的处理方案)。

  不管是哪种情况导致的内存不足, 进行内存分析都是必不可少的步骤,在 “死锁检测” 这一小节中,已经介绍了 Jconsole 和 JVisualVM 这两种工具的远程配置方式 和 部分功能,对于内存不足的情况,我们同样使用这两种工具进行分析。以下是内存分析的实战:使用jpa批量插入数据时,插入数据量达到56W时, 抛出OutOfMemoryError异常:

关键代码如下:

//创建数据插入对象
EntityManager entityManager = JPAUtil.getEntityManager();
//开始时间
long startTime = Calendar.getInstance().getTimeInMillis();
//已插入条数
int totalSize = 0;
for (DataSource dataSource : effectiveDataSource) {
List dataList = new ArrayList<>();
for (int i = 1; i <= dataSource.getDataSize(); i++) {
dataList.add(dataSource.next());
if(i % config.getBatchSize() == 0) {
totalSize = batchSave(entityManager, dataList, startTime, totalSize, config, dataSource);
}
}
batchSave(entityManager, dataList, startTime, totalSize, config, dataSource);
logger.info(“数据插入完成:” + dataSource.getId());
}
closeEntityManager(entityManager);
重新启动服务,使用Jconsole监控服务内存变化情况,当插入一定的数据后,年老代的变化情况如下:

垃圾回收情况如下:

由Jconsole的监控情况可知,年老代收集器收集次数高达41次,但年老代的内存占用却一直递增,并且接近达到上限,至此已经可以推测出随着运行时间的增加,不断产生无法回收的对象,最终导致内存溢出。接下来我们通过JVisualVM来排查消耗最内存的对象,并尝试释放这部分对象占用的空间。

使用JVisualVM监控进程后,本地进程可以直接点击 堆Dump 查看实例, 远程调试点击 堆Dump 可在服务端生成 hprof 文件,将hprof文件复制到本地,再导入JVisualVM中即可。堆Dump情况如下所示:

int[] 类型的实例数 和 内存占比都居首位,可从这里开始排查,双击查看int[] 的实例,可随机选择实例查看引用,关注项数较大的类型,如:

上图中Object[] 类型的数组中含有EntityEntryContext$ManagedEntityImpl 类型的实例1048576个,毫无疑问已经是内存泄露的首要嫌疑对象。继续展开这个Object[]类型的引用:

可以定位到这个数组是被IdentityHashMap类型的实例所引用,而这个实例又被EntityEntryContext 对象的 nonEnhancedEntityXref 属性引用,如下:

private transient IdentityHashMap<Object,ManagedEntity> nonEnhancedEntityXref;

我们的目的是清空 nonEnhancedEntityXref 引用的IdentityHashMap,一般有以下两种选择:一是直接销毁EntityEntryContext 对象,二是调用EntityEntryContext 内部的方法,清空nonEnhancedEntityXref。查看EntityEntryContext 内部方法,发现clear方法可以直接做到这点:

public void clear() {
//…省略代码
if ( nonEnhancedEntityXref != null ) {
//目标方法
nonEnhancedEntityXref.clear();
}
//…省略代码
}
继续顺着EntityEntryContext 对象的引用跟踪到 StatefulPersistenceContext 类:

public class StatefulPersistenceContext implements PersistenceContext {
private EntityEntryContext entityEntryContext;
public void clear() {
//…省略代码
entityEntryContext.clear();
}
}
继续展开引用,StatefulPersistenceContext 被 SessionImpl 的实例所引用,查看源码发现SessionImpl的clear方法会调用StatefulPersistenceContext的clear方法:

public final class SessionImpl implements SessionImplementor...{private transient StatefulPersistenceContext persistenceContext;public void clear() {//..省略代码try {internalClear();}catch (RuntimeException e) {throw exceptionConverter.convert( e );}}private void internalClear() {persistenceContext.clear();//..省略代码}}

至此,嫌疑对象和处理方案均已找到,修改后的代码如下:

//创建数据插入对象
EntityManager entityManager = JPAUtil.getEntityManager();
//开始时间
long startTime = Calendar.getInstance().getTimeInMillis();
//已插入条数
int totalSize = 0;
int refreshSize = 0;
for (DataSource dataSource : effectiveDataSource) {List<Object> dataList = new ArrayList<>();for (int i = 1; i <= dataSource.getDataSize(); i++) {dataList.add(dataSource.next());if(i % config.getBatchSize() == 0) {refreshSize += dataList.size();//新增超过refreshSize数据刷新entityManager,使被占用的资源能及时回收if(refreshSize >= config.getRefreshSize()) {logger.info("刷新entityManager");entityManager.clear();refreshSize = 0;}totalSize = batchSave(entityManager, dataList, startTime, totalSize, config, dataSource);}}batchSave(entityManager, dataList, startTime, totalSize, config, dataSource);logger.info("数据插入完成:" + dataSource.getId());
}
closeEntityManager(entityManager);

refreshSize默认设置为20W,即每新增20W条数据调用entityManager(SessionImpl对象)的clear方法,从而解决内存溢出的问题。代码修改后,
插入数据超过100W条仍在正常执行。

打开Jconsole监控年老代内存变化,发现垃圾回收时能够清理掉大部分空间,如下:

3 敏捷开发
区别于企业视角的“用户需求为核心进行的迭代开发”,这章的标题名虽然也是敏捷开发,但定义仅适用于研发人员,即“尽量避免重复开发,使相同的工作内容模板化”。

3.1 模板代码
对开发代码进行模板化是减少重复性工作的一项重要内容。前有新体系下统一使用的脚手架,后有各种现存的插件或者小工具用于创建代码,如比较通用的 mybatis-generator

插件。由于不同项目的差异性,各小组之间也都有个性化的代码创建方式, 如:

      idea + groovy 组合:自定义groovy 文件,置于idea目录下,在idea工具中即可生成目标代码。java + vm模板引擎 组合: 自定义代码创建工具,无需依赖开发工具,java研发人员无需了解新语法,但开发成本高于前一种。

无论选择哪种创建代码的方式,只要能满足当前项目需求即可,研发人员可根据自身的偏好和使用习惯进行选择,无需强制使用同一种代码创建工具。但切忌的是无意义的重复开发,

少部分同事甚至连持久化对象都手动开发,但表字段较多时,需耗费大量时间写对应的实体类,且容易出错和不规范。

     除了上面介绍的适用文件级代码的生成方式,一些零散的固定代码、注释等 推荐使用idea自带的  Live Templates 功能。除了 已有的 fori 、for、psvm、sout 等已有的动态模板,

Live Templates 同样也支持自定义模板:



3.2 批量编辑
批量编辑功能也是开发工程经常遇到的场景,很多同事遇到需要变量编辑的功能不假思索的就一个个去编辑了,花费了较多时间。其实很多开发工具都支持批量编辑的功能,如

Notepad ++,这里推荐的是 sublime 工具。

下面是一段错误码的国际化配置,根据需求,需要改造成枚举类,如果逐个修改势必花费大量时间。

errorCode.0x17601000=Unknown error
errorCode.0x17601001=Invoke fail
errorCode.0x17601002=Illegal state error
errorCode.0x17601003=Illegal argument error
errorCode.0x17601004=Server is too busy
errorCode.0x17601005=Unsupported operation
errorCode.0x17601006=No permission
errorCode.0x17601007=User not logged in
errorCode.0x17601008=Get user information error
errorCode.0x17601009=Get mac address error
errorCode.0x17601010=Close Stream error
errorCode.0x17601011=Get local ip error
errorCode.0x17601012=Init secure lib error
errorCode.0x17601013=Generate key pair error
errorCode.0x17601014=Generate security token error
errorCode.0x17601015=Shake iac key error
errorCode.0x17601016=Get server path error
errorCode.0x17601017=Encrypt error
errorCode.0x17601018=Encrypt error
errorCode.0x17601019=Decrypt error
把这段配置复制到sublime工具中,使用alt+f3 选中相同内容,或者shift + 鼠标右键按住 拖动光标,效果如下:

每一行的0x前都有一个光标,修改一行的内容相当于对所有行进行操作,毫不费力就达到了需求,并且花费的时间不会随着数量增多而改变,改动后的结果如下:

3.3 数据模拟
在开发过程中,模拟数据也是一件比较费事费力的事情,以下是一个基于java开发的数据模拟小工具,解压后可通过使用手册查看各项配置,或者根据手册上的svn地址自行下载源码进行维护。

data-batch-production-1.0-SNAPSHOT-release.zip,见资源《dev-util》

3.4 代码生成(idea + groovy 组合)【袁】
数据库视图:

1、点击右侧的Database图标,要是没有该图标,请去自行百度

2、点击 + 号

3、选择 Data Source

4、选择 一种数据库类型(需要连哪种就选哪种)

配置数据库连接:

1、根据实际情况填写相关配置即可

2、点击Test Connection测试连接通过,就ok了

代码生成:

1、选中一张或者多张表,选择生成类型(Entity,Service,Controller。。。可自行扩展)

2、选择需要保存目录模块,点击生成即可

3、生成完毕

4 玩转代码(张桂荣)
4.1 减少缩进
CustomTranferDTO dto = getCustomTranferDTOById(CUSTOM_TRANSFER_DTO_ID);
if(!Objects.isNull(dto)){
String dtoName = dto.getName();
if(!Objects.isNull(dtoName )){
String[] results = dtoName.splite(’,’);
……………………
return results;
}
}
return null;
避免if的多次、深层嵌套,可把异常过程放在if语句块后,而不是把正常过程放在if语句块后。即将if条件为真并执行修改为if条件为假返回或抛异常。上述代码就可以修改为:

CustomTranferDTO dto = getCustomTranferDTOById(CUSTOM_TRANSFER_DTO_ID);
if(Objects.isNull(dto)){
return null;
}
String dtoName = dto.getName();
if(Objects.isNull(dtoName )){
return null;
}
String[] results = dtoName.splite(’,’);
……………………
return results;!
看上去就整齐多了。

4.2 巧用反射
某些特定场合,使用反射能够减少冗余代码。结合组件中的一个函数来说明,这个函数是用于根据不同的归属地,对车流量进行累加:

/*** 对正常识别的车牌进行流量累加** @param plateNo 车牌号* @param DO 车道车流量详情DO* @author: zhangguirong 2019-01-22 13:57*/
private void countFlowForAbnormalPlate(String plateNo, THighwayVfsLaneTypeLocDO DO) {char plateLocation = plateNo.charAt(0);switch (plateLocation) {case '京':DO.setLocationBeijing(DO.getLocationBeijing() + 1);break;case '津':DO.setLocationTianjin(DO.getLocationTianjin() + 1);break;case '冀':DO.setLocationHebei(DO.getLocationHebei() + 1);break;case '蒙':DO.setLocationNeimenggu(DO.getLocationNeimenggu() + 1);break;case '辽':DO.setLocationLiaoning(DO.getLocationLiaoning() + 1);break;case '吉':DO.setLocationJilin(DO.getLocationJilin() + 1);break;case '黑':DO.setLocationHeilongjiang(DO.getLocationHeilongjiang() + 1);break;case '沪':DO.setLocationShanghai(DO.getLocationShanghai() + 1);break;case '苏':DO.setLocationJiangsu(DO.getLocationJiangsu() + 1);break;case '浙':DO.setLocationZhejiang(DO.getLocationZhejiang() + 1);break;case '皖':DO.setLocationAnhui(DO.getLocationAnhui() + 1);break;case '闽':DO.setLocationFujian(DO.getLocationFujian() + 1);break;case '赣':DO.setLocationJiangxi(DO.getLocationJiangxi() + 1);break;case '鲁':DO.setLocationShandong(DO.getLocationShandong() + 1);break;case '豫':DO.setLocationHenan(DO.getLocationHenan() + 1);break;case '鄂':DO.setLocationHubei(DO.getLocationHubei() + 1);break;case '湘':DO.setLocationHunan(DO.getLocationHunan() + 1);break;case '粤':DO.setLocationGuangdong(DO.getLocationGuangdong() + 1);break;case '桂':DO.setLocationGuangxi(DO.getLocationGuangxi() + 1);break;case '琼':DO.setLocationHainan(DO.getLocationHainan() + 1);break;case '渝':DO.setLocationChongqing(DO.getLocationChongqing() + 1);break;case '川':DO.setLocationSichuan(DO.getLocationSichuan() + 1);break;case '贵':DO.setLocationGuizhou(DO.getLocationGuizhou() + 1);break;case '云':DO.setLocationYunnan(DO.getLocationYunnan() + 1);break;case '藏':DO.setLocationXizang(DO.getLocationXizang() + 1);break;case '陕':DO.setLocationShanxiShan(DO.getLocationShanxiShan() + 1);break;case '晋':DO.setLocationShanxiJin(DO.getLocationShanxiJin() + 1);break;case '甘':DO.setLocationGansu(DO.getLocationGansu() + 1);break;case '青':DO.setLocationQinghai(DO.getLocationQinghai() + 1);break;case '宁':DO.setLocationNingxia(DO.getLocationNingxia() + 1);break;case '新':DO.setLocationXinjiang(DO.getLocationXinjiang() + 1);break;case '港':DO.setLocationXianggang(DO.getLocationXianggang() + 1);break;case '澳':DO.setLocationAomen(DO.getLocationAomen() + 1);break;case '台':DO.setLocationTaiwan(DO.getLocationTaiwan() + 1);break;case '使':DO.setLocationShi(DO.getLocationShi() + 1);break;case 'W':DO.setLocationWj(DO.getLocationWj() + 1);break;default:logger.info("Plate information is incorrect! Give up statistics!");}
}

根据车牌第一个字符,分别对车流量统计对象THighwayVfsLaneTypeLocDO的不同字段进行累加操作。逻辑非常简单,但是写出的代码非常长。可以考虑用反射来解决:

首先,定义一个二维数组:归属地简称-成员变量/字段名,并将其转化为Map(数组遍历效率比Map.get()低):

private static final String[][] locationMapArrays = new String[][] {{"京", "locationBeijing"},{"津", "locationTianjin"},{"冀", "locationHebei"},{"蒙","locationNeimenggu" },{"辽","locationLiaoning"},{"吉","locationJilin"},{"黑","locationHeilongjiang"},{"沪","locationShanghai"},{"苏","locationJiangsu"},{"浙","locationZhejiang"},{"皖","locationAnhui"},{"闽","locationFujian"},{"赣","locationJiangxi"},{"鲁","locationShandong"},{"豫","locationHenan"},{"鄂","locationHubei"},{"湘","locationHunan"},{"粤","locationGuangdong"},{"桂","locationGuangxi"},{"琼","locationHainan"},{"渝","locationChongqing"},{"川","locationSichuan"},{"贵","locationGuizhou"},{"云","locationYunnan"},{"藏","locationXizang"},{"陕","locationShanxiShan"},{"晋","locationShanxiJin"},{"甘","locationGansu"},{"青","locationQinghai"},{"宁","locationNingxia"},{"新","locationXinjiang"},{"港","locationXianggang"},{"澳","locationAomen"},{"台","locationTaiwan"},{"使","locationShi"},{"W","locationWj"}
};private static final Map<Object, Object> locationMap = Collections.unmodifiableMap(ArrayUtils.toMap(locationMapArrays));

然后,利用反射的方式,修改之前的函数:

/*** 对正常识别的车牌进行流量累加** @param plateNo 车牌号* @param DO 车道车流量详情DO* @author: zhangguirong 2019-01-22 13:57*/
private void countFlowForAbnormalPlate(String plateNo, THighwayVfsLaneTypeLocDO DO) {String filed = (String) locationMap.get(plateNo.substring(0, 1));if (StringUtils.isBlank(filed)){return;}try {// 根据名字获取类的字段final Field declaredField = THighwayVfsLaneTypeLocDO.class.getDeclaredField(filed);// 字段是私有的话,需要设置该属性,这样才能操作declaredField.setAccessible(true);// 获取对象该字段的值final Integer o = (Integer) declaredField.get(DO);// 设置对象该字段的值declaredField.set(DO, o + 1);} catch (NoSuchFieldException | IllegalAccessException e) {logger.warn("Plate information is incorrect. Give up statistics. -- {}", plateNo);}
}

这种对对象的成员变量进行赋值操作,特别是成员变量比较多的情况下,使用反射还是比较有利的。

4.3 Lombok注解【袁】
使用@Data 注解,可以减少大量的模板代码,让代码更加简洁。但是。。。FindBugs会扫描出漏洞,此问题待统一解决。

Java开发必备技巧相关推荐

  1. eclipse java开发插件_10大Java开发必备的Eclipse插件

    原标题:10大Java开发必备的Eclipse插件 今天小编来给大家介绍10个Java开发人员必备的Eclipse 插件,它们有各自的优势,你可以从中选择适合你的那款. 1. EGit EGit是 J ...

  2. java开发必备基础

    Java总复习 java知识总复习 第一章:java概述: 1:Java的历史: ​ Java诞生于SUN(Stanford University Network),09年SUN被Oracle(甲骨文 ...

  3. 高级JAVA开发必备技能:java8 新日期时间API((一)JSR-310:ZoneId 时区和偏移量)(JAVA 小虚竹)

    技术活,该赏 点赞再看,养成习惯 大家好,我是小虚竹.之前有粉丝私聊我,问能不能把JAVA8 新的日期时间API(JSR-310)知识点梳理出来.答案是肯定的,谁让我宠粉呢.由于内容偏多(超十万字了) ...

  4. 高级JAVA开发必备技能:时区的规则发生变化时,如何同步JDK的时区规则(实战方案,建议收藏)

    技术活,该赏 点赞,收藏再看,养成习惯 场景 服务器安装JDK时,默认会有支持JDK版本对应的时区规则.但是时区规则可能会变化的,那如何保持时区规则是最新的呢? TZUpdater 工具介绍 ​ 提供 ...

  5. Java 开发必备:神器

    导读:Java开发人员经常要和各种各样的工具打交道,除了常用的IDE工具以外,其实还有很多工具是我们在日常开发及学习过程中要经常使用到的.本文作者Hollis偏爱使用在线工具,因为这样比较方便.本文就 ...

  6. Java开发自学技巧!链表反转的两种实现方法,太香了

    导语 回首向来萧瑟处,归去,也无风雨也无晴. 我一哥们也是做Java开发,暂且先叫他峰哥吧,到今年已经入行6年了,每天还是写一些业务代码,至今还是个单身狗,愁的头发真的都快谢顶了. 虽然峰哥每天做着C ...

  7. Java开发自学技巧!中原银行java开发

    前言 都知道MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作.MyBatis 可以通过 ...

  8. 手机开发必备技巧:javascript及CSS功能代码分享

    1. viewport: 也就是可视区域.对于桌面浏览器,我们都很清楚viewport是什么,就是出去了所有工具栏.状态栏.滚动条等等之后用于看网页的区域, 这是真正有效的区域.由于移动设备屏幕宽度不 ...

  9. Java开发小技巧(五):HttpClient工具类

    前言 大多数Java应用程序都会通过HTTP协议来调用接口访问各种网络资源,JDK也提供了相应的HTTP工具包,但是使用起来不够方便灵活,所以我们可以利用Apache的HttpClient来封装一个具 ...

最新文章

  1. 如何利用离散Hopfield神经网络进行数字识别(2)
  2. python系统-python 系统相关操作
  3. 【Android FFMPEG 开发】FFMPEG 解码 AVPacket 数据到 AVFrame ( AVPacket-解码器 | 初始化 AVFrame | 解码为 AVFrame 数据 )
  4. ActiveMQ安装使用
  5. Python Django 正向查询与逆向查询
  6. JMX和Spring –第3部分
  7. LeetCode 962. 最大宽度坡(单调栈)
  8. 关于 SET QUOTED_IDENTIFIER ON 和 SET ANSI_NULLS ON
  9. Android 应用开发---ViewPager---4自主实现滑动指示条
  10. Mysql支持中文全文检索的插件mysqlcft-应用中的问题
  11. XMPP核心协议客户端
  12. Ubuntu无法进入操作系统的恢复和备份操作
  13. (转)DPDK内存管理 04 ---- rte_malloc内存管理
  14. PHP将汉字转化为拼音
  15. 【简单的小技巧】如何从网页上下载内嵌的PDF文件?
  16. 线性代数05 齐次/非齐次线性方程组的具体解集
  17. PV操作经典例题——和尚打水
  18. 计算机音乐夜空,星空音乐在线点播系统
  19. 以卖单车为例形象理解23种设计模式
  20. 在国外期刊发表文章时一定用到的,SCI论文写法攻略

热门文章

  1. 解密一个话费慢充的灰产项目
  2. 如何设置lazada促销活动--Flash Sale
  3. 王慧文的光年之外离OpenAI还有多远?
  4. 【软件定义汽车】【中间件】iceoryx冰羚
  5. linux系统hostapd强制使用40MHz频宽
  6. win10蓝屏后的解决办法
  7. linux下罗技摄像头采集图片,网络摄像头罗技和Linux
  8. 干货全拿走-用Excel制作小市值轮动价值投资选股器
  9. Unable to identify any set of controllers that can actuate the specified joints:
  10. 双屏幕显示,两个显示器分辨率不一样处理