GeXiangDong

精通Java、SQL、Spring的拼写,擅长Linux、Windows的开关机

0%

出现的问题

JDK8 调用 java.util.Base64解码时出现异常:java.lang.IllegalArgumentException: Illegal base64 character 20

原因

character 20 是空格,如果待解码的字符串没错,可能是遇到了编码标准的问题。 Base64有几个规范:

  • RFC4648 / RFC2045 这两个规范规定encode后的字符包括:0-9,a-z,A-Z,+/
  • RFC1521 这个规范除上述的0-9,a-z,A-Z,+/外,还包含空格符和换行符

JDK8的Base64遵守的是第一个无空格符和换行符的规范,所以遇到空格或换行会抛异常。

解决方法

知道了原因,解决起来就很简单了,换支持RFC1521标准的decoder解码就可以了,例如apache commons-codec。

动态继承一个类/抽象类(不是接口),(动态实现接口参照https://gist.github.com/gexiangdong/f7536a8d86a631b1c391acf13d334a90

此实现需要cglib, pom中增加依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

实现代码如下:

待创建的父类,可以是抽象类

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 被创建的类的例子,也可以是抽象类
*/
public class MyClass {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

动态创建

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
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 使用CGLIB动态生成一个类的子类的例子
*/
public class MyClassProxy {
public static void main(String[] argvs) throws Exception{
MyClass mc = createDefaultImplementation(MyClass.class);
mc.setName("waytt");
System.out.println(mc.getName());
}
public static <A> A createDefaultImplementation(Class<A> abstractClass) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(abstractClass);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("MethodInterceptor.intercept(proxy, " + method.getName() + ",...)");
if (!Modifier.isAbstract(method.getModifiers())) {
//not abstract method, 可调用类的方法并返回
return methodProxy.invokeSuper(proxy, args);
} else {
// 抽象方法,不能执行父类的方法了,需要自己构建一个返回值
System.out.println("abstract method....");
Class type = method.getReturnType();
// 生成返回值
return null
}
}
});
return (A) enhancer.create();
}
public static <A> A createDefaultImplementation(String className) throws ClassNotFoundException{
return (A) createDefaultImplementation(Class.forName(className));
}
}

通过Proxy类动态实现一个接口 (只能是接口,可多个接口,如果需要动态创建抽象类的子类,可参照https://gist.github.com/gexiangdong/599b58566e349be40d99ee02877eb985
Raw

实现代码如下:

接口 MyInterface.java

1
2
3
4
5
6
7
/**
* 一个自定义接口,供下面的程序调用;
*/
public interface MyInterface {
public String getName();
public void setName(String name);
}

通过Proxy动态创建 MyInvocationHandler.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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 实现InvocationHandler的类,对创建出来的类的调用都会转给此类
*/
public class MyInvocationHandler implements InvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("invoke(" + proxy.getClass().getName() + ", method:" + method.getName() + ", " + (args == null ? "NULL" : args) + ")");
if(method.getName().equals("getName")) {
return "waytt";
} else if(method.getName().equals("setName")) {
System.out.println("args " + (args != null && args.length > 0 ? args[0] : "NULL"));
return null;
}
return null;
}


public static void main(String[] argvs) throws Exception{
// 动态创建接口的程序在此
InvocationHandler handler = new MyInvocationHandler();
// 第二个参数是需要实现的接口,可以多个,一个类实现多个接口
MyInterface e = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class }, handler);
e.setName("abc");
System.out.println("getName() return " + e.getName());
}
}

查找某个命令/文件所在的位置

whereis

只查询标准的可执行路径内文件(默认只有 /usr/bin, /bin, /usr/sbin, /sbin 等四个目录)

使用方法: whereis filename

which

查询PATH环境变量中设置的所有目录。

使用方法: which filenamewhich -a filename -a 参数表示查询并列出所有,不加此参数只列出第一个。

locate

整个磁盘查询,但需要先建立索引数据库。 sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist 用来建索引

find

整个硬盘查询,速度慢。

使用方法: find / -name filename

ls 的参数

显示完整日期

1
2
3
ls --full-time
ls -l --time-style=full
ls -l --timestyle '+%Y%m%d %H:%M:%S'

如果经常使用,也可以把TIME_STYLE设置到环境变量里,以后ls -l即可

1
2
export TIME_STYLE='+%Y%m%d %H:%M:%S'
ls -l

ls排序

1
2
3
4
5
ls --sort=extension
ls --sort=size
ls --sort=time
ls --sort=version
ls --sort=none
  • -t 按修改日期排序;DESC
  • -S 按文件大小排序,DESC
  • -r 反转排序,变成DESC
  • -U 使用创建日期,不使用修改日期(显示和排序)

统计文件个数

统计某文件夹下文件的个数

1
ls -l |grep "^-"|wc -l

统计某文件夹下目录的个数

