GeXiangDong

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

0%

通过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

使用NGINX做双向认证的网站,访问时需要客户端安装有证书才可继续访问。可用于仅限特定用户群的网站,例如企业内部的业务管理系统,可提高系统安全性,阻止未授权用户访问。

在下述方法中,客户端证书采用自制(可节约成本),服务器端证书可自制也可购买,两者不必同一个CA,建议购买(有免费的域名证书)因为自制的浏览器会出现不信任标志。

自制证书使用openssl工具,使用前需要先安装.

制作证书

CA证书

1
2
3
4
5
#制作CA私钥
openssl genrsa -out ca.key 2048

#制作CA根证书(包含公钥),会要求填写信息
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

服务器证书

如果使用购买的服务器证书,可忽略此部分

1
2
3
4
5
6
7
8
9
10
#制作服务器端私钥:
openssl genrsa -out server.key 2048
#服务器端公钥
openssl rsa -in server.key -out server.public.key

#生成CSR(证书签发请求)
openssl req -new -key server.public.key -out server.csr

#用CA私钥签发证书
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt

客户端浏览器证书

1
2
3
4
5
6
7
8
9
10
11
12
13
#制作客户端私钥
openssl genrsa -out client.key 2048
#客户端公钥
openssl rsa -in client.key -out client.public.key

#生成CSR(签发请求)
openssl req -new -key client.public.key -out client.csr

#用CA私钥签发证书
openssl x509 -req -sha256 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -out client.crt

#转成pfx格式
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.pfx

生成CSR环节会要求输入一些信息,如下:

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:SH
Locality Name (eg, city) []:SH
Organization Name (eg, company) [Internet Widgits Pty Ltd]:公司名(英文字母)
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:这里要输入你的主机名域名
Email Address []:someone@yourcompany.com

下面这段需要输入的两项保留为空(直接按回车)即可。

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

更改nginx配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 443;
server_name admin.devmgr.cn;
ssl on;
# 服务器端证书配置,具体参数请根据证书发行方提供的帮助配置
ssl_certificate /etc/nginx/certs/1_admin.devmgr.cn_bundle.crt;
ssl_certificate_key /etc/nginx/certs/2_admin.devmgr.cn.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;

# 下面是配置客户端认证,需要配置使用的CA证书
ssl_client_certificate /etc/nginx/certs/ca.crt;
ssl_verify_client on;

root /var/www/admin;
}

在后端程序内获取客户端证书信息

NGINX会把客户端证书做成内置变量,可通过proxy_set_header传递给应用,在应用内获取request header即可得到证书对应的信息。

1
2
3
4
5
6
7
8
location /webapp {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 把客户端证书的主题信息作为request header (Client-Cert)传递给应用
proxy_set_header Client-Cert $ssl_client_s_dn;
}

除了 $ssl_client_s_dn 外,NGINX还提供很多内置变量对应证书的其他部分内容,想起请参考 nginx的ngx_http_ssl_module 模块文档。使用时需要注意nginx版本是否支持对应的变量。

websocket协议的握手过程。
首先客户端和服务器端建立的是http 1.1的连接,客户端发送请求,请求的资源地址是websocket的地址,但在连接中增加几个请求头:

Connection: upgrade
Upgrade: Websocket
Sec-WebSocket-Key: base64格式的随机数
Sec-Websocket-Version: 13 (13是版本号)

服务器收到这种连接后,如果支持升级到websocket,会发出类似如下的回应:

HTTP/1.1 101 Switching Protocols
Server: Apache-Coyote/1.1
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: OfS0wDaT5NoxF2gqm7Zj2YtetzM=
Date: Tue, 04 Sep 2018 05:46:48 GMT

之后websocket连接就建好了,双方可以通讯了,下面有段建立连接的代码,可以参考。解析websocket帧部分没有写,只是当成字符串打印出来了,因为帧中包含一些二进制的控制信息,所以打印内容会有乱码(即使消息是纯文本也有乱码,因为有部分帧的头部信息也被当成字符串打印了)

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
import java.net.*;
import java.io.*;

public class WebsocketClient {

public static void main(String[] args) throws Exception {

String hostname = "localhost";
int port = 8080;

try (Socket socket = new Socket(hostname, port)) {

OutputStream output = socket.getOutputStream();
PrintWriter writer = new PrintWriter(output, true);

writer.println("GET /chat HTTP/1.1");
writer.println("Host: " + hostname + ":" + port);
// 下面两行是告诉服务器端,此次连接的使用的http协议期望升级到websocket协议,如果服务端同意升级会返回
// Connection: upgrade upgrade: websocket表示已经升级了,而且状态吗是101 switching protocols
writer.println("Connection: upgrade");
writer.println("Upgrade: WebSocket");
// 下面这2行是必须的,sec-websocket-key是base64格式的一个随机数,由客户端生成
// sec-websocket-version是指明客户端的websocket版本
writer.println("Sec-WebSocket-Key: "AQIDBAUGBwgJCgsMDQ4PEC=" =");
writer.println("Sec-WebSocket-Version: 13");
writer.println("User-Agent: MSIE");
writer.println("Origin: http://localhost:8080/");
writer.println();

InputStream input = socket.getInputStream();

BufferedReader reader = new BufferedReader(new InputStreamReader(input));

System.out.println("request sent...");
int i=0;
while (i < 10) {
if (reader.ready()){
// 在读websocket发送过来的消息时,这里没有解码消息帧,都当字符串了,消息的数据报文格式前面有些事数据包控制的bit,所以打印时有乱码
char[] bin = new char[2048];
int len = reader.read(bin, 0, bin.length);
char[] buf = new char[len];
System.arraycopy(bin, 0, buf, 0, len);
System.out.println(new String(buf));
i++;
}
}
// 连接好了后可以发送数据,发送的数据报格式是 Base Framing Protocol, ABNF RFC5234
writer.close();
output.close();
socket.close();
} catch (UnknownHostException ex) {
System.out.println("Server not found: " + ex.getMessage());
} catch (IOException ex) {
System.out.println("I/O error: " + ex.getMessage());
}
}
}