EricZeng

  • 首页

  • 分类

  • 标签

  • 归档

  • 站点地图

  • 关于

  • 搜索

kettle插件开发pom配置

发表于 2019-10-21 分类于 kettle

背景介绍

看过网上很多介绍kettle插件开发的博文,在真正开发之前存在很多繁琐的工作,例如:将kettle工具中的lib包安装到本地maven仓库。通常会执行如下操作:

  1. 打开kettle/data-integration/lib目录,执行如下命令
1
2
3
4
5
6
mvn install:install-file -Dfile=./kettle-core-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=kettle-core -Dversion=6.1.0.1-196 -Dpackaging=jar
mvn install:install-file -Dfile=./kettle-dbdialog-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=kettle-dbdialog -Dversion=6.1.0.1-196 -Dpackaging=jar
mvn install:install-file -Dfile=./kettle-engine-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=kettle-engine -Dversion=6.1.0.1-196 -Dpackaging=jar
mvn install:install-file -Dfile=./kettle-ui-swt-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=kettle-ui-swt -Dversion=6.1.0.1-196 -Dpackaging=jar
mvn install:install-file -Dfile=./pentaho-metadata-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=pentaho-metadata -Dversion=6.1.0.1-196 -Dpackaging=jar
mvn install:install-file -Dfile=./metastore-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=metastore -Dversion=6.1.0.1-196 -Dpackaging=jar
  1. 打开kettle/data-integration/libswt/win64目录,执行如下命令
1
mvn install:install-file -Dfile=./swt-6.1.0.1-196.jar -DgroupId=org.pentaho.di -DartifactId=swt -Dversion=6.1.0.1-196 -Dpackaging=jar

执行这一堆命令,我们可能稍微能忍受一下(毕竟都是搬运工嘛,复制粘贴一下就好了)。然而,往往我们自己使用的又不是6.1.0.1-196这个版本,或者kettle升级了,或者本地仓库重置了,亦或接手这个项目的后来者(比如我),我们这个脚本还要手动替换一下。

为了实现插件开发流程的简化,结合已有的maven插件对pom文件进行了简单的修改,毕竟maven的天职就是对依赖包的统一管理嘛!!!

pom配置

在打开IDE之前,需要配置一下kettle的系统环境变量KETTLE_HOME,这个环境变量会在pom中使用。举个例子,在windows系统中,KETTLE_HOME可以配置为D:\Program Files\kettle71,该目录为kettle的安装目录,其文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.
└── kettle71
   └── data-integration
   ├── classes
      │      └── ...
   ├── lib
      │      └── *.jar
   ├── libswt
│ ├── linux
│ │ ├── x86
      │ │   │   └── swt.jar
      │   │   └── x86_64
      │   │    └── swt.jar
│ ├── osx64
      │   │   └── swt.jar
│ ├── win32
      │   │   └── swt.jar
│ └── win64
      │      └── swt.jar
....

打开IDE或直接打开maven管理的kettle插件工程,修改pom.xml文件:

  1. properties标签配置
1
2
3
4
5
<properties>
<kettle.version>7.1.0.0-12</kettle.version>
<kettle.home.lib>${env.KETTLE_HOME}/data-integration/lib</kettle.home.lib>
<kettle.home.libswt>${env.KETTLE_HOME}/data-integration/libswt/win64</kettle.home.libswt>
</properties>
  1. plugin标签配置
    添加maven-install-plugin插件:
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<id>kettle-core</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>kettle-core</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\kettle-core-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>kettle-dbdialog</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>kettle-dbdialog</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\kettle-dbdialog-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>kettle-engine</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>kettle-engine</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\kettle-engine-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>kettle-ui-swt</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>kettle-ui-swt</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\kettle-ui-swt-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>pentaho-metadata</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>pentaho-metadata</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\pentaho-metadata-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>metastore</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>metastore</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.lib}\metastore-${kettle.version}.jar</file>
</configuration>
</execution>
<execution>
<id>swt</id>
<goals>
<goal>install-file</goal>
</goals>
<phase>validate</phase>
<configuration>
<groupId>org.pentaho.di</groupId>
<artifactId>swt</artifactId>
<version>${kettle.version}</version>
<packaging>jar</packaging>
<file>${kettle.home.libswt}\swt.jar</file>
</configuration>
</execution>
</executions>
</plugin>

