Java相关代码分析

分析Java相关的代码片段、细节等。

1. Java,Spring,MySQL中的时间问题

1.1 Java

1.1.1 Java8之前

  • java.util.Date
    • Date如果不格式化,打印出的日期可读性差
    • 使用SimpleDateFormat对时间进行格式化,但SimpleDateFormat是线程不安全的
  • 案例
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
* 关于java.util.Date、java.sql.Timestamp和String之间的互相转换的方法
*/
public class DateUtil {

/**
* 将String字符串转换为java.util.Date格式日期
*
* @param strDate
* 表示日期的字符串
* @param dateFormat
* 传入字符串的日期表示格式(如:"yyyy-MM-dd HH:mm:ss")
* @return java.util.Date类型日期对象(如果转换失败则返回null)
*/
public static java.util.Date strToUtilDate(String strDate, String dateFormat) {
SimpleDateFormat sf = new SimpleDateFormat(dateFormat);
java.util.Date date = null;
try {
date = sf.parse(strDate);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}

/**
* 将String字符串转换为java.sql.Timestamp格式日期,用于数据库保存
*
* @param strDate
* 表示日期的字符串
* @param dateFormat
* 传入字符串的日期表示格式(如:"yyyy-MM-dd HH:mm:ss")
* @return java.sql.Timestamp类型日期对象(如果转换失败则返回null)
*/
public static java.sql.Timestamp strToSqlDate(String strDate, String dateFormat) {
SimpleDateFormat sf = new SimpleDateFormat(dateFormat);
java.util.Date date = null;
try {
date = sf.parse(strDate);
} catch (ParseException e) {
e.printStackTrace();
}
java.sql.Timestamp dateSQL = new java.sql.Timestamp(date.getTime());
return dateSQL;
}

/**
* 将java.util.Date对象转化为String字符串
*
* @param date
* 要格式的java.util.Date对象
* @param strFormat
* 输出的String字符串格式的限定(如:"yyyy-MM-dd HH:mm:ss")
* @return 表示日期的字符串
*/
public static String dateToStr(java.util.Date date, String strFormat) {
SimpleDateFormat sf = new SimpleDateFormat(strFormat);
String str = sf.format(date);
return str;
}

/**
* 将java.sql.Timestamp对象转化为String字符串
*
* @param time
* 要格式的java.sql.Timestamp对象
* @param strFormat
* 输出的String字符串格式的限定(如:"yyyy-MM-dd HH:mm:ss")
* @return 表示日期的字符串
*/
public static String dateToStr(java.sql.Timestamp time, String strFormat) {
DateFormat df = new SimpleDateFormat(strFormat);
String str = df.format(time);
return str;
}

/**
* 将java.sql.Timestamp对象转化为java.util.Date对象
*
* @param time
* 要转化的java.sql.Timestamp对象
* @return 转化后的java.util.Date对象
*/
public static java.util.Date timeToDate(java.sql.Timestamp time) {
return time;
}

/**
* 将java.util.Date对象转化为java.sql.Timestamp对象
*
* @param date
* 要转化的java.util.Date对象
* @return 转化后的java.sql.Timestamp对象
*/
public static java.sql.Timestamp dateToTime(java.util.Date date) {
String strDate = dateToStr(date, "yyyy-MM-dd HH:mm:ss SSS");
return strToSqlDate(strDate, "yyyy-MM-dd HH:mm:ss SSS");
}

/**
* 返回表示系统当前时间的java.util.Date对象
* @return 返回表示系统当前时间的java.util.Date对象
*/
public static java.util.Date nowDate(){
return new java.util.Date();
}

/**
* 返回表示系统当前时间的java.sql.Timestamp对象
* @return 返回表示系统当前时间的java.sql.Timestamp对象
*/
public static java.sql.Timestamp nowTime(){
return dateToTime(new java.util.Date());
}
}

1.1.2 Java8及其之后(重点关注表示范围)

