关于ReentrantLock实现的一点思考

ReentrantLock内部由Sync类实现,Sync类是ReentrantLock的一个内部类,该类继承自AbstractQueuedSynchronizer,该类继承自AbstractOwnableSynchronizer,AbstractOwnableSynchronizer中持有一个exclusiveOwnerThread,表示当前持有锁的线程。

Sync类是一个抽象类,它有两个实现类:FairSync和NonFairSync,分别代表公平锁和非公平锁。

非公平锁和公平锁实现类似,前者相对简单,在AbstractQueuedSynchronizer中有一个整型state变量,在Java中通过Unsafe类提供的CAS来操作state变量,用于表示锁是否获取(0表示当前没有线程获取锁,反之非0)

在非公平锁中,首先尝试cas(state),如果成功,则表示当前线程获取锁,此时将exclusiveOwnerThread设置为当前线程,否则会判断state是否为0,如果是,则再次cas(state),否则判断当前锁的持有者是否是当前线程,如果是,则state加一操作,否则获取锁失败。然后将当前线程加入等待队列,然后以自旋的方式等待锁。

公平锁和非公平锁实现过程差别不大,差别在于,如果是非公平锁的话,当某个线程调用lock方法时,它不会抢占式的去设置同步状态(即state的值),而不是直接调用 acquire 将线程放入同步队列中等待获取锁,当抢占失败,才会放入队列中等待锁。除此之外,在调用tryAcquire时,公平锁每次回判断是否有线程比当前线程等待时间更长,如果有,则让队列中第一个线程获取锁。

参考:
https://www.cnblogs.com/xrq730/p/4979021.html

http://www.cnblogs.com/nullllun/p/9004309.html

Leer más

Java内存优化之POI Excel(三)

在上一篇工作记录中提到了Excel内存优化策略,其实很失败。很多东西好像自己以为想当然,其实不是。以为我将Excel分成5000行一个文件,既可以优化内存,又可以方便阅读,直到有一天我看到结算组是怎么使用呢?打开每个Excel,复制粘贴到一个文件里,当看到他们打开一个Zip,里面有11个Excel,我都不忍直视了……其实做这种优化比较耗时间,效果也不是很容易看到,可能做了好几天这套方案,结果并没有收益,一转眼又要周五了,还好这次有效果。

让过程更直观

上篇博客提到了JConsole,可以让我们更加直观的看到任务执行过程中的各种信息,比如内存(Eden、Old、Survivor),线程数,gc过程和次数等。如下

大概截取了一个任务执行的所有过程,可以看出系统初始化内存基本上稳定在200-300m,然后猛然增加了200+m,这就是从数据库撸出来的近50000条数据,

Leer más

Java内存优化之POI Excel(二)

问题背景及排查:Java内存优化之POI Excel(一)
其实除了内存调整,还做了策略上的调整,既然创建一整个数十万行的Excel比较耗内存,那我就分片创建呗,所谓分片就是将一个大的Excel分成几个小的Excel,然后打成Zip。

分片创建Excel

Talk is cheap, show me the code!

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
42
43
44
45
public String export() throws Exception {
List<String> excelFileSplices = Lists.newArrayList();
if (pageable) {
// 可以分成几个Excel
int pageCount = this.dataList.size() / PER_SPLICE_SIZE + 1;
int index = 0;
while (index < pageCount) {
List<T> dataSplices = this.dataList.subList(index * PER_SPLICE_SIZE,
Math.min(this.dataList.size(), (index + 1) * PER_SPLICE_SIZE));
String fileName = exportOne(dataSplices);
excelFileSplices.add(fileName);
index++;
}
} else {
String fileName = exportOne(this.dataList, ignoredProperties);
excelFileSplices.add(fileName);
}

log.info(excelFileSplices);

if (excelFileSplices.size() == 1) {
return excelFileSplices.get(0);
} else {
String zipFilePath = TMP_FILE + "/" + UUID.randomUUID().toString() + ".zip";
ZipArchiveOutputStream zipArchiveOutputStream = null;
InputStream is = null;
try {
zipArchiveOutputStream = new ZipArchiveOutputStream(new File(zipFilePath));
zipArchiveOutputStream.setUseZip64(Zip64Mode.AsNeeded);
for (String srcFilePath : excelFileSplices) {
File sourceFile = new File(srcFilePath);
ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(sourceFile, sourceFile.getName());
zipArchiveOutputStream.putArchiveEntry(zipArchiveEntry);
is = new BufferedInputStream(new FileInputStream(sourceFile));
IOUtils.copy(is, zipArchiveOutputStream);
sourceFile.delete();
}
zipArchiveOutputStream.closeArchiveEntry();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(zipArchiveOutputStream);
}
return zipFilePath;
}
}

