Posts Tagged ‘Java’

JDK8版本过高引起MySQL连接失败:javax.net.ssl.SSLHandshakeException: No appropriate protocol

September 28th, 2021

最近在做面向k8s的项目镜像,遇到很奇怪的问题,SpringCloud 项目连接mysql 抛 javax.net.ssl.SSLHandshakeException: No appropriate protocol,调查一段时间后,发现是Java security中的配置不对导致连接Mysql异常。

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
	at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171) ~[na:1.8.0_292]
	at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:98) ~[na:1.8.0_292]
	at sun.security.ssl.TransportContext.kickstart(TransportContext.java:220) ~[na:1.8.0_292]
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:428) ~[na:1.8.0_292]
	at com.mysql.cj.protocol.ExportControlled.performTlsHandshake(ExportControlled.java:316) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.StandardSocketFactory.performTlsHandshake(StandardSocketFactory.java:188) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.a.NativeSocketConnection.performTlsHandshake(NativeSocketConnection.java:99) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	at com.mysql.cj.protocol.a.NativeProtocol.negotiateSSLConnection(NativeProtocol.java:331) ~[mysql-connector-java-8.0.17.jar:8.0.17]
	... 68 common frames omitted

看到SSLHandshakeException,心里打起了问号?这个错误比较反常,最终网上找了一番,问题定位了:JDK8高版本导致的,因为之前用的是oracle:1.8,然后换成了openjdk:8,然后发现项目无法启动了。

方法一:

此处连接的MySQL,导致的报错,修改jdbcUrl,在其后面加useSSL=false后运行正常

方法二:

删除SSLv3, TLSv1, TLSv1.1并保存java.security文件,重启项目即可解决问题,删除后此处为:

# Example:
#   jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
    DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
    include jdk.disabled.namedCurves

方法三:

降低JDK版本,这个相当也容易操作,比如可以从1.8.0_292降到1.8.0_281,甚至是1.8.0_275版本,但个人不建议,因为Oracle对JDK 8的支持一直会到2030年:即使很长一段时间用JDK 8,但JDK 8本身也是有小版本迭代的,比如你明年换了电脑,安装JDK,基本是1.8.0_292之后的版本,那这个问题会一直存在。

方法四:

方法一 能解决由SSL调用权限导致的所有问题,但破坏了安全性
方法二 针对MySQL的问题,可以快速解决
方法三 不推荐

因此,碰到类似问题,基本的思路是兼容JDK 8高版本甚至JDK高版本,比如代码层面的:

private Socket overrideTlsProtocol(final Socket socket) {
    if (!(socket instanceof SSLSocket)) {
        throw new RuntimeException("Error, an instance of SSLSocket is expected");
    }
    ((SSLSocket) socket).setEnabledProtocols(new String[]{"SSLv3"});
    return socket;
}

修改为:

private Socket overrideTlsProtocol(final Socket socket) {
    if (!(socket instanceof SSLSocket)) {
        throw new RuntimeException("Error, an instance of SSLSocket is expected");
    }
    ((SSLSocket) socket).setEnabledProtocols(new String[]{"SSLv3", "TLSv1","TLSv1.1"});
    return socket;
}

Springboot 拦截器的坑 WebMvcConfigurationSupport 失效

October 16th, 2020

今天遇到一个拦截器失效的问题,具体看源码分析下。

环境:

springboot 2.x

spring 5.x

===================

拦截器创建的几种方式

  • extends WebMvcConfigurationSupport
  • implements WebMvcConfigurer

如果项目中出现了一次 extends WebMvcConfigurationSupport ,其他的 extends WebMvcConfigurationSupport 和 implements WebMvcConfigurer 会失效 。

先看下 WebMvcConfigurationSupport 这个类, addInterceptors 这个方法,默认继承的是 DelegatingWebMvcConfiguration,这个类就是获取 `所有 ` 实现 WebMvcConfigurer 的子类,调用他们的方法,如果有多个 通过实现 WebMvcConfigurer 创建的拦截器,是都可以生效的。

