JDK各个版本特性速览

JDK各个版本特性速览

JDK各个版本特性速览

JDK 1.5

很重要的一个版本。

  • 更新了Java内存模型(JMM)
    • 增强volatile语义
    • 增强final语义
  • 泛型
  • For-Each循环
  • 注解
  • 枚举
  • JUC包
  • lock接口

JDK 1.6

  • Desktop类和SystemTray类
  • JAXB2实现对象与XML之间的映射
  • StAX
  • Compiler API
  • 轻量级 Http Server API
  • 插入式注解处理API(Pluggable Annotation Processing API)
  • 用Console开发控制台程序
  • 对脚本语言的支持(如:ruby,groovy,javascript)
  • Common Annotations

并发相关:sychronized锁优化:偏向锁、轻量级锁、重量级锁

JDK 1.7

  • 二进制字面量
  • 数字字面量可以出现下划线
  • switch 语句可以用字符串
  • 泛型实例的创建可以通过类型推断来简化
  • try-with-resources 语句
  • 异常处理(捕获多个异常)
  • JSR203 NIO2.0(AIO)新IO的支持
  • JSR292与InvokeDynamic指令
  • Path接口、DirectoryStream、Files、WatchService(重要接口更新)
  • fork/join framework

JDK 8之后的更新内容,强烈推荐这篇文章:

聊聊 Java8 以后各个版本的新特性

JDK 1.8

  • Lambda
  • Stream
  • Optional
  • Date/Time API (JSR 310)
  • JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)。

JDK 9

  • 平台级modularity(原名:Jigsaw) 模块化系统
  • Java 的 REPL 工具: jShell 命令
  • 多版本兼容 jar 包(这个在处理向下兼容方面,非常好用)
  • 语法改进:接口的私有方法
  • 语法改进:UnderScore(下划线)使用的限制
  • 底层结构:String 存储结构变更(这个很重要)
  • 集合工厂方法:快速创建只读集合
  • 增强的 Stream API
  • 全新的 HTTP 客户端 API
    • HTTP/2, WebSocket
  • 其它特性

JVM相关:
G1是JDK 9默认GC。

JDK 10

  • 局部变量的类型推断 var关键字
  • GC改进和内存管理 并行全垃圾回收器 G1
  • 垃圾回收器接口
  • 线程-局部变量管控
  • 合并 JDK 多个代码仓库到一个单独的储存库中
  • 新增API:ByteArrayOutputStream
  • List、Map、Set新增API:copyOf(Collection)
  • 新增API:java.util.Properties
  • 新增API: Collectors收集器
  • 其它特性

JVM相关:

JDK 11

2018年9月26日,Oracle 官方宣布 Java 11 正式发布。这是 Java 大版本周期变化后的第一个长期支持版本(LTS版本,Long-Term-Support,持续支持到2026年9月),非常值得关注。

  • 本地变量类型推断
  • 字符串加强
  • 集合加强
  • Stream 加强
  • Optional 加强
  • InputStream 加强
  • HTTP Client API
  • 化繁为简,一个命令编译运行源代码

JVM相关:
引入了实验性的ZGC

参考资料

spring cloud版本由1.5.x升级到2.x所遇到的坑

spring cloud版本由1.5.x升级到2.x所遇到的坑

众所知周,spring cloud 1.5版本与2.x版本差异很大,官方没有做向下兼容,导致大家对于升级spring cloud版本都非常慎重。

此处,首先推荐阅读官方给出的迁移手册 Spring Boot 2.0 Migration Guide

虽然是英文的,但建议还是好好读读。

预备知识

废话一下:
升级操作前务必要对spring, spring boot, spring cloud版本、各自关联有清晰的认识。
建议看官网,然后也可以参考此文章SpringBoot与SpringCloud的版本对应详细版

事前准备

首先,官方建议,若要升级到2.x,首先应该分步骤来:

  • 先升级到1.5.x
  • 再升级到2.0
  • 再升级到2.1等更高版本

再者,建议在迁移到2.0时,先在pom中添加如下依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>

这个是官方提供用于验证原有配置是否还生效的包,项目启动时将会给出提示,并告知在新版spring cloud中,对应配置关键字是什么。

迁移操作。。。。

更新步骤说起来简单,即更新依赖文件中的版本号,但spring cloud涉及n多组件,升级过程中必然遇到各种坑。

1. 更新spring cloud, spring boot

以本人负责项目为例。
spring cloud更新前:

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

更新后:

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

spring boot更新前:

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

spring boot更新后:

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