现在,只需要执行mvn validate命令即可将kettle依赖的jar包安装到本地的maven仓库。若更换kettle版本,只需更换properties标签中kettle.version的值即可。当然配置中还是存在依赖系统环境的缺陷,即${kettle.home.libswt},在windows64中使用win64,若换linux需要将win64改为liunx/x86_64,目前还没找到更好的解决办法,如果有谁知道欢迎告知!

参考资料

  1. kettle通用插件[kettlePlugins]使用说明
  2. Apache install:install-file
  3. Maven安装jar文件到本地仓库
  4. Maven中的几个重要概念(二):lifecycle, phase and goal

JVM参数优化

发表于 2019-09-28 分类于 java , jvm

背景介绍

最近,将应用手动部署到新的CentOS环境上,JRE/Tomcat都是新安装的,所以还没对JVM进行优化。习惯了本地开发和持续集成系统上现成的环境,往往会忽略JVM参数的配置。所以在启动没多久后就OOM了。。。

JVM优化

优化前

应用启动后GC状态如下图所示,从老年代可以看到应用已经执行了9次Full GC,而且消耗了14.418s,可以说这种状态应用是持续不了多久的。

https://eericzeng.github.io

图1 优化前初始运行

JVM为了获取足够的可用空间还会执行Full GC,此时垃圾收集器不是并行执行Full GC,所以Stop The World(STW)到来了。再看看第二张图的变化,老年代达到1.273G,已经完全占满,没有额外的空间可供分配的,就会出现OOM。如果是一个Web应用,我们就会发现前端各种无响应、各种超时。

https://eericzeng.github.io

图2 优化前运行一段时间后

执行优化

JVM添加哪些参数会优化应用呢?不妨再从第二张图看看,我们的应用缺什么。

  1. 老年代都不够分配,当然需要增大堆空间了。

    所以需要添加-Xmx设置最大堆空间,以8G服务器为例,最大可以设置7G+,最好不要设置满,从调优角度来考虑,可以逐步网上加,暂且设置4G。

  2. 再看看新生代,Eden区与Survivor区(S0/S1)比例十分不合理。

    图2中Eden: S0: S1 = 1:1:1,对于采用标记复制的新生代推荐的比例是8:1:1,所以需要修改SurvivorRatio参数为8。

  3. 老年代堆空间增长很快。

    老年代堆空间增长很快,所以我们需要延长新生对象到老年代的代数(MaxTenuringThreshold)。

  4. 垃圾收集器

    由于本应用与用户的交互性很强,所以老年代的垃圾收集器采用并发执行的CMS垃圾收集器,同时,需要选择合适的新生代垃圾收集器ParNewGC

综合以上四点设置的JVM优化参数如下:

1
2
3
4
5
6
7
-Xms4g
-Xmx4g
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4

这个时候的GC状态见图3所示:

https://eericzeng.github.io

图3 初步调优GC状态

这个时候JVM状态比没调优之前要好的多,但是还是刚刚运行就发生了几次Full GC。再根据图3GC状态进行分析:

  1. 新生代太小了。

    即使有MaxTenuringThreshold参数加持,但是多少对象都熬不过两代就因为S0/S1太小了被复制到了老年代,需要适当增大新生代空间(NewSize和MaxNewSize)。

  2. 堆空间调整

    增大新生代必然会挤占老年代,而且,对于刚刚运行没多久的应用已经发生了4次Full GC也说明堆空间还是不够用,因此还需要调整-Xmx参数。

此时,JVM优化参数如下:

1
2
3
4
5
6
7
8
9
-Xms6g
-Xmx6g
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4
-XX:NewSize=1g
-XX:MaxNewSize=1g