  • java.time.LocalDate
Modifier and Type Field and Description
static LocalDate MAX The maximum supported LocalDate, ‘+999999999-12-31’.
static LocalDate MIN The minimum supported LocalDate, ‘-999999999-01-01’.
  • java.time.LocalTime
Modifier and Type Field and Description
static LocalTime MAX The maximum supported LocalTime, ‘23:59:59.999999999’.
static LocalTime MIDNIGHT The time of midnight at the start of the day, ‘00:00’.
static LocalTime MIN The minimum supported LocalTime, ‘00:00’.
static LocalTime NOON The time of noon in the middle of the day, ‘12:00’.
  • java.time.LocalDateTime
Modifier and Type Field and Description
static LocalDateTime MAX The maximum supported LocalDateTime, ‘+999999999-12-31T23:59:59.999999999’.
static LocalDateTime MIN The minimum supported LocalDateTime, ‘-999999999-01-01T00:00:00’.
  • java.time.Instant
Modifier and Type Field and Description
static Instant EPOCH Constant for the 1970-01-01T00:00:00Z epoch instant.
static Instant MAX The maximum supported Instant, ‘1000000000-12-31T23:59:59.999999999Z’.
static Instant MIN The minimum supported Instant, ‘-1000000000-01-01T00:00Z’.
  • 案例
1
//案例

1.1.3 xx天xx小时xx分钟

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DAY_OF_MONTH,1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.MILLISECOND, 0);
System.out.println(calendar.getTimeInMillis());
}

1.2 Spring

1.3 MySQL

1.3.1 支持的数据类型

  • TIME(不常用)(hhh:mm:ss)

MySQL retrieves and displays TIME values in ‘hh:mm:ss’ format (or ‘hhh:mm:ss’ format for large hours values). TIME values may range from ‘-838:59:59’ to ‘838:59:59’.

  • DATE(常用,精度到天,不保存时区信息)

The supported range is '1000-01-01' to '9999-12-31'

  • DATETIME(常用,支持到了微秒级别,不保存时区信息)

The DATETIME type is used for values that contain both date and time parts. MySQL retrieves and displays DATETIME values in 'YYYY-MM-DD hh:mm:ss' format. The supported range is '1000-01-01 00:00:00' to '9999-12-31 23:59:59'.

  • TIMESTAMP(常用,支持到了微秒级别,保存了时区信息)

The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC.

  • 自动更新

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CREATE TABLE t1 (
    ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

    CREATE TABLE t2 (
    dt1 DATETIME ON UPDATE CURRENT_TIMESTAMP, -- default NULL
    dt2 DATETIME NOT NULL ON UPDATE CURRENT_TIMESTAMP -- default 0
    );
  • YEAR(不常用)(1901 to 2155, or 0000)

1.3.2 Java包

  • java.sql.Date
  • java.sql.Timestamp(和MySQL中的范围有区别)
Constructor and Description
Timestamp(int year, int month, int date,int hour,int minute,int second,int nano) Deprecated. instead use the constructor Timestamp(long millis)
Timestamp(long time) Constructs a Timestamp object using a milliseconds time value.

1.4 参考资料

1.5 问题

  • 精度(尤其是在使用MySQL时候作为判断条件时)
  • 线程安全性
  • 时区

2. 使用mongoDB存储SpringBoot日志

2.1 统一日志框架

在系统开发的过程中,会使用到不同的技术,不同的技术会使用不同的日志框架.为了更好地处理日志信息,首先需要将日志框架进行统一.

为了将其他的日志框架装换为slf4j,只需要在pom.xml进行如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--统一日志框架: Slf4j+logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>

2.2 Spring Boot连接mongoDB

2.2.1 mongoDB安装和使用

  • 安装—-使用docker

  • 新建数据库

    • use admin

2.2.2 mongoDB的连接

mongoDB的连接和其他数据库的连接存在一定的差异,主要是体现在mongoDB为每一个数据库设置了用户和密码,在建立建立连接通常采用一下方式.

1
2
//spring.data.mongodb.uri=mongodb://用户名:密码t@ip:27017/数据库
MongoClientURI mongoClientURI=new MongoClientURI(mongoUrl);

2.3 将日志信息写入mogoDB

2.3.1 重写logback.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- use Spring default values -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<appender name="MONGODB" class="com.mao.api.util.MongoAppender">
<collectionName>logging</collectionName>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="MONGODB"/>
</root>
</configuration>

2.3.2 定义Template

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
package com.mao.api.core.config;

import com.mongodb.MongoClientURI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;

import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

/**
* @Classname MongoConfig
* @Description TODO
* @Date 19-5-23 下午4:46
* @Created by mao<tianmao818@qq.com>
*/
//@Configuration
public class MongoConfig {
@Value("${spring.data.mongodb.uri}")
private String mongoUrl;

public MongoTemplate mongoTemplate() {
MongoClientURI mongoClientURI=new MongoClientURI(mongoUrl);
MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClientURI);
DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
converter.setTypeMapper(new DefaultMongoTypeMapper(null));

return new MongoTemplate(mongoDbFactory, converter);
}
}