那多个 继承 WebMvcConfigurationSupport 为啥只有一个生效呢,答案在这个类WebMvcAutoConfiguration 的 ConditionalOnMissingBean 注解,只实例化一个Bean,多个继承也只有一个生效。
再看下 addInterceptors 啥时候触发的,获取拦截器的时候,获取过就不再获取了,所以 addInterceptors 在项目启动触发才有效。而 getInterceptors 这个方法是在 handerMapping映射的时候触发的(比如 RequestMappingHandlerMapping、BeanNameUrlHandlerMapping)。

解决方案

针对不同的场景解决方案也不一样,我想到的有3个方案。

  • 不继承 WebMvcConfigurationSupport ,拦截器全部通过实现 WebMvcConfigurer 接口(推荐)
  • 只继承一次 WebMvcConfigurationSupport ,在这个类管理所有的拦截器(不推荐,耦合性太高)
  • 针对我的场景,通过过滤器实现的。注入的代码就不贴了,before 和 fater 方法实现了类似拦截器的 preHandle 和 afterCompletion。有一点需要注意的是指定过滤器的排序(默认已经是最高了,可以忽略),由于过滤器是链式调用,如果想当拦截器用,必须指定最先加载,还有就是过滤器会拦截静态资源,做好对静态资源的放行。

 

https://www.lyscms.info/blog/detail/33A55BEE2FD94E66B40990EA4967D3F7

https://blog.csdn.net/pengdandezhi/article/details/81182701

Java Executor 框架使用的思考

April 18th, 2018

最近在使用Java的线程池做一些线程频繁创建销毁的事情,我的目标是利用已有的云Queue服务,将我们的服务接入这个Queue,目前我们使用的是一个基于内存Queue的异步队列框架,这就导致当进程因为某种原因导致崩溃后,内存Queue中的数据全部丢失,这是我们所不能容忍的。

因此我们在云Queue上创建两个Queue,在处理高峰时刻将一部分数据导入backup queue中,当主 queue中的Message消费完毕后,主动去消费backup queue 中的数据。开始我使用一种按需分配的方式,即先使用Excutor创建一个pool后,只有一个线程去轮询主Queue中是否有数据,云Queue中有数据就创建一个线程加入到pool中执行,最后自到触发RejectedExecutionException,这个时候我将主Queue中的数据放到backup Queue中执行。

另外一种比较土的方案就是Server一启动,直接启动400个线程加入到pool中执行业务逻辑,长轮询主Queue是否有数据,有就去消费,没有就去消费Backup Queue中的数据。

从直觉上,大家都会认为第一种方案资源消耗会更优,但是在实际的工程实现中,必然存在设计和实现的折中,我的每个task执行逻辑会有多大五六个返回值,有的返回值需要将消息推送给用户,如果我按照第一个方案去做,那么必然有一刻处理峰值,会将大量的Message都导入backup Queue,只要进入backup Queue中后,就无法给用户实时推送消息,换句话说,消息就丢失了,虽然我很想用第一种方案去实现复杂逻辑,但是在实际的实现中会导致我的backup queue会分成很多种情况,这就导致实现复杂度和queue的复杂度提升更快。经过半个月的讨论,我终于敲定使用简单粗暴的方式去实现我的功能。

以上是我的一个吐槽。

 

https://blog.csdn.net/pfnie/article/details/52755769

Java序列化与反序列化

August 8th, 2016

最近在对一个大型Java项目进行调试,这个Java程序当点击一个按钮后就会未响应,后来经过调试发现代码中存在大量的io操作,他们将从文件读取的数据存入到HashMap、LinkList中。找到了程序性能瓶颈,我设计一种缓存思路,将这种Object直接写入文件,只要存在我要找的Object文件,我就不会费时费力的重新存入HashMap,而是从Object中读取。