这个时候的GC状态见图4所示:

https://eericzeng.github.io

图4 二次优化后GC状态图

从图中不难发现Minor GC次数从97减少到了31次,明显少了很多,而且比较好使的MinorGC基本上都发生在程序启动阶段。再加上GC日志和其他辅助参数,用压力工具Loadrunner压测一下,JVM参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-Xms6g
-Xmx6g
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=15
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4
-XX:NewSize=1g
-XX:MaxNewSize=1g

-server
-Xss512k
-XX:+DisableExplicitGC
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintHeapAtGC
-Xloggc:logs/gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=logs/HeapDumpOnOutOfMemoryError

https://eericzeng.github.io

图5 测试一段时间后GC状态

新生代Minor GC 499次,排除启动开始比较耗时的31次,之后的每次Minor GC耗时为:(42.896-14.809)/(499-31)≈60ms,目前单次Full GC是最耗时的,达到210.861ms,对于用户交互来说还是可以接受的。当然这不一定是最优的结果,可以持续优化下去,达到最理想的状态——没有Full GC、少量Minor GC。

总结

之前看过很多JVM的知识,但是一直没有实际操作过,这个也算本人第一次优化JVM参数吧,如有不当之处欢迎指正。

参考资料

  1. jvisualvm远程连接
  2. jvm参数-verbose:gc和-XX:+PrintGC的区别

jvisualvm远程连接Tomcat

发表于 2019-09-20 更新于 2019-09-23 分类于 java , jvm

jvisualvm远程连接有两种方式:jmx和jstatd。

JMX

需要在服务器上修改Tomcat的启动参数,打开$TOMCAT_HOME/bin/catalina.sh,在文件中添加如下参数:

1
2
3
4
5
6
7
CATALINA_OPTS="$CATALINA_OPTS
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=8777
-Dcom.sun.management.jmxremote.rmi.port=8777
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=192.168.1.110"

其中,192.168.1.110是部署Tomcat的服务器地址。然后我们在本地启动jvisualvm,右击导航栏的“远程”->”添加远程主机”,输入远程主机IP——以“192.168.1.110”为例,右击远程主机“192.168.1.110”->“添加JMX连接”,输入端口号8777,勾选不要求SSL连接,这里没有使用安全凭证,因此也不需要勾选安全凭证,点击“确定”进行连接。

注意:使用CATALINA_OPTS,而不要使用JAVA_OPTS,使用JAVA_OPTS在关闭Tomcat时会出现如下错误导致Tomcat无法关闭:

1
Error: JMX connector server communication error: service:jmx:rmi://localhost.localdomain:8777

然而,如果JMX没有缺点也就不需要使用第二种方法了。JMX无法使用VisualGC插件,这个真的无法忍受,只能用下面这种方法进行连接。

jstatd

jstatd需要JDK1.8,低版本的可能没有这个工具。运行jstatd命令需要开放权限,创建安全策略文件如下:

1
2
3
cd $TOMCAT_HOME/
touch jstatd.all.policy
vim jstatd.all.policy

添加如下内容:

grant codebase "file:${java.home}/../lib/tools.jar" {
    permission java.security.AllPermission;
};

运行jstatd工具:

1
jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.110 &

以上默认端口是1099,若需要修改可以添加-p参数进行制定。

好了,可以本地打开jvisualvm远程连接一下192.168.1.110,VisualGC可以正常使用,神奇的是CPU监控显示“不受此JVM支持”(手动愣住(°ー°〃)),这就是为什么会有两种方法同时存在?!!!

多台服务器时钟同步

发表于 2019-09-17 分类于 CentOS

背景介绍

当所有应用在都集中在同一台服务器上,我们可能感受不到时钟同步的重要性。然而,一旦进行分布式部署时,始终未同步就会带来许多莫名其妙的问题,比如认证服务器时钟滞后应用服务器,进行OAuth 2认证时即使拿到认证码也是无效的;认证服务器时钟大幅超前又会出现session过期的问题;还有时钟不同步代理的业务逻辑问题等。