2.3.3 定义日志Appender

(重写append,start,stop,setApplicationContext)

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
59
60
61
62
63
64
65
66
67
68
69
70
package com.mao.api.util;/**
* @Classname MongoAppender
* @Description TODO
* @Date 19-5-23 下午4:49
* @Created by mao<tianmao818@qq.com>
*/
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

@Component
public class MongoAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements ApplicationContextAware{
private static MongoTemplate mongoTemplate;
private String collectionName;

@Override
protected void append(ILoggingEvent event) {
if (!started) {
return;
}
//日志存储内容
LogEntity log = new LogEntity();
log.threadName = event.getThreadName();
log.level = event.getLevel().levelStr;
log.formattedMessage = event.getFormattedMessage();
log.loggerName = event.getLoggerName();
log.timestamp = event.getTimeStamp();
//使用模板保存日志
mongoTemplate.save(log, collectionName);
}

@Override
public void start() {
super.start();
}

@Override
public void stop() {
super.stop();
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (applicationContext.getAutowireCapableBeanFactory().getBean(MongoTemplate.class) != null) {
mongoTemplate = applicationContext.getAutowireCapableBeanFactory().getBean(MongoTemplate.class);
LoggerFactory.getLogger(this.getClass()).info("[ApplicationContext] Autowire MongoTemplate, MongoAppender is ready.");
}
}

private class LogEntity {
String threadName;
String level;
String formattedMessage;
String loggerName;
Long timestamp;
}

public String getCollectionName() {
return collectionName;
}

public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}

2.4 效果

2.4.1 查看collections

2.4.2 查看日志细节

3. 使用递归的方法生成菜单

摘抄于:https://www.cnblogs.com/lucky-pin/p/10740037.html

递归生成一个如图的菜单,编写两个类数据模型Menu、和创建树形的MenuTree。通过以下过程实现:

  • 首先从菜单数据中获取所有根节点。

  • 为根节点建立次级子树并拼接上。

  • 递归为子节点建立次级子树并接上,直至为末端节点拼接上空的“树”。

首先,编写数据模型Menu。每条菜单有自己的id、父节点parentId、菜单名称text、菜单还拥有次级菜单children。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.List;

public class Menu {
private String id;
private String parentId;
private String text;
private String url;
private String yxbz;
private List<Menu> children;
public Menu(String id,String parentId,String text,String url,String yxbz) {
this.id=id;
this.parentId=parentId;
this.text=text;
this.url=url;
this.yxbz=yxbz;
}
/*省略get\set*/
}

创建树形结构的类MenuTree。方法getRootNode获取所有根节点,方法builTree将根节点汇总创建树形结构,buildChilTree为节点建立次级树并拼接上当前树,递归调用buildChilTree不断为当前树开枝散叶直至找不到新的子树。完成递归,获取树形结构。

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
import java.util.ArrayList;
import java.util.List;