2. 更新关联的包

更新这两个包后,若有使用eureka/feign/hystrix,会因为这些包重命名、原有包在新版spring cloud中不能再使用,此处仅给出对应包名,细微之处建议仔细查看相关的官方文档,确保使用上不会问题。

E版本重命名列表参见Spring Cloud Edgware Release Notes

spring_cloud_edgware_rename_starters.png

2.1 eureka

mvnrepository
更新前:
下面的这两个包在mvnrepository.com上已经被标记为deprecated

1
2
3
4
5
6
7
8
9
10
<dependency>
<!--mvnrepository 上标记为:Spring Cloud Starter Eureka Server (deprecated, please use spring-cloud-starter-netflix-eureka-server) -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependency>
<!--mvnrepository 上标记为:Spring Cloud Starter Eureka (deprecated, please use spring-cloud-starter-netflix-eureka-client) -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

更新后

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2.2 feign

更新前:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

更新后:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

注意,feign本身也有一些变化,可以参考此文章 Spring cloud系列十八 Spring Cloud 从Dalston.SR5到Greenwich.SR1 的升级记录,比如:

  1. feign中部分包名变化
1
org.springframework.cloud.netflix.feign.**.java -> org.springframework.cloud.openfeign.**.java
  1. @FeignClient增加一个属性值contextId

具体变化建议查看官网,懒的话可以看下面两篇文章:

3. 属性变化

此处就要感谢spring-boot-properties-migrator 这个包了,会自动给出新版本里应该用哪个属性。
比如:
应用的上下文路径:server.context-path: authority –> server.servlet.context-path: authority
上传文件参数配置:spring.http.multipart.* -> spring.servlet.multipart.*
如原 spring.http.multipart.maxFileSize:5Mb -> spring.servlet.multipart.maxFileSize:5MB
同时这里5Mb中的Mb的必须是大写字母,必须是5MB,否则会抛出异常:failed to convert java.lang.String to @org.springframework.boot.convert.DataSizeUnit org.springframework.util.unit.DataSize

4. 其他包的升级

spring cloud, spring boot, spring升级后,相关联的包及建议同步升级,否则可能会无效,比如说pagehelper,druid,jedis等。既然要动依赖包,建议这次就一次性到位,本身整个项目的所有功能肯定需要回归验证一遍,建议就一起升级。
可以参考这两篇文章:

参考资料

spring-boot以jar包方式时读取resource或是template文件

spring-boot以jar包方式时读取resource或是template文件

现象

以jar包方式部署系统,想读取resource或是template下面的文件时,报 File Not Found

我遇到的情况是,整个项目达成了一个包,在开发环境(windows + idea)读取文件没问题,但在预发布环境(centos, 打成一个jar部署),则报错。
使用

1
jar -xvf xxx.jar

命令解压jar后,大体结构如下:

1
2
3
4
BOOT-INF
META-INF
org
...

继续往下找可以找到我想要读取的资源文件,说明打包正常,那只能说明:
以jar形式部署后,采用一般的java 读取文件的API接口,可能无法从jar包直接读取到文件。

解决

定位到是读取文件方式问题后,试验了几种,以下都列下:

方法1

1
2
ClassPathResource classPathResource = new ClassPathResource("excelTemplate/test.xlsx");
InputStream inputStream =classPathResource.getInputStream();

方法2

1
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("excelTemplate/test.xlsx");

方法3

1
InputStream inputStream = this.getClass().getResourceAsStream("/excelTemplate/test.xlsx");

方法4

1
2
File file = ResourceUtils.getFile("classpath:excelTemplate/test.xlsx");
InputStream inputStream = new FileInputStream(file);

上述源代码参考SpringBoot读取Resource下文件的几种方式
其中:
方法1/2/3在开发环境、预发布环境都可以读取到jar包中的文件,方法4则只有开发环境中可以、直接从jar包读取失败。

前3种方法其实本身一样,都是调用了类加载器读取文件流。以方法1为例,ClassLoader的getResourceAsStream实现了根据路径获取输入流的过程,具体则需要一步步跟,这个就需要各位去看了。具体可以参考这篇文章彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理

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
……
/**

*/
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}

public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
……

参考资料

spring项目部署linux时读取字体失败导致绘图时输出乱码

spring项目部署linux时读取字体失败导致绘图时输出乱码

现象

spring项目部署linux时读取字体失败导致绘图时输出乱码,比如说调用Graphics2D graphic绘图时用到字体,却输出乱码。

原因