NTP

NTP全称Network Time Protoco,网络时间协议。

安装

1
yum install -y ntp

配置

1
vim /etc/ntp.conf

配置文件ntp.conf中包含了默认的配置,我们只需根据自己的环境做适当的修改,如启用同步限制:

1
restrict default nomodify notrap nopeer noquery

服务端

对于服务端需要设置server同步标准时间,若服务位于内网无法访问外部网络,只能使用本地时间,添加如下配置:

1
server 127.127.1.0 iburst local clock

注意: 若未添加本地时间且不可访问外部时间,客户端会报no server suitable for synchronization found错误。
启动ntp服务:

1
service ntpd start

客户端

发送如下命令进行同步:

1
ntpdate [ntp server ip]

成功响应会出现如下响应:

1
16 Sep 21:37:00 ntpdate[28240]: adjust time server [ntp server ip] offset 0.009964 sec

同步失败响应:

1
16 Sep 21:36:47 ntpdate[28239]: no server suitable for synchronization found

同步失败排查:

  • 服务端ntpd服务是否启动
  • 服务端防火墙是否拦截123端口
  • 服务启动后客户端需要等待一段时间后再进行同步
  • 检查服务端配置文件ntp.conf

参考资料

  1. 网络时间协议

md5和sha1文件完整性校验

发表于 2019-09-12 分类于 CentOS

简单而强大的命令:md5sum/sha1sum。

背景介绍

下载多个大文件,中间网络出现过多次断点续传、下载工具报错等问题,自己也不知道文件到底是否是完整的,依次比较是否下载完所有文件略显费力。官方提供了md5/sha1文件完整性校验码,如何使用这两个文件进行校验是本文的目的。

md5sum校验

md5sum是Linux系统自带的命令,windows系统安装了bash shell也可以使用该命令。将校验文件md5sums.txt复制到下载的文件同级目录下,只需执行如下命令:

1
md5sum -c md5sums.txt

执行结果示例如下:

1
2
3
4
file1.zip: OK
file2.zip: OK
file3.zip: OK
file4.zip: FAILED

说明:-c参数为–check的缩写,意思是从文件中读取md5校验码并校验文件中指定文件。OK代表校验通过,FAILED代表文件不完整。

生成md5校验码

当我们共享给其他人文件资源时,也可以自己生成md5校验码供其他用户校验。生成md5校验码命令:

1
md5sum *.zip > md5sums.txt

该命令会给当前目录下的每一个zip文件生成一个对应的md5校验码,文件内容示例如下:

1
2
3
4
d934615427115004a17771afc9d294e7 file1.zip
74cf07ce15cdf3f0639d69093661c26a file2.zip
d3dc5b39344a8f861741398e8ecb96fd file3.zip
ff7922f5c3b02c2cf99203912660e735 file3.zip

sha1sum命令与md5sum类似,因此不再赘述。

MySQL后台执行SQL导入

发表于 2019-09-12 分类于 MySQL

背景介绍

需要向MySQL数据库中导入大量SQL,而且多个SQL文件到不小,最大的有5.47GB。无论使用Navicat还是命令行导入SQL脚本,时间的等待是不可接受的。因此需要写一个导入SQL的脚本。

脚本介绍

创建一个.sh脚本文件import.sh,并输入如下内容:

1
2
#!/bin/bash
mysql -u root -p[password] --database=db <test.sql

说明:-u参数指定使用的数据库用户;-p参数是输入数据密码,-p和password之间不能有空格,否则会被识别为数据库;–database参数指定数据库为db;test.sql是指与脚本同级目录下的SQL文件,如果不是请使用绝对路径。远程数据库可使用-h参数指定,如:

1
2
/#!/bin/bash
mysql -h [ip] -u root -p[password] --database=db <test.sql

给脚本添加可执行权限,控制台执行如下命令:

1
chmod u+x import.sh