public class MenuTree {
private List<Menu> menuList = new ArrayList<Menu>();
public MenuTree(List<Menu> menuList) {
this.menuList=menuList;
}

//建立树形结构
public List<Menu> builTree(){
List<Menu> treeMenus =new ArrayList<Menu>();
for(Menu menuNode : getRootNode()) {
menuNode=buildChilTree(menuNode);
treeMenus.add(menuNode);
}
return treeMenus;
}

//递归,建立子树形结构
private Menu buildChilTree(Menu pNode){
List<Menu> chilMenus =new ArrayList<Menu>();
for(Menu menuNode : menuList) {
if(menuNode.getParentId().equals(pNode.getId())) {
chilMenus.add(buildChilTree(menuNode));
}
}
pNode.setChildren(chilMenus);
return pNode;
}

//获取根节点
private List<Menu> getRootNode() {
List<Menu> rootMenuLists =new ArrayList<Menu>();
for(Menu menuNode : menuList) {
if(menuNode.getParentId().equals("0")) {
rootMenuLists.add(menuNode);
}
}
return rootMenuLists;
}
}

 最后,插入一些数据试试效果。得到的json就可以生成图一菜单了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;

public class Hello {
public static void main(String []args) {
List<Menu> menuList= new ArrayList<Menu>();
/*插入一些数据*/
menuList.add(new Menu("GN001D000","0","系统管理","/admin","Y"));
menuList.add(new Menu("GN001D100","GN001D000","权限管理","/admin","Y"));
menuList.add(new Menu("GN001D110","GN001D100","密码修改","/admin","Y"));
menuList.add(new Menu("GN001D120","GN001D100","新加用户","/admin","Y"));
menuList.add(new Menu("GN001D200","GN001D000","系统监控","/admin","Y"));
menuList.add(new Menu("GN001D210","GN001D200","在线用户","/admin","Y"));
menuList.add(new Menu("GN002D000","0","订阅区","/admin","Y"));
menuList.add(new Menu("GN003D000","0","未知领域","/admin","Y"));
/*让我们创建树*/
MenuTree menuTree =new MenuTree(menuList);
menuList=menuTree.builTree();
/*转为json看看效果*/
String jsonOutput= JSON.toJSONString(menuList);
System.out.println(jsonOutput);
}
}

4. 重试

4.1 Guava Retrying框架重试机制的使用

API远程接口在调用时会偶发网络超时、网络异常,导致调用失败,这时候某些特殊需求可能需要使用重试机制,当发生网络等异常时重新再发起调用请求。Guava Retryer能完美的解决这一需求。

4.1.1 引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>

4.1.2 根据调用返回接口判断是否需要重试

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

import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicates;
/**
* @description: GitHub Retryer框架重试机制的使用
* @author ityuan.com
* @date 2019年6月26日 上午11:07:38
*/
public class RetryTester {

public static void main(String[] args) throws ExecutionException, RetryException {

Retryer<Long> retryer = RetryerBuilder.<Long>newBuilder()
// 返回false也需要重试
.retryIfResult(Predicates.equalTo(1L))
// 重调策略
.withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
// 尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();

retryer.call(new Callable<Long>() {
@Override
public Long call() throws Exception {
System.out.println("返回值是0L,看我能出现几次");
return 1L;
}
}
);

}
}

4.1.3 根据调用发生异常判断是否需要重试

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
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

import com.github.rholder.retry.RetryException;
import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
/**
* @description: GitHub Retryer框架重试机制的使用
* @author ityuan.com
* @date 2019年6月26日 上午11:07:38
*/
public class RetryTester2 {

public static void main(String[] args) throws ExecutionException, RetryException {

Retryer<Long> retryer = RetryerBuilder.<Long>newBuilder()
.retryIfException()
.withStopStrategy(StopStrategies.stopAfterAttempt(2)) // 重试2次后停止
.build();

retryer.call(new Callable<Long>() {
@Override
public Long call() throws Exception {
System.out.println("异常打印,看我能出现几次");
throw new RuntimeException();
}
}
);

}
}

