前言

考完计网和人智基础,好像大二浑浑噩噩就这样过去了,坐了两天火车回家,这几天静下心来把之前的项目给消化一下。

线程变量

有些时候一个线程可能在不同的类方法之间来回调用,调用的时候可能需要重复携带多次相同的变量,这时候我们希望将该变量定义为全局,减少码量的同时项目结构会更清晰,但我们的项目一般是在多线程高并发场景下运行的,这时候线程之间对这个变量的定义可能并不相同,如果我们使用全局变量,线程之间共享这个变量,那我们怎么知道使用的是不是自己定义的变量呢,ThreadLocal给我们提供了一种解决方案。

ThreadLocal是什么

ThreadLocal就是线程变量,为每个线程所拥有,对其他线程保持隔离。

它是怎么实现的呢?

简单来说,你的进程维护了一个Map,这个Map将ThreadLocal作为key存入,其中的value是我们自定义的数据,可以是Integer、String等各种类型,这里只是很简单的描述,详细源码可以看JavaGuide中关于ThreadLocal的介绍

示例代码

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

private static ThreadLocal<String> localVar = new ThreadLocal<String>();

static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
//清除本地内存中的本地变量
localVar.remove();
}
public static void main(String[] args) throws InterruptedException {

new Thread(new Runnable() {
public void run() {
ThreadLocaDemo.localVar.set("local_A");
print("A");
//打印本地变量
System.out.println("after remove : " + localVar.get());

}
},"A").start();

Thread.sleep(1000);

new Thread(new Runnable() {
public void run() {
ThreadLocaDemo.localVar.set("local_B");
print("B");
System.out.println("after remove : " + localVar.get());

}
},"B").start();
}
}

A :local_A
after remove : null
B :local_B
after remove : null

在项目中的使用

介绍一种token技术和ThreadLocal结合使用的场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//通常将ThreadLocal封装起来,使其在项目中具有实际意义,这里是将ThreadLocal作为一个存储id的线程变量
public class BaseContext {

public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

public static void setCurrentId(Long id) {
threadLocal.set(id);
}

public static Long getCurrentId() {
return threadLocal.get();
}

public static void removeCurrentId() {
threadLocal.remove();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//我们可以在jwt校验的时候将用户的token解析后将id存进去
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getAdminTokenName());

//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
//将id存储起来
BaseContext.setCurrentId(empId);
log.info("当前员工id:", empId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 新增员工
* @param employeeDTO
*/
@Override
public void insert(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO, employee);

employee.setUpdateTime(LocalDateTime.now());
employee.setCreateTime(LocalDateTime.now());

employee.setStatus(StatusConstant.ENABLE);

employee.setPassword(DigestUtils.md5DigestAsHex(DEFAULT_PASSWORD.getBytes()));

//这里使用了ThreadLocal中存储的id
employee.setCreateUser(BaseContext.getCurrentId());
employee.setUpdateUser(BaseContext.getCurrentId());

employeeMapper.insert(employee);
}

全局异常处理器

有时候写项目,我们会使用try-catch不断将异常捕获,这也是对异常的一种处理方式,但是用户在使用的时候出现问题,他自己是看不到出现的异常的,我们只是捕获异常却没有进行处理,如果在每一次捕获中都对异常进行一次处理,工作量将变得巨大(面向对象变为面向异常,总之Springboot对此提供了一个很好的全局处理器。SpringBoot中,@ControllerAdvice 即可开启全局异常处理,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用@ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。

原理咱们先不做讨论(纯不会

这是一个简单的处理sql重复字段的处理器,我们希望给用户返回重复提醒

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
/**
* 全局异常处理器,处理项目中抛出的业务异常
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

/**
* 捕获业务异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
//这里可以将捕获的异常转换为字符串,异常的形式是这样的
//Duplicate entry 'zhangsi' for key 'employee.idx_username'
String message = ex.getMessage();
if(message!=null && message.contains("Duplicate entry")){
//这里是对字符串处理,转换为提醒用户的字段
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISETS;
//返回错误信息
return Result.error(msg);
}else{
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
}

参考