用nohup命令执行脚本:

1
nohup ./import.sh &

说明:一定要加“&”,才能脚本放到后台运行。

nohup执行后系统会打印出命令执行的PID,类似如下打印信息:

1
[1] 22215

根据PID使用ps命令可以查看命令执行请求:

1
ps -aux | grep 22215

打印信息如下:

1
2
root     22215  0.0  0.0 113120  1184 pts/1    S    21:25   0:00 /bin/bash ./import.sh
root 21895 0.0 0.0 112648 964 pts/1 S+ 21:27 0:00 grep --color=auto 22215

单元测试IllegalStateException问题

发表于 2019-09-07 更新于 2019-09-08 分类于 java

该文是解决IllegalState Failed to load ApplicationContext异常中的一种。

背景介绍

  • spring-web-4.3.6.RELEASE
  • Junit v4.12
  • h2 v1.4.192
  • Run As单元测试类,全部测试用例可以通过
  • mvn test部分测试类下面所有的测试用例都报错

mvn test报错信息如下:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
testMethod(io.github.eericzeng.StudentTest) Time elapsed: 0 sec  <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.command.ddl.CreateTable.update(CreateTable.java:115)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:184)
at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:158)
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:471)
at org.springframework.jdbc.datasource.init.ResourceDatabasePopulator.populate(ResourceDatabasePopulator.java:238)
at org.springframework.jdbc.datasource.init.CompositeDatabasePopulator.populate(CompositeDatabasePopulator.java:87)
at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:48)
at org.springframework.jdbc.datasource.init.DataSourceInitializer.execute(DataSourceInitializer.java:108)
at org.springframework.jdbc.datasource.init.DataSourceInitializer.afterPropertiesSet(DataSourceInitializer.java:93)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:134)
at org.springframework.test.context.web.AbstractGenericWebContextLoader.loadContext(AbstractGenericWebContextLoader.java:61)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:108)
at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:251)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray2(ReflectionUtils.java:208)
at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:158)
at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:86)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:95)

Results :

Tests in error:
? IllegalState Failed to load ApplicationContext

异常排查

由异常信息getJdbcSQLException/ApplicationContext推断出配置信息有问题,而且是关于数据库的异常,getJdbcSQLException是h2抛出的,说明h2在初始化就存在问题。配置单元测试log4j日志,log4j日志关键信息如下:

1
Caused by: org.h2.jdbc.JdbcSQLException: Table "student" already exists; SQL statement:

执行SQL创建数据表失败,说明建表语句被执行了多次。检查SQL脚本发现,脚本中只有创建数据表的语句,并没有DROP语句,所以每次执行测试用例时都会失败。

解决办法

根据本次异常排查情况,在CREATE TABLE前添加如下SQL语句:

1
DROP TABLE IF EXISTS 'student';

CentOS7服务开机自动启动

发表于 2019-09-07 分类于 CentOS

专治健忘症

以Apache HTTP服务为例:

添加开机启动

开机启动http服务

1
systemctl enable httpd

添加手动安装的服务,如nginx,可参考该文。

删除开机启动

删除开机启动http服务

1
systemctl disable httpd

查看所有开机启动的服务

1
systemctl list-unit-files | grep enabled

BeanUtils拷贝Map、JSONObject中的枚举、POJO属性

发表于 2019-08-26 分类于 java

版本说明

  • commons-beanutils:1.9.3
  • fastjson:1.2.58

场景

使用BeanUtils.copyProperties()拷贝Map/JSONObject时,无法正常拷贝属性类型为枚举或POJO。此时,需要使用ConvertUtils注册一个自定义的转换类。

实验

实验准备:

  1. Student类(拷贝对象),Student中有三个属性name/sex/grade分别为String/enum/POJO类型(包含属性比较方法propertiesEquals());
  2. Sex类,枚举类型,定义了FEMAL/MALE;
  3. Grade类,简单对象,包含三个属性。