Guava时间重试机制:固定、自增、斐波拉契数组

https://rholder.github.io/guava-retrying/javadoc/2.0.0/com/github/rholder/retry/WaitStrategies.html

4.1.4 添加异常监听

1
.withRetryListener(new MyRetryListener<>())
  • 监听代码
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
import com.github.rholder.retry.Attempt;  
import com.github.rholder.retry.RetryListener;
import java.util.concurrent.ExecutionException;

@SuppressWarnings("hiding")
public class MyRetryListener<Long> implements RetryListener {

@Override
public <Long> void onRetry(Attempt<Long> attempt) {

// 第几次重试,(注意:第一次重试其实是第一次调用)
System.out.print("[retry]time=" + attempt.getAttemptNumber());

// 距离第一次重试的延迟
System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());

// 重试结果: 是异常终止, 还是正常返回
System.out.print(",hasException=" + attempt.hasException());
System.out.print(",hasResult=" + attempt.hasResult());

// 是什么原因导致异常
if (attempt.hasException()) {
System.out.print(",causeBy=" + attempt.getExceptionCause().toString());
} else {
// 正常返回时的结果
System.out.print(",result=" + attempt.getResult());
}
// bad practice: 增加了额外的异常处理代码
try {
Long result = attempt.get();
System.out.print(",rude get=" + result);
} catch (ExecutionException e) {
System.err.println("this attempt produce exception." + e.getCause().toString());
}
System.out.println();
}
}

4.1.5 总结

RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。

  • RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,通过retryIfException 和retryIfResult设置,同时支持多个且能兼容。

  • retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。

  • retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。

  • retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error。

4.2 @Retryable(spring的重试机制)

4.2.1 引入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>

4.2.2 在启动类或者配置类上添加注解@EnableRetry

  • 配置类
1
2
3
@Configuration
@EnableRetry
public class AppConfig { ... }
  • 启动类
1
2
3
@SpringBootApplication
@EnableRetry
public class Application

4.2.3 在需要重试的方法上添加注解@Retryable

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
@Service
public class DemoService {
@Retryable(value= {Exception.class},maxAttempts = 3)
public void call() throws Exception {
System.out.println("do something...");
throw new Exception("RPC调用异常");
}
@Recover
public void recover(RemoteAccessException e) {
System.out.println(e.getMessage());
}
}

@Retryable(maxAttempts = 3, backoff = @Backoff(value = 3000, multiplier = 1.5))
public Customer getCustomer(String customerId) {
if (true) {
JSONArray data = retObj.getJSONArray("data");
if (data != null && !data.isEmpty()) {
return data.toJavaList(Customer.class).get(0);
}
} else {
log.error("异常,{}", customerId);
throw new RuntimeException("获数据失败");
}
return null;
}
  • @Retryable被注解的方法发生异常时会重试

  • @Retryable注解中的参数说明:

    • maxAttempts :最大重试次数,默认为3,如果要设置的重试次数为3,可以不写;
    • value:抛出指定异常才会重试
    • include:和value一样,默认为空,当exclude也为空时,所有异常都重试
    • exclude:指定不处理的异常,默认空,当include也为空时,所有异常都重试
    • backoff:重试等待策略,默认使用@Backoff@Backoff的value默认为1000L,我们设置为2000L。
  • @Backoff重试补偿机制,默认没有

  • @Backoff注解中的参数说明:

    • value:隔多少毫秒后重试,默认为1000L,我们设置为3000L;
    • delay:和value一样,但是默认为0;
    • multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。

5. 线程池

5.1 Spring的线程池ThreadPoolTaskExecutor使用案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class ThreadConfig {
@Bean
public TaskExecutor executorA() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(30);
executor.setAllowCoreThreadTimeOut(true);
executor.setThreadNamePrefix("default_task_executor_thread");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
-------------本文结束感谢您的阅读-------------
我知道是不会有人点的,但万一有人想不开呢?