根据查询Java Docs api,我发现一组Interface Serializable,只要class继承了Serializable接口,那么这个类就可以被序列化,因此我把该类中所有的成员变量所在class都继承了Serializable接口。具体读写Object文件的方式和一般文件读取非常类似。

存Object文件:

class T implements Serializable
{
	int i = 10;
	int j = 9;
	double d = 2.3;
	transient int k = 15;
}
....
FileOutputStream fos = new FileOutputStream("...");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(t);

写Object文件:

FileInputStream fis = new FileInputStream("....");
ObjectInputStream ois = new ObjectInputStream(fis);
T t= (T)ois.readObject();

Java提供了transient 关键字,他的意思就是表明该成员变量不会被序列化,也就是说反序列化后该成员变量保存的值会消失。如果继承了Serializable,我们是无法干预jre对class对象的序列化,所以writeObject、readObject对象一定要顺序相同,因为只有我们知道序列化的内容。

jre为每个序列化的class都赋值了serialVersionUID ,以此来计算反序列化时该值是否相同,不同的话也就意味着该class不能被恢复出来。

The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the receiver has loaded a class for the object that has a different serialVersionUID than that of the corresponding sender’s class, then deserialization will result in an InvalidClassException. A serializable class can declare its own serialVersionUID explicitly by declaring a field named “serialVersionUID” that must be static, final, and of type long:

ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果我们想控制序列化的时机,那么我们应该让class继承Externalizable接口,Externalizable接口事实上又继承了 Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

我们可以在writeExternal()和readExternal()中自定义序列化的方式,这样子可以增强灵活性。也就是说每次writeObject时,都会调用WriteExtenal()方法,而在WriteExtenal()方法中我们需要将当前对象的值写入到流中;而每次readObject()时,调用的是默认的构造函数,如果我们不在 readExternal()方法中读取每个成员变量,那么成员变量就是null!

 

参考:

http://www.cnblogs.com/chenfei0801/archive/2013/04/06/3002146.html

解决乱码思路—Java读写文件中遇到的问题

July 26th, 2016

最近在做一个大型Java项目的移植工作,遇到大量字符编码问题,传统的Windows平台主要使用GBK编码,而linux上面为了保持字符的统一性,使用UTF8编码,这个就导致从windows迁移过来的配置文件和资源文件乱码。之前我写过一篇《字符编码浅析》,简单介绍了下这几种编码的不同。

这里我首先解决Linux平台下的乱码问题:如果只是要看一个不同编码的文件,我们可以用vim打开,然后:set fileencoding=utf8  gbk….的方式改变查看文档的编码。如果我们想转换文档编码形式,那么使用以下的命令(将一个GBK编码的文件转换成UTF-8编码):

$enconv -L zh_CN -x UTF-8 filename

文档编码转换完成,这里我从Java的角度阐述如果读写。因为网上关于Java读写的博文足够多,我主要来总结一下:

Java 的IO有两种流的类型:那就是字节流(InputStream、OutputStream)和字符流(Reader、Writer)。我们在读写中文的时候务必使用字符流,因为中文是2个字节组成,如果读取一个字节必然出现问题。

InputStream常用的类是如下,直接对读数据的是节点流,浅色的是处理流,依靠InputStream对数据进行读写,拥有更强大的功能。

20160808200550

OutputStream常用类如下,直接写的是节点流,浅色的是处理流。

20160808200918

二进制读写的时候要使用Stream的方式进行,而读写字符就使用Reader、Writer。

20160808201307

20160808201400

总结一下两种类型,节点流为以下表格,通过这些对象直接对数据源进行读写。

类型 字符流 字节流
File FileReader、FileWriter FileInputStream、FileOutputStream
Memory Array CharArrayReader、CharArrayWriter ByteArrayInputStream、ByteArrayOutputStream
Memory String StringReader、StringWriter
Pipe PipedReader、PipedWriter PipedInputStream、PipedOutputStream
  • 但是节点流提供功能单一,需要使用处理流对其进行拓展,其中尤其是 BufferedReader、BufferedWriter最为常用,先看一个表格:

 