定义如下:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* 学生类,属性中有一个枚举类型、一个POJO。
*
* @author zengguang
*
*/
public class Student {

/**
* 姓名
*/
private String name;

/**
* 性别,enum
*/
private Sex sex;

/**
* 成绩,POJO
*/
private Grade grade;

public Student() {
}

public Student(String name, Sex sex, Grade grade) {
this.name = name;
this.sex = sex;
this.grade = grade;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Sex getSex() {
return sex;
}

public void setSex(Sex sex) {
this.sex = sex;
}

public Grade getGrade() {
return grade;
}

public void setGrade(Grade grade) {
this.grade = grade;
}

}
/**
* 成绩。
*
* @author zengguang
*
*/
public class Grade {
private Integer mathematics;
private Integer chinese;
private Integer english;

public Grade() {
}

public Grade(Integer mathematics, Integer chinese, Integer english) {
super();
this.mathematics = mathematics;
this.chinese = chinese;
this.english = english;
}

public Integer getMathematics() {
return mathematics;
}

public void setMathematics(Integer mathematics) {
this.mathematics = mathematics;
}

public Integer getChinese() {
return chinese;
}

public void setChinese(Integer chinese) {
this.chinese = chinese;
}

public Integer getEnglish() {
return english;
}

public void setEnglish(Integer english) {
this.english = english;
}

}

/**
* 枚举类,性别。
*
* @author zengguang
*
*/
public enum Sex {
MALE, FEMAL;
}

实验对比