字体文件找不到,比如说开发环境是windows,可能是使用“微软雅黑”字体,是正常的,但部署到线上(往往是linux),就会是乱码。

解决

1、找到项目所需字体,上传到linux服务器中,并放到指定位置

先说结论:我个人不推荐这样做!!!

比如说这篇帖子中说的(我没试验,网上搜到顺手转给大家):
java jdk-awt.font在centos上中文乱码的问题, 安装中文字体

之所以不推荐,是因为这样做,线上部署时增加了依赖关系,增加了维护成本。

2、将所依赖字体放入到项目中,直接一起部署到线上环境

我个人觉得这样做比较好,因为不会依赖外部环境,所需做的只是读取自带的字体文件而已。
当然,这里埋着几个坑,分享下:

(1)整个项目(资源文件、依赖包等)打成1个jar包部署时,注意读取文件的方式

由于文件都已经保存到了一个jar包中,直接调用

1
2
File file = ResourceUtils.getFile("classpath:excelTemplate/test.xlsx");
InputStream inputStream = new FileInputStream(file);

这种方式可能读取不到文件,需要使用Classloader去读取,具体参考上篇文章spring-boot以jar包方式时读取resource或是template文件

(2)读取字体文件、创建Font对象时,可能存在不断创建临时文件的问题

这个问题我在网上搜时频繁出现,我自己没遇到,贴出来供大家参考。

不会创建临时文件的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//filepath字体文件的路径
private static java.awt.Font getSelfDefinedFont(String filepath){
java.awt.Font font = null;
File file = new File(filepath);
try{
font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, file);
font = font.deriveFont(java.awt.Font.PLAIN, 40);
}catch (FontFormatException e){
return null;
}catch (FileNotFoundException e){
return null;
}catch (IOException e){
return null;
}
return font;
}

参考资料

连接redis时出现 “ERR Client sent AUTH, but no password is set” 异常的原因及解决办法

连接redis时出现 “ERR Client sent AUTH, but no password is set” 异常的原因及解决办法

现象

使用redis-cli连接redis时,报错:
ERR Client sent AUTH, but no password is set

异常信息类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:53)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)
at cn.hncu.RedisPool.getJedis(RedisPool.java:66)
at cn.hncu.RedisJava.main(RedisJava.java:15)
Caused by: redis.clients.jedis.exceptions.JedisDataException: ERR Client sent AUTH, but no password is set
at redis.clients.jedis.Protocol.processError(Protocol.java:127)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340)
at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:239)
at redis.clients.jedis.BinaryJedis.auth(BinaryJedis.java:2139)
at redis.clients.jedis.JedisFactory.makeObject(JedisFactory.java:108)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:868)

原因

Redis服务器没有设置密码,但客户端向其发送了AUTH(authentication,身份验证)请求。

解决

解决方法1:配置参数不传密码

不推荐,请直接跳过这个步骤。

解决方法2:给redis集群设置密码

首先找到正在使用的配置文件,在配置文件中找到requirepass,去掉注释设置密码,编辑完后保存,重新启动Redis,再运行程序

参考资料

git使用技巧-快速回退远端分支到某个指定版本

git使用技巧-快速回退远端分支到某个指定版本

先使用

1
git log

获取想要回退版本的commit id,假定此处为82d9cab12731870bd04327464e501479da3a87e2

然后通过

1
git reset 82d9cab12731870bd04327464e501479da3a87e2

将git本地版本回滚到指定commit。

然后通过

1
git push -f

将本地更新强制推送到远端。

mysql source命令导入大文件时失败的解决方法

mysql source命令导入大文件时失败的解决方法

方法:
1、将大文件分成多个小文件
2、检查mysql的max_allowed_packet参数配置,命令如下:

1
show variables like '%max_allowed_packet%';

适当调大该参数,比如在启动mysql client端时,通过加参数的方式,在客户端调整该参数

1
mysql --max_allowed_packet=32M

更多修改方法参见官方文档:B.4.2.9 Packet Too Large,以及这篇 How to change max_allowed_packet size

参考资料

spring boot启动失败不输出日志

spring boot启动失败不输出日志

spring boot启动失败,没有任何错误日志输出,只输出了如下信息:

可能的原因:

1、日志配置文件没有配好

解决:修改日志等级,找到logback-boot.xml文件,找到<root level="INFO"> </root>,修改日志等级,添加标准输出

1
2
3
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

如果自己没有写日志配置,可能是被其他jar包中的log配置文件覆盖了你本地的默认的日志。
解决办法:编写自己的日志配置文件或者排除一下资源文件。