其实策略实现比较简单,就是将数据全部撸到内存(其实这些数据也占不了多少内存,只是创建Excel的时候消耗了大量内存),然后在内存分页,分别创建Excel,最后将Excel达成Zip包上传到七牛。

不妥协的DaYe

1
2
3
WO: 以后下载清单会是压缩包了,会麻烦一点点...
DaYe:我们要这个就是为了简单吗 ,你还麻烦一点 ,你怎么好意思说啊...
WO: ......

不说了,继续改吧。

可见的效果

其实这种性能优化,最讨厌感觉上优化了,明明做了啊,但是到底优化了多少,是一点点,还是很多,还是适得其反呢?怎么能做到心中没点B数。

神奇的JConsole

Sun真是良心,除了JDK,还提供了很多很好的附加工具,还说只是试验品,软件附加的,但是其实很好用,而且很稳定。

什么是JConsole呢?在终端(Mac或者Windows)上输个jconsole就能看到,如图

image.png

图中com.zuihuibao.excel.ExcelOperator 34890是我启动的程序,可以选中它,然后连接。

image.png

点击不安全的连接,就可以看到各种你想看到的数据,比如当前内存大小内存峰值等,我打算用它来测量我优化的实际效果。

image.png

PS,可能第一次你一直连不上,请在你的启动程序上加个参数
-Djava.rmi.server.hostname=localhost image.png

Leer más

Java内存优化之POI Excel(一)

结算系统上线后,每到月初月末,都有点胆战心惊,最怕听到“某某某,我这个下载又不行”、“我这个都下载了20分钟了,怎么还不行啊!”…… 我能怎么办哇,停下来把锅捡起来呗。

捡锅记之检锅

捡锅了,然后呢?当然是查一查问题出在哪了。ssh上服务器,先说说服务器配置吧。这台服务器是在某离职大神的建议下购买的,配置还不错。单核SSD硬盘,其他配置如下:

1
2
3
4
5
6
7
[web@monitor ~]$ free -m
total used free shared buff/cache available
Mem: 3790 2467 173 0 1150 1021
Swap: 0 0 0

[web@monitor ~]$ cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)

查看服务状态

记得当初车险系统刚升级SpringBoot的时候,经常发现系统挂掉,Java进程也被kill掉,还纠结了好久,排查了很久发现是被由于Linux的OOM Killer机制杀掉的。

Linux OOM_killer是Linux自我保护的方式,当内存不足时不至于出现太严重问题,有点壮士断腕的意味。在kernel 2.6,内存不足将唤醒oom_killer,挑出/proc//oom_score最大者并将之kill掉,可以把/proc//oom_score_adj值改小(最小-17)来临时避免此种情况。

所以出现服务停止后,我第一反应就是查看Java进程还在不在。

1
2
3
[web@monitor ~]$ jcmd
19335 settlement.jar --spring.profiles.active=prod
30142 sun.tools.jcmd.JCmd

居然还在,唯一的借口都不给我!

检查Java进程栈

我第一个想到的是死锁,导致进程假死。看看Java栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[web@monitor ~]$ jstack -l 19335
2017-11-03 21:44:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):

"http-nio-8087-exec-10" #1017 daemon prio=5 os_prio=0 tid=0x00007f2cd0016000 nid=0x5c31 waiting on condition [0x00007f2c9d21a000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000920abcf8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

Locked ownable synchronizers:
- None
.....

太长了,此处截取部分。我记得jstack 有时候可以帮我们检测出死锁的,可以直接

1
jstack -l <pid> | grep deadlock

如果有结果,则表示有死锁,当然也不排除其他情况(ps: 也遇到过数据库链接失效,导致某任务执行了3个多小时后失败的。)jstack的其他情况可以参考下:http://blog.csdn.net/wanglha/article/details/51133819

检查Java堆

