GeXiangDong

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

0%

动态继承一个类/抽象类(不是接口),(动态实现接口参照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

使用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版本是否支持对应的变量。