2、jar冲突

3、idea本地缓存导致的失败

这个是我遇到的情况,莫名其妙,前一天晚上还好好的,第二天突然就启动不了,什么都不输出,还是只显示下面这图

此时可以尝试执行mvn clean命令,清除target目录下的之前打好的jar包或者是war包。

当然,也可以尝试这样,然后重新build。

参考资料

mybatis plus配置多数据源时批量接口调用失败以及解决方案

mybatis plus配置多数据源时批量接口调用失败以及解决方案

表现

仅配置单个数据源时,mybatis plus的save/saveBatch接口调用正常
配置多个数据源、动态切换时,mybatis plus的save接口调用正常,saveBatch调用失败,报错如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
org.apache.ibatis.exceptions.PersistenceException: 
### Error flushing statements. Cause: org.apache.ibatis.executor.BatchExecutorException: com.xxx.survey.mapper.SurveyAnswerMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: 对象名 't_survey_answer' 无效。
### Cause: org.apache.ibatis.executor.BatchExecutorException: com.xxx.survey.mapper.SurveyAnswerMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: 对象名 't_survey_answer' 无效。
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.flushStatements(DefaultSqlSession.java:255)
at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.saveBatch(ServiceImpl.java:128)
at com.baomidou.mybatisplus.extension.service.IService.saveBatch(IService.java:58)
at com.baomidou.mybatisplus.extension.service.IService$$FastClassBySpringCGLIB$$f8525d18.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671)
at com.xxx.survey.service.impl.SurveyAnswerServiceImpl$$EnhancerBySpringCGLIB$$dbeefaf3.saveBatch(<generated>)
at com.xxx.survey.ServiceTest.testGetAttendanceRecord(ServiceTest.java:91)
at sun.refl

原因

配置动态数据源之后,我在配置对象中mapper xml文件夹配置没有区分开,不同数据源的mapper都放到了同一个文件夹中,即不同的

1
2
3
4
5
// 设置mapper.xml文件的路径
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//动态数据源的情况下,需要分别制定mapper xml文件的保存位置,否则会引起mybatis plus批量接口失败(比如save成功、但saveBatch失败)
Resource[] resource = resolver.getResources("classpath:mapper/*.xml");
mybatisPlus.setMapperLocations(resource);

我这里之前都两个数据源mapper配置的是classpath:mapper/*.xml

解决

不同数据库的xml放到不同文件夹下即可:
数据源1

1
2
3
4
5
// 设置mapper.xml文件的路径
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//动态数据源的情况下,需要分别制定mapper xml文件的保存位置,否则会引起mybatis plus批量接口失败(比如save成功、但saveBatch失败)
Resource[] resource = resolver.getResources("classpath:mapper/folder1/*.xml");
mybatisPlus.setMapperLocations(resource);

数据源2

1
2
3
4
5
// 设置mapper.xml文件的路径
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//动态数据源的情况下,需要分别制定mapper xml文件的保存位置,否则会引起mybatis plus批量接口失败(比如save成功、但saveBatch失败)
Resource[] resource = resolver.getResources("classpath:mapper/folder2/*.xml");
mybatisPlus.setMapperLocations(resource);

即可解决。

spring boot中配置mybatis plus输出SQL查询语句日志

spring boot中配置mybatis plus输出SQL查询语句日志

在spring boot的application.yml文件中配置:

1
2
3
4
# 配置sql打印日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

如果是application.properties,添加:

1
2
# 配置sql打印日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

打印结果类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
……
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f27d5a9] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.jdbc.JDBC4Connection@bad933d] will not be managed by Spring
==> Preparing: SELECT id,city_name,is_close,is_deleted,is_test FROM t_hr_city WHERE is_deleted = ? AND is_test = ? ORDER BY id ASC
==> Parameters: 0(Integer), 0(Integer)
<== Columns: id, city_name, is_close, is_deleted, is_test
<== Row: 1, 北京, 0, 0, 0
<== Row: 2, 深圳, 0, 0, 0
<== Row: 3, 上海, 0, 0, 0
<== Row: 4, 成都, 0, 0, 0
<== Row: 5, 广州, 0, 0, 0
<== Row: 6, 武汉, 0, 0, 0
<== Row: 7, 杭州, 0, 0, 0
<== Row: 8, 江西, 0, 0, 0
<== Row: 9, 广西, 0, 0, 0
<== Row: 10, 石家庄, 0, 0, 0
<== Total: 10
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f27d5a9]
……

参考资料