Java对象大部分情况实在堆上创建的(有时候会在栈上或者有块叫做TLAB的空间),那么来检查下堆的情况吧。

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
42
43
44
45
[web@monitor ~]$ jmap -heap 19335
Attaching to process ID 19335, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.144-b01

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1879048192 (1792.0MB)
NewSize = 625999872 (597.0MB)
MaxNewSize = 625999872 (597.0MB)
OldSize = 1253048320 (1195.0MB)
NewRatio = 2
SurvivorRatio = 5
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 569901056 (543.5MB)
used = 105560864 (543.5MB)
free = 464340192 (0MB)
100% used
From Space:
capacity = 27262976 (26.0MB)
used = 6621032 (6.314308166503906MB)
free = 20641944 (19.685691833496094MB)
24.28580064039964% used
To Space:
capacity = 26214400 (25.0MB)
used = 0 (0.0MB)
free = 26214400 (25.0MB)
0.0% used
PS Old Generation
capacity = 1253048320 (1195.0MB)
used = 55013328 (1195.0MB)
free = 1198034992 (0MB)
100% used

纳尼!!!Eden区和老年代都耗尽了(这些数据是后来假造的,当时的确看到的是两个100%)。

看看gc情况

1
2
3
[web@monitor ~]$ jstat -gcutil 19335
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.79 99.89 98.99 97.96 96.07 901 3.654 287 1.160 4.814

介绍下这几个参数,S0、S1表示两个Survivor区(复制GC算法使用),E表示Eden区,大部分对象在此创建,O老年代,多次GC后对象存活区域,M表示元数据区(JAVA8后取代永久代,存放一些class元数据,我们不用太关心它,基本上有JVM帮我们管理),YGC和FGC分别表示Eden区GC次数和Full GC的次数,正常情况下Full GC次数比较少,跑个几天Full GC几次差不多了,如果很多次就不正常了(当然排除哪个二货手动调用System.gc()),Full GC比较多,你可能会看到如下错误:java.lang.OutOfMemoryError: GC overhead limit exceeded,这个表示JVM试图通过GC回收内存,但是什么也没有回收到。默认情况下,JVM花费了98%的时间在GC上,但是GC过之后只有不到2%的堆内存被回收。

甩锅记之内存的锅

我去看下这群货到底要下载什么数据,一看吓一跳,两个月全国所有的保单数据。这是什么概念呢?两个月差不多20多万数据吧,如果不做关联单,那差不多是30多万到40万左右,30万行的Excel什么概念?自己想想吧。

我当然不能说你们别下这么多数据啊,反正我一个打工小弟说了也没人听啊!好吧,升级内存总可以吧,这锅你背。

1
java -Xms1536m -Xmx1536m -jar service.jar

初始化内存和最大内存设置一样是为了减少GC次数,为啥要减少GC呢?简单说就是GC比较霸道,它工作的时候STOP THE WORLD,所有的工作都得停下,等待GC完成。话说什么时候开始GC呢?你可以使用如下命令观察下:

1
2
 jstat -gcutil <pid> [intervalTime] [invokeCount]
其中intervalTime表示多久执行一次(单位毫秒),invokeCount表示总共会执行多少次。如:jstat -gcutil 19335 1000

大概当Eden区打到100%时会发生一次Young GC,Full GC类似。

堆内存的各个区

改完内存继续观察(后面还会升内存,毕竟这锅给内存背比较简单🤣),发现其实Eden、Survivor、Old区使用情况有很大差异,有的区很快就满了,有的还不到才20几。不行啊,得改,平均点!(这里说的是使用情况,不是都一样大)。

各个区大小比例

可以通过jmap命令查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[jarvan4dev@Macbook] ~ $ jmap -heap 34890
Attaching to process ID 34890, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.74-b02

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 715653120 (682.5MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)

默认情况下(JDK8,不同版本可能不一样),NewRatio=2,表示新生代和老年代比例为1:2,SurvivorRatio=8,表示一个Survivor区和新生代比例为1:8,两个Survivor区(S0和S1),即每个Survivor区占堆内存的1/10,调节这两个参数可以调节各区大小比例,来达到最优的使用情况。

1
java -Xms1792m -XmX1792m -XX:NewRatio=2 -XX:SurvivorRatio=5 -jar service.jar

Leer más

最佳实践之Java基础

List转String[]

在开发中可能会遇到Javabean链表转String数组,举个栗子
有一个List获取所有的username,以数组方式

1
2
3
4
5
6
7
8
class User {
private String username
}

List<User> users = getUserList();
List<String> usernameList = users.stream().map(User::getUsername)
.collect(Collectors.toList());
String[] usernames = org.apache.commons.lang3.ArrayUtils.toStringArray(usernameList.toArray());