1
ls -l |grep "^d"|wc -l

统计文件夹下文件的个数,包括子文件夹里的

1
ls -lR|grep "^-"|wc -l

如统计/home/temp 目录(包含子目录)下的所有文件包含java的文件:

1
ls -lR /home/temp | grep java | wc -l 

统计文件夹下目录的个数,包括子文件夹里的

1
ls -lR | grep "^d" | wc -l

查看目录下所有文件大小

1
du -d 1 -h

命令查看当前目录下所有文件夹的大小 -d 指深度,后面加一个数值,表示统计到几级子目录。

修改文件创建/修改时间

修改文件创建时间

1
touch -mt YYYYMMDDhhmm filename

修改文件更新时间

1
touch -t  YYYYMMDDhhmm filename

微信小程序获得iv, sessionkey 后传递给后端API,然后解密用户信息等敏感信息,有时解密时报错,说iv只有15个byte的问题(应该是16个)。

这是由于小程序段获得的iv是base64 encode之后的,其中可能含有“+”,+通过url参数直接传递,到服务端后,会被当作空格,空格base64.decode后会少一位,变成了15位。

要得到正确的iv,需要小程序传递前先转义,再传递才行。

1
url = "xxx?iv=" + encodeURIComponent(iv)

encryptedData也需要类似的处理。

这样才不会被认作空格,认作空格后,base64.decode会少一位。

Java8 ImageIO读取TIFF会得到null

默认java8(或更低版本)不能处理tiff图片,例如

1
2
BufferedImage bufferedImage = ImageIO.read(new File("desk.tif"));
// 这里得到的bufferedImage == null

上面的代码得到的 bufferedImage 变量是null值

解决办法

在maven的依赖里增加一个imageio-tiff就会解决这个问题。

1
2
3
4
5
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.4.1</version>
</dependency>

此依赖的详细信息可参考如下网址:
https://github.com/haraldk/TwelveMonkeys

Java 9 与 TIFF

Java 9开始jdk已经支持TIFF,不用再额外增加依赖。

Java 9支持的图片格式,oracle官方文档

在 spring-boot 项目中, slf4j和logback被自动引入,只需要配置logback即可使用,当不是spring-boot项目时,需要我们手工做点事才能使用。 (Spring Boot项目中配置logback, 请参考这里)

增加依赖

1
2
3
4
5
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

这一个依赖就够了,它会自动帮我们增加另外两个依赖: logback-core 和 slf4j-api

配置logback.xml

这和spring-boot中项目中区别不大,文件名从logback-spring.xml被改成了 logback.xml,还是放置到 src/main/resources 目录下,里面不再设置spring的profile了。

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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>target/log.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>

<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>

<logger name="cn.devmgr" level="TRACE"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>

</configuration>

可以通过CompletableFuture 来执行某个任务,超时后放弃任务。

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
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CompletableFutureTester {

public static void main(String[] argvs) {
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println("enter cf");
try {
//sleep 3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1");
return "done";
});

try {
//试图在2秒内得到结果,否则算超时
String result = cf1.get(2, TimeUnit.SECONDS);
System.out.println("result is : " + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("超时未得到结果");
e.printStackTrace();
}
}
}

遇到的问题

使用 SpringMVC + Spring Security 做的网页,前端嵌套在 iframe/frame 中内容不显示,一片空白。

打开浏览器的网页检查器,会在主控台发现错误信息:

Refused to display ‘http://x.x.x.x/xx/’ in a frame because it set ‘X-Frame-Options’ to ‘DENY’.

查看 Response Header 会发现 spring security 加了一个header

X-Frame-Options: Deny

解决方法

通过在 Spring Security 中设置
httpSecurity.headers().frameOptions().disable()
禁止调这个头信息即可。

使用正则表达式,从markdown文件中提取链接显示的字符和链接到的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String content = "这是段.md文件的内容。其中[这是一个链接](./html/README.md),还有其他内容,这里又有一个[another link](another.md),这次链接到了another.md。";

//括号用于分组;需要匹配括号则用\c
//[^\]] 表示任意不是']'的字符,后面的*表示任意多个
Pattern p = Pattern.compile("\\[([^\\]]*)\\]\\(([^\\)]*)\\)", Pattern.CASE_INSENSITIVE);

Matcher matcher = p.matcher(content);
while(matcher != null && matcher.find()) {
String link = matcher.group();
for(int i=0; i<= matcher.groupCount(); i++){
//得到的分组,第0个是整个字串,例如第一次发现时打印:[这是一个链接](./html/README.md)
//第一个是正则表达式中第一个括号匹配到的内容,例如第一次发现时打印:这是一个链接
System.out.println(matcher.group(i));
}
System.out.println();
}

运行结果:

1
2
3
4
5
6
7
[这是一个链接](./html/README.md)
这是一个链接
./html/README.md

[another link](another.md)
another link
another.md