Java 开发常用代码块之一

网络编程

获取 IP 地址

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 cn.hutool.core.util.StrUtil;
import javax.servlet.http.HttpServletRequest;

/**
* IP地址工具
*/
public class IPUtils {

/**
* 获取IP地址
* 如果使用Nginx等反向代理软件,不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StrUtil.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StrUtil.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
System.out.println("IPUtils ERROR " + e.getLocalizedMessage());
}
return ip;
}

}

正则表达式解析 URL

通过正则表达式,获取 URL 中的协议、域名、端口、URI。

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
import cn.hutool.core.util.StrUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HttpUtil {

public static final String HTTP_PROTOCOL = "http://";

public static final String HTTPS_PROTOCOL = "https://";

/**
* 解析URL(包括协议、域名、端口、URI)
*
* @param url
* @return
*/
public static Map<String, String> parseUrl(String url) {
Map<String, String> map = new HashMap(4);
try {
Pattern pattern = Pattern.compile("(https?://)([^:^/]*)(:\\d*)?(.*)?");
Matcher matcher = pattern.matcher(url);
boolean findResult = matcher.find();
if (!findResult) {
return map;
}

String protocol = matcher.group(1);
String domain = matcher.group(2);
String port = matcher.group(3);
String uri = matcher.group(4);

if (StrUtil.isBlank(port)) {
if (HTTP_PROTOCOL.equals(protocol)) {
port = "80";
} else if (HTTPS_PROTOCOL.equals(protocol)) {
port = "443";
} else {
port = "unknown";
}
} else {
port = port.replace(":", "");
}

map.put("protocol", protocol);
map.put("domain", domain);
map.put("port", port);
map.put("uri", uri);
} catch (Exception e) {
e.printStackTrace();
}
return map;
}

}

多线程编程

正确关闭线程池

最终参考 JDK 官方文档 的示例代码,正确(优雅)关闭线程池的写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void shutdownAndAwaitTermination(ExecutorService threadPool) {
if (threadPool != null && !threadPool.isShutdown()) {
// 平滑关闭线程池
threadPool.shutdown();
try {
// 阻塞当前线程 60 秒,等候待执行的任务和正在执行的任务执行完成
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
// 等待超时后,立刻关闭线程池
threadPool.shutdownNow();

// 再次阻塞当前线程 60 秒,然后检查线程池是否已经关闭
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("Thread pool did not terminate");
}
}
} catch (Exception e) {
// 捕获到异常后,立刻关闭线程池
threadPool.shutdownNow();
// 捕获到异常后,中断当前线程
Thread.currentThread().interrupt();
}
}
}

提示

更多关于线程池如何正确关闭的内容,请阅读 这里 的教程。

线程池处理异常

由于线程池提交任务有多种方式,这也导致了对异常的处理方式不一样。为了统一处理线程池的异常,不能使用 Excutors 类来创建线程池,而是使用 ThreadPoolExecutor 类手动创建线程池,同时需要重写 ThreadPoolExecutor 类的 afterExecute() 方法来统一处理异常。

提示

更多关于线程池如何处理异常的内容,请阅读 这里 的教程。

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
@Slf4j
public class ThreadPoolExceptionDemo {

/**
* 手动创建线程池,并统一处理异常的写法
*/
public ExecutorService createThreadPool() {
// 手动创建线程池
ExecutorService threadPool = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors() * 2,
30L,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)) {

/**
* 重写 afterExecute() 方法,实现统一异常处理
*/
@Override
protected void afterExecute(Runnable runnable, Throwable throwable) {
// 判断是否调用 execute() 方法提交任务
if (throwable != null) {
log.error(throwable.getMessage(), throwable);
}
// 判断是否调用 submit() 方法提交任务
if (throwable == null && runnable instanceof Future<?>) {
try {
Future<?> future = (Future<?>) runnable;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
throwable = ce;
log.error(ce.getMessage(), ce);
} catch (ExecutionException ee) {
throwable = ee.getCause();
log.error(ee.getMessage(), ee);
} catch (InterruptedException ie) {
log.error(ie.getMessage(), ie);
Thread.currentThread().interrupt();
}
}
}
};
return threadPool;
}

}

代码使用案例

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
public class ThreadPoolDemo {

public static void main(String[] args) {
// 手动创建线程池
ExecutorService threadPool = createThreadPool();
try {
// 调用 submit() 方法提交任务,默认会吞掉异常,改写后可以抛出异常了
threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "进入 submit() 方法 ---start");
for (int i = 1; i <= 4; i++) {
if (i == 3) {
int age = 10 / 0;
}
System.out.println("come in execute: " + i);
}
System.out.println(Thread.currentThread().getName() + "\t" + "进入 submit() 方法 ---end");
});
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}

......

}

程序执行的输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pool-1-thread-1	进入 submit() 方法 ---start
come in execute: 1
come in execute: 2
16:11:12.103 [pool-1-thread-1] ERROR com.java.interview.pool.ThreadPoolDemo - java.lang.ArithmeticException: / by zero
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.java.interview.pool.ThreadPoolDemo$1.afterExecute(ThreadPoolDemo.java:113)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1129)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.ArithmeticException: / by zero
at com.java.interview.pool.ThreadPoolDemo.lambda$test02$1(ThreadPoolDemo.java:55)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
... 2 common frames omitted