Leer más

Vue + SpringBoot 小问题记录

下午事情不是特别多的时候,闲下来看看spring security,配合着iview-admin搞了个小系统,过段时间释放出源码来。当前在登录这块遇到点小问题,记录一下。

先介绍下这个系统“架构”,前端Vue,后端springboot,前后端分离,登录的时候将后端生成的token放入header中,key为Authorization,然后每次请求的时候带上以供服务器验证。

零、跨域问题

跨域是什么就不介绍了,前后端分离肯定要考虑跨域问题,跨域也有多种解决方案,比较常用的两种:一种是在服务端允许部分或者全部Origin,另一种是在前端配置,前端配置又分成两种方案,注意这里之所以说前端也有两种方案是因为我前端打包工具用的是webpack,webpack里面有个组件proxyTable,关于proxyTable就不细说了,请自行谷百,毕竟我是个后端汪。注意proxyTable是在本地调试的时候使用的哦!前端的另一种配置是在Nginx,这种一般在生产中使用。

1. Nginx跨域配置

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
42
43
44
45
46
location /api/ {
set $allow_headers 'Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
set $allow_method 'GET, POST, PUT, DELETE, OPTIONS';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $allow_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}

if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}

if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
if ($request_method = 'PUT') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
if ($request_method = 'DELETE') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
port_in_redirect on;
proxy_pass http://127.0.0.1:8080/your_context_path/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

2. SpringBoot对跨域的支持

作为后端汪,为了图方便我肯定选择在后端配置支持跨域,熟悉也简单。

只需要声明一个bean即可

1
2
3
4
5
6
7
8
9
10
11
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedOrigins("*").allowedMethods("*");
}
};
}

可以查看allowedXXX()的实现,Java Doc上有很明确的说明,”*”表示支持所有。

3. Spring security对跨域的支持

为什么还要配置security对跨域的支持呢?因为我的登录是是交给Spring security完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
.cors().and()
.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "您还没有登录");
}).accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "您没有访问权限");
}).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 允许所有用户访问
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/api/ping", "/api/captcha").permitAll()
// 对除了上述规则外的请求进行验证,兜底的,不能放在最前面
.anyRequest().authenticated()
.and().addFilterAfter(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}

注意,主要是 cors()这个方法起作用。另外,我想对antMatchers(HttpMethod.OPTIONS).permitAll()进行下说明,前端通过POST方式访问后端的REST接口时,发现两条请求记录,一条请求的Request Method为Options,另一条请求的Reuest Method为Post。OPTIONS方法有几个作用,请参看:https://www.cnblogs.com/virtual/p/3720750.html,此处配置是为了所有的OPTIONS方法直接通过authentication。

一、axios无法获取服务端返回的自定义header

为什么会有这个问题呢?也就是说我为什么要去读取reponse的header呢?前面我已经做过说明,是为了登录请求后拿到header中的token。可是打印了所有的header就是没有key为Authorization的header。

原因

在使用CORS方式跨域时,浏览器只会返回以下默认头部header:

1
2
3
4
5
Content-Language
Content-Type
Expires
Last-Modified
Pragma

如果你想在客户端app中获取自定义的header信息,需要在服务器端header中添加Access-Control-Expose-Headers

解决方案

解决方法就是在上面SpringBoot对跨域支持中声明的Bean中添加一条配置,如下:

1
2
3
4
5
6
7
8
9
10
11
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedHeaders("*")
.exposedHeaders("Authorization")
.allowedOrigins("*").allowedMethods("*");
}
};
}

注意,exposedHeaders()不能使用”*“

参考:

  1. http://www.uxuew.cn/vuejs/7135.html
  2. https://www.jianshu.com/p/95b2caf7e0da

Leer más

git 删除远程分支

https://blog.zengrong.net/post/1746.html

删除远程分支一直报错,提示:

error: unable to delete ‘master4online’: remote ref does not exist
error: failed to push some refs to ‘http://code.zuihuibao.cn/zhb/car_ins.git

You may need to prune your local “cache” of remote branches first. Try running:

git fetch -p origin

before deleting.

Leer más

Java规范笔记---读《阿里巴巴Java开发手册》

以下规范摘自《阿里巴巴Java开发手册》,基本摘取了自己平时需要注意或者容易出错的部分,以提现自己。