处理类型 字符流 字节流
Buffering BufferedReader、BufferedWriter BufferedInputStream、BufferedOutputStream
 Filtering  FilterReader、FilterWriter FilterInputStream、FilterOutputStream
字节流字符流转换 InputStreamReader、OutputStreamWriter
Object序列化 ObjectInputStream、ObjectOutputStream
数据转换 DataInputStream、DataOutputStream
计数 LineNumberReader LineNumberInputStream
Peeking ahead PushbackReader PushbackInputStream
Print PrintWriter PrintStream
  • BufferedReader和BufferedWriter来读写文件(字符读写,只要不是二进制文件,都可以用字符,字节是给二进制文件使用的),这种方式读写效率最好(而且可以选择想要的字符集),这个主要将数据先缓冲起来,然后一起写入或者读取出来。经常使用的是readLine()方法,一次读取一行数据。其中我们可以根据装饰者模式修改BufferReader和BufferWriter中的类,比如new InputStreamReader(new FileInputStream(…), StandardCharsets.UTF_8),通过这种方式,就可以以UTF-8的方式读取文件,或者以new OutputStreamWriter(new FileOutputStream(“test.txt”), “UTF-8”)写文件

使用方式 FileReader->BufferedReader->readLine、FileWriter->BufferedWriter->write

private static void read() throws FileNotFoundException, IOException {  
        File file = new File("a.txt");// 指定要读取的文件  
        // 获得该文件的缓冲输入流  
        BufferedReader bufferedReader = new BufferedReader(new FileReader(file));  
        String line = "";// 用来保存每次读取一行的内容  
        while ((line = bufferedReader.readLine()) != null) {  
            System.out.println(line);  
        }  
        bufferedReader.close();// 关闭输入流  
    }  
private static void write() throws IOException {  
        File file = new File("a.txt");// 指定要写入的文件  
        if (!file.exists()) {// 如果文件不存在则创建  
            file.createNewFile();  
        }  
        // 获取该文件的缓冲输出流  
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file));  
        bufferedWriter.write("你好世界");  
        bufferedWriter.newLine();
        bufferedWriter.write("hello world");  
        bufferedWriter.flush();// 清空缓冲区  
        bufferedWriter.close();// 关闭输出流  
    }  

与他类似的就是BufferedInputStream和BufferedOuputStream,用这两个读取二进制文件,不同的在于读取的是二进制文件。

FileInputStream ——>BufferedInputStream -> read、FileOutputStream->BufferedOutputStream->write

        // 指定要读取文件的缓冲输入字节流  
        BufferedInputStream in = new BufferedInputStream(new FileInputStream("a.jpg"));  
        File file = new File("b.jpg");  
        if (file != null) {  
            file.createNewFile();  
        }  
        // 指定要写入文件的缓冲输出字节流  
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));  
        byte[] bb = new byte[1024];// 用来存储每次读取到的字节数组  
        int n;// 每次读取到的字节数组的长度  
        while ((n = in.read(bb)) != -1) {  
            out.write(bb, 0, n);// 写入到输出流  
        }  
        out.close();// 关闭流  
        in.close();  

有的时候我们还要使用File对象,这个时候可以查看FileInputStream、FileOutputStream、FileReader、FileWriter的构造方法。类似于FileReader(File file)。

字符流到字节流的转换也很重要,类似于:

InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
    String s = null;
    try {
      s = br.readLine();
...

OutputStreamWriter osw = new OutputStreamWriter(
           new FileOutputStream("...."));
osw.write("mircosoftibmsunapplehp");

Java中io类太多了,有时候容易眼花,不知道用哪个,这个时候只要记住字符流和字节流的异同,合理使用类,并查询java api即可。

参考:

http://blog.csdn.net/dangnianmingyue_gg/article/details/47361323