  • Java对象之间的拷贝
  • BeanMap拷贝到Java对象
  • JSONObject对象拷贝到Java对象
  • HashMap对象拷贝到Java对象

测试代码

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
47
48
49
50
51
52
53
54
55
56
Grade g = new Grade(100, 99, 98);
Student origBean = new Student("小明", Sex.MALE, g);
Student destBean = new Student();
// JavaBean → JavaBean
try {
BeanUtils.copyProperties(destBean, origBean);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(destBean, origBean, "JavaBean → JavaBean");
// BeanMap → JavaBean BeanMap/BeanUtils同为beanutils包中的类
BeanMap destBeanMap = new BeanMap(origBean);
try {
BeanUtils.copyProperties(destBean, destBeanMap);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(destBeanMap, origBean, "BeanMap → JavaBean");
// Map → JavaBean
String json = JSONObject.toJSONString(origBean);
JSONObject obj = JSONObject.parseObject(json);
ConvertUtils.register(new Converter() {
public <T> T convert(Class<T> clazz, Object value) {
// 应对json中枚举类型被转为字符串的问题
if (value instanceof String) {
return clazz.cast(Sex.valueOf((String) value));
}
return clazz.cast(value);
}
}, Sex.class);
ConvertUtils.register(new Converter() {
public <T> T convert(Class<T> clazz, Object value) {
Grade g = new Grade();
try {
BeanUtils.copyProperties(g, value);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return clazz.cast(g);
}
}, Grade.class);
try {
BeanUtils.copyProperties(destBean, obj);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
// 为方便比较,orig与dest反过来计算
validate(obj, destBean, "JSONOjbect → JavaBean");
Map<?, ?> m = JSONObject.toJavaObject(obj, Map.class);
Map<?, ?> map = new HashMap<>(m);
try {
BeanUtils.copyProperties(destBean, map);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(map, destBean, "HashMap → JavaBean");

完整代码

github: https://github.com/eEricZeng/demo/tree/beanutils

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
package io.github.eericzeng;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;

import com.alibaba.fastjson.JSONObject;

/**
* 使用{@link BeanUtils#copyProperties}拷贝Map/Json中的Enum和POJO。
*
* @author zengguang 2019年8月24日
*
*/
public class BeanUtilsTest {

public static void main(String[] args) {
BeanUtilsTest beanUtilsTest = new BeanUtilsTest();
beanUtilsTest.copyProperties();
}

/**
* 校验属性值是否相等。
*
* @param dest
* 拷贝目标对象
* @param orig
* 拷贝源对象
* @param label
* 标签
*/
private void validate(Object dest, Student orig, String label) {
if (orig.propertiesEquals(dest)) {
System.out.println(label + ": properties copy success !");
} else {
System.out.println(label + ": properties copy fail !");
}
}

/**
* 正确的拷贝方法。
*/
private void copyProperties() {
Grade g = new Grade(100, 99, 98);
Student origBean = new Student("小明", Sex.MALE, g);
Student destBean = new Student();
// JavaBean → JavaBean
try {
BeanUtils.copyProperties(destBean, origBean);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(destBean, origBean, "JavaBean → JavaBean");
// BeanMap → JavaBean BeanMap/BeanUtils同为beanutils包中的类
BeanMap destBeanMap = new BeanMap(origBean);
try {
BeanUtils.copyProperties(destBean, destBeanMap);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(destBeanMap, origBean, "BeanMap → JavaBean");
// Map → JavaBean
String json = JSONObject.toJSONString(origBean);
JSONObject obj = JSONObject.parseObject(json);
ConvertUtils.register(new Converter() {
public <T> T convert(Class<T> clazz, Object value) {
// 应对json中枚举类型被转为字符串的问题
if (value instanceof String) {
return clazz.cast(Sex.valueOf((String) value));
}
return clazz.cast(value);
}
}, Sex.class);
ConvertUtils.register(new Converter() {
public <T> T convert(Class<T> clazz, Object value) {
Grade g = new Grade();
try {
BeanUtils.copyProperties(g, value);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return clazz.cast(g);
}
}, Grade.class);
try {
BeanUtils.copyProperties(destBean, obj);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
// 为方便比较,orig与dest反过来计算
validate(obj, destBean, "JSONOjbect → JavaBean");
Map<?, ?> m = JSONObject.toJavaObject(obj, Map.class);
Map<?, ?> map = new HashMap<>(m);
try {
BeanUtils.copyProperties(destBean, map);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
validate(map, destBean, "HashMap → JavaBean");
}

/**
* 学生类,属性中有一个枚举类型、一个POJO。
*
* @author zengguang
*
*/
public class Student {

/**
* 姓名
*/
private String name;

/**
* 性别,enum
*/
private Sex sex;

/**
* 成绩,POJO
*/
private Grade grade;

public Student() {
}

public Student(String name, Sex sex, Grade grade) {
this.name = name;
this.sex = sex;
this.grade = grade;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Sex getSex() {
return sex;
}

public void setSex(Sex sex) {
this.sex = sex;
}

public Grade getGrade() {
return grade;
}

public void setGrade(Grade grade) {
this.grade = grade;
}

public boolean propertiesEquals(Object o) {
if (this == o)
return true;
if (null == o)
return false;
if (o instanceof Student) {
Student s = (Student) o;
if (!this.name.equals(s.name))
return false;
if (!this.sex.equals(s.sex))
return false;
if (!this.grade.propertiesEquals(s.grade))
return false;
return true;
} else if (o instanceof Map) {
Map<?, ?> m = (Map<?, ?>) o;
if (!this.name.equals(m.get("name")))
return false;
Object obj = m.get("sex");
Sex s = m.get("sex") instanceof String ? Sex.valueOf((String) obj) : (Sex) obj;
if (!this.sex.equals(s))
return false;
if (!this.grade.propertiesEquals(m.get("grade")))
return false;
return true;
}
return false;
}

@Override
public String toString() {
return "Student [name=" + name + ", sex=" + sex + ", grade=" + grade + "]";
}

}

/**
* 成绩。
*
* @author zengguang
*
*/
public class Grade {
private Integer mathematics;
private Integer chinese;
private Integer english;

public Grade() {
}

public Grade(Integer mathematics, Integer chinese, Integer english) {
super();
this.mathematics = mathematics;
this.chinese = chinese;
this.english = english;
}

public Integer getMathematics() {
return mathematics;
}

public void setMathematics(Integer mathematics) {
this.mathematics = mathematics;
}

public Integer getChinese() {
return chinese;
}

public void setChinese(Integer chinese) {
this.chinese = chinese;
}

public Integer getEnglish() {
return english;
}

public void setEnglish(Integer english) {
this.english = english;
}

public boolean propertiesEquals(Object o) {
if (this == o)
return true;
if (null == o)
return false;
if (o instanceof Grade) {
Grade g = (Grade) o;
if (!this.mathematics.equals(g.mathematics))
return false;
if (!this.chinese.equals(g.chinese))
return false;
if (!this.english.equals(g.english))
return false;
return true;
} else if (o instanceof Map) {
Map<?, ?> m = (Map<?, ?>) o;
if (null == m.get("mathematics"))
return false;
if (null == m.get("chinese"))
return false;
if (null == m.get("english"))
return false;
Object om = m.get("mathematics") instanceof String ? Integer.valueOf((String) m.get("mathematics"))
: m.get("mathematics");
Object oc = m.get("chinese") instanceof String ? Integer.valueOf((String) m.get("chinese"))
: m.get("chinese");
Object oe = m.get("english") instanceof String ? Integer.valueOf((String) m.get("english"))
: m.get("english");
if (!this.mathematics.equals(om))
return false;
if (!this.chinese.equals(oc))
return false;
if (!this.english.equals(oe))
return false;
return true;
}
return false;
}

@Override
public String toString() {
return "Grade [mathematics=" + mathematics + ", chinese=" + chinese + ", english=" + english + "]";
}

}

/**
* 枚举类,性别。
*
* @author zengguang
*
*/
public enum Sex {
MALE, FEMAL;
}
}

题外话

BeanUtils性能堪忧,可参考某大佬写的对比博客,见参考资料。

参考资料

  1. BeanCopy类库

带资源的try语句

发表于 2019-08-21 分类于 java

语法

带资源的try语句,英文try-with-resources,JDK1.7及之后有效:

1
2
3
4
5
try(/**
* 实现了java.io.Closeable接口的资源定义。
*/) {
// 业务代码
}

示例:

1
2
3
4
5
try (BufferedReader br = new BufferedReader(new FileReader("text.txt"))){
// 业务代码
} catch (IOException e) {
e.printStackTrace();
}

与不带资源try的对比

如果只有一个资源使用try-with-resources并无太大优势,try/catch/finally则更简洁一点。但是当多个资源同时打开时,就方便太多了,对比如下:

try-with-resources

1
2
3
4
5
6
try (BufferedReader br1 = new BufferedReader(new FileReader("text1.txt"));
BufferedReader br2 = new BufferedReader(new FileReader("text2.txt"))) {
// 业务代码
} catch (IOException e) {
e.printStackTrace();
}

JDK1.7之前

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
BufferedReader br1 = null;
BufferedReader br2 = null;
try {
br1 = new BufferedReader(new FileReader("text1.txt"));
br2 = new BufferedReader(new FileReader("text2.txt"));
// 业务代码
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br1) {
br1.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br2) {
br2.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

注意:JDK1.7之前正确关闭的资源是finally和try互相嵌套才能正确关闭,下面这种是错误的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BufferedReader br1 = null;
BufferedReader br2 = null;
try {
br1 = new BufferedReader(new FileReader("text1.txt"));
br2 = new BufferedReader(new FileReader("text2.txt"));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != br1) {
br1.close(); // br1执行close()出现异常,br2就无法关闭了
}
if (null != br2) {
br2.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

常见异常

之前开发时经常出现一个异常提示如下:

1
java.io.IOException: 您的主机中的软件中止了一个已建立的连接

后来发现调用的框架工具中未正确关闭资源,导致连续请求时触发该异常。

参考资料

  1. The Java™ Tutorials
123
Eric Zeng

Eric Zeng

你的才华撑不起你的野心时,请静下心来学习
25 日志
11 分类
75 标签
RSS
GitHub E-Mail
Creative Commons
© 2019 EricZeng
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Mist v7.2.0
|
0%