命名风格

  1. 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类 命名以它要测试的类的名称开始,以 Test 结尾。
  2. POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
  3. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

    例:应用工具类包名为com.alibaba.open.util、类名为MessageUtils(此规则参考 spring 的框架结构)

  4. 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。

    例:BeanFactory,OrderBuilder

  5. 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码简洁,加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
  6. JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默 认实现。
  7. 接口实现类命名规则:
    1. 一般使用在接口名称后面加Impl表示实现类
    2. 如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)。例:AbstractTranslator 实现 Translatable。
  8. 枚举类名建议带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。

    枚举其实就是特殊的常量类,且构造方法被默认强制是私有。

  9. Service/DAO层方法命名规约
    1. 获取单个对象的方法用get做前缀。
    2. 获取多个对象的方法用list做前缀。
    3. 获取统计值的方法用count做前缀。
    4. 插入的方法用save做前缀。
    5. 删除的方法用remove做前缀。
    6. 修改的方法用update做前缀。
  10. 领域模型命名规约
    1. 数据对象:xxxDO,xxx即为数据表名。
    2. 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
    3. 展示对象:xxxVO,xxx一般为网页名称。

常亮定义

  1. 常量的复用层次
    1. 跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。
    2. 应用内共享常量:放置在一方库的modules中的constant目录下。
    3. 子工程内部共享常量:即在当前子工程的constant目录下。
    4. 包内共享常量:即在当前包下单独的constant目录下。
    5. 类内共享常量:直接在类内部private static final定义。

代码格式

  1. 如果是大括号内为空,则简洁地写成{}即可,不需要换行,右大括号后还有else等代码则不换行;表示终止的右大括号后必须换行
  2. if/for/while/switch/do 等保留字与括号之间都必须加空格。
  3. 任何二目、三目运算符的左右两边都需要加一个空格。
  4. 缩进采用 4 个空格,禁止使用 tab 字符。
  5. 【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义
    之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。

OOP规约

  1. 【强制】所有的覆写方法,必须加@Override 注解。
  2. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

    说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行 判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑, 推荐使用 equals 方法进行判断。

  3. 基本数据类型与包装数据类型

    1. 【强制】所有的POJO类属性必须使用包装数据类型。
    2. 【强制】RPC方法的返回值和参数必须使用包装数据类型。
    3. 【推荐】所有的局部变量使用基本数据类型。
  4. 【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
  5. 【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
  6. 【强制】POJO 类必须写 toString 方法。

    使用 IDE 的中工具:source> generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。 说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排 查问题。

  7. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。

  8. 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。
  9. 【推荐】setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在
    getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。
  10. 【强制】关于 hashCode 和 equals 的处理,遵循如下规则:
    1. 只要重写equals,就必须重写hashCode。
    2. 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的 对象必须重写这两个方法。
    3. 如果自定义对象做为Map的键,那么必须重写hashCode和equals。
      说明: String重写了hashCodeequals方法,所以我们可以使用String对象作为key来使用。
  11. 【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ; 说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。
  12. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。

    说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配 内存空间,并返回新数组地址;如果数组元素大于实际所需,下标为[ list.size() ]的数组 元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素 个数一致。

    • 正例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    		List<String> list = new ArrayList<String>(2); 
    list.add("guan"); list.add("bao"); String[] array = new String[list.size()];
    array = list.toArray(array);
    ```
    * 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它 类型数组将出现 ClassCastException 错误。 13. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方 法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
    > 说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
    14. remove 元素请使用`Iterator`方式,如果并发操作,需要对 Iterator 对象加锁。
    15. 【推荐】集合初始化时,指定集合初始值大小。 > 说明:HashMap使用HashMap(int initialCapacity) 初始化,
    > 1. 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16。
    > 2. 反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容 量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
    16. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。如果是 JDK8,使用 Map.foreach 方法。
    17. 【【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。 > 正例:注意线程安全,使用 DateUtils。亦推荐如下处理:

Leer más

Java获取泛型的真实类型

https://github.com/FasterXML/java-classmate

http://www.cnblogs.com/JamKong/p/4540142.html

Leer más

RabbitMQ从入门到精通—publish confirm机制

前面我们说到防止消息丢失的ACK机制,这只是针对消费者和RabbitMQ Server的,但是生产者呢?生产者如何确认消息发送成功?其实最近我们负责和客户端通信的PHP端经常就会问我:“消息你们到底收没收到啊?”,因为他自己也不确定到底有没有发送成功,这就需要今天所说的publish confirm机制。

Leer más