Java注解方式之防止重復請求
自定義注解方式防止前端同一時間多次重復提交
一、 前情提要
有這樣一個業(yè)務,上課的時候老師給表現(xiàn)好的學生送小花花,
每節(jié)課都能統(tǒng)計出某個學生收到的花的總數(shù)。
按照產(chǎn)品需求,前端點擊送花按鈕后30秒內(nèi)是不能再次送花的(信任的基礎)
(上課老師送花行為都進行統(tǒng)計了,可見互聯(lián)網(wǎng)是多么可怕)
二、技術設計
2.1 庫表設計
CREATE TABLE `t_student_flower` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵(自增)', `classroom_id` bigint(20) NOT NULL COMMENT '每堂課的唯一標識', `student_id` bigint(20) NOT NULL COMMENT '學生唯一標識', `flower_num` bigint(20) NOT NULL DEFAULT '0' COMMENT '學生收到的花數(shù)量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.2 業(yè)務邏輯

業(yè)務邏輯很簡單,針對某一堂課的某一個學生,老師第一次送花就新增一條記錄,之后老師給這個學生送花就在原有的記錄基礎上增加送花數(shù)量即可。
如果前端能保證一堂課,一個學生,30秒內(nèi)只能送一次花,這樣設計能99.9999%的保證業(yè)務沒問題
2.3 代碼編寫
至于創(chuàng)建SpringBoot項目,連接Mybatis 準備在Mybatis篇章寫,這里主要點不是這些。
重要是業(yè)務邏輯
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>student_flower</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>student_flower</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok 一款還不錯的副主編程工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<!--測試使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
# 服務端口配置
port: 8888
spring:
# 數(shù)據(jù)源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
# mapper掃描路徑
mapper-locations: classpath:mapper/*.xml
# 實體類別名映射包路徑
type-aliases-package: com.example.student_flower.entity
configuration:
# 開啟駝峰命名
map-underscore-to-camel-case: true
StudentFlowerController
package com.example.student_flower.controller;
import com.example.student_flower.service.StudentFlowerService;
import com.sun.istack.internal.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 10:35
*/
@RestController
public class StudentFlowerController {
@Autowired
StudentFlowerService studentFlowerService;
/**
*
* @param classroomId 教師ID
* @param studentId 學生ID
*/
@GetMapping(value = "/test/sendflower/{classroomId}/{studentId}")
public void sendFlower(@NotNull @PathVariable("classroomId") Long classroomId , @NotNull @PathVariable("studentId") Long studentId){
studentFlowerService.SendFlower(classroomId,studentId);
}
}
StudentFlowerService
package com.example.student_flower.service;
import com.example.student_flower.dao.TStudentFlowerMapper;
import com.example.student_flower.entity.TStudentFlower;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 10:38
*/
@Service
public class StudentFlowerService {
@Autowired
TStudentFlowerMapper mapper;
public void SendFlower(Long classroomId, Long studentId){
TStudentFlower tStudentFlower = mapper.selectByClassroomIdAndStudentId(classroomId, studentId);
// 第一次送花 沒有記錄 新增
if (tStudentFlower == null) {
TStudentFlower tsf = new TStudentFlower();
tsf.setClassroomId(classroomId);
tsf.setStudentId(studentId);
tsf.setFlowerNum(1);
mapper.insert(tsf);
} else {
// 已經(jīng)送過花了 原來數(shù)量上+1
tStudentFlower.setFlowerNum(tStudentFlower.getFlowerNum() + 1);
mapper.update(tStudentFlower);
}
}
}
TStudentFlowerMapper
package com.example.student_flower.dao;
import com.example.student_flower.entity.TStudentFlower;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 10:14
*/
@Mapper
public interface TStudentFlowerMapper {
// 插入
void insert(TStudentFlower tStudentFlower);
// 更新
void update(TStudentFlower tStudentFlower);
// 查詢
TStudentFlower selectByClassroomIdAndStudentId(
@Param("classroomId") Long classroomId,
@Param("studentId") Long studentId);
}
TStudentFlowerMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.student_flower.dao.TStudentFlowerMapper">
<!--新增-->
<insert id="insert" parameterType="TStudentFlower">
INSERT INTO t_student_flower (classroom_id,student_id,flower_num)
VALUES (#{classroomId},#{studentId},#{flowerNum})
</insert>
<!--更新-->
<update id="update" parameterType="TStudentFlower">
UPDATE t_student_flower
SET flower_num = #{flowerNum}
WHERE id=#{id};
</update>
<select id="selectByClassroomIdAndStudentId"
resultType="TStudentFlower">
select * from t_student_flower
where classroom_id = #{classroomId} and student_id = #{studentId}
</select>
</mapper>
2.4 測試
瀏覽器直接訪問:
http://127.0.0.1:8888/test/sendflower/1/1
就會給classroomId = 1 ,studentId = 1 的學生送一朵花
2.5 問題所在
一切看似沒有問題,因為請求頻率還沒有達到可以出錯的速度。
我們寫一個測試用了來模擬前端不可信任的時候(由于某種原因他們送花事件綁定了多次沒有解綁,也就是同一時間發(fā)送多次送花請求)
package com.example.student_flower;
import com.example.student_flower.service.StudentFlowerService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class StudentFlowerApplicationTests {
@Autowired
StudentFlowerService service;
@Test
void sendFlower() throws InterruptedException {
final Long classroomId = 2L;
final Long studengId = 102L;
Thread thread1 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread1執(zhí)行完了");
});
Thread thread2 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread2執(zhí)行完了");
});
Thread thread3 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread3執(zhí)行完了");
});
thread1.start();
thread2.start();
thread3.start();
// 睡會兒 等三個線程跑完 很low? 做測試湊活用吧
Thread.sleep(TimeUnit.SECONDS.toMillis(20));
}
}
執(zhí)行完看一下數(shù)據(jù)庫結果:

這肯定是有問題的 多三條要出問題的,要扣錢績效的
三、解決方案
解決方案有很多,我今天介紹一種自定義注解的方式(其實就是用了分布redis鎖)
方案看似很簡單:

自定義注解MyAnotation
package com.example.student_flower.common.anotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程 分享一個生活在互聯(lián)網(wǎng)底層做著增刪改查的碼農(nóng)的感悟與學習
*
* 關于自定義注解 后邊有機會專門寫一寫 先會用
* @create 2021-09-11 15:26
*/
@Target({ElementType.METHOD}) // 方法上使用的注解
@Retention(RetentionPolicy.RUNTIME) // 運行時通過反射訪問
public @interface MyAnotation {
/**
* 獲取鎖時默認等待多久
*/
int waitTime() default 3;
/**
* 鎖過期時間
*/
int expireTime() default 20;
/**
* 鎖key值
*/
String redisKey() default "";
/**
* 鎖key后拼接的動態(tài)參數(shù)的值
*/
String[] params() default {};
}
自定義切面處理邏輯,進行放重復提交校驗MyAspect
package com.example.student_flower.common.aspect;
import com.example.student_flower.common.anotation.MyAnotation;
import com.example.student_flower.util.HttpContextUtils;
import com.example.student_flower.util.SpelUtil;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
*
* 關于spring面向切面的知識 等以后文章有機會我寫一寫(自己也不太熟 暫時會用)
*
* @create 2021-09-11 15:29
*/
@Slf4j
@Aspect
@Component
public class MyAspect {
@Autowired
RedissonClient redissonClient;
// 這個是那些方法需要被切 -- 被標記注解MyAnotation的方法要被切
@Pointcut("@annotation(com.example.student_flower.common.anotation.MyAnotation)")
public void whichMethodAspect() {
}
/**
* 切面 執(zhí)行業(yè)務邏輯 在實際業(yè)務方法執(zhí)行前 后 都可以進行一些額外的操作
* 切面的好處就是對你不知不覺
*/
@Around("whichMethodAspect()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 獲取注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyAnotation myAnotation = method.getAnnotation(MyAnotation.class);
// 2. 鎖等待時間
int waitTime = myAnotation.waitTime();
// 2. 鎖超時時間 怕萬一finally沒有被執(zhí)行到的時候 多長時間自動釋放鎖(基本不會不執(zhí)行finnaly 除非那個點機器down了)
final int lockSeconds = myAnotation.expireTime();
// 3. 特殊業(yè)務自定義key
String key = myAnotation.redisKey();
// 自定義redisKey是否使用參數(shù)
String[] params = myAnotation.params();
// 4.獲取HttpServletRequest
HttpServletRequest request = HttpContextUtils.getRequest();
if (request == null) {
throw new Exception("錯誤的請求 request為null");
}
assert request != null;
// 5. 組合redis鎖key
// 5.1 如果沒有自定義 用默認的 url+token
if (StringUtils.isBlank(key) && (params == null || params.length == 0)) {
// 這里怎么獲取token 主要看自己項目用的什么框架 token在哪個位置存儲著
String token = request.getHeader("Authorization");
String requestURI = request.getRequestURI();
key = requestURI+token;
} else {
// 5.2 自定義key
key = SpelUtil.generateKeyBySpEL(key, params, joinPoint);
}
// 6. 獲取key
// 獲取鎖 獲取不到最多等waitTime秒 lockSeconds秒后自動釋放鎖
// 每個項目組應該會有自己的redisUtil的封裝 我這里就用最簡單的方式
// 怎么使用鎖不是重點 重點是這個思想
RLock lock = redissonClient.getLock(key);
log.info("tryLock key = {}", key);
boolean b = lock.tryLock(waitTime, lockSeconds, TimeUnit.SECONDS);
// 獲取鎖成功
if (b) {
try {
log.info("tryLock success, key = {}", key);
// 7. 執(zhí)行業(yè)務代碼 返回結果
return joinPoint.proceed();
} finally {
lock.unlock();
}
} else {
// 獲取鎖失敗
log.info("tryLock fail, key = {}", key);
throw new Exception("請求頻繁,請稍后重試");
}
}
}
Redisson配置RedissonConfig
package com.example.student_flower;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 16:31
*/
public class RedissonConfig {
// 這里就簡單設置 真實項目中會做到配置文件或配置中心
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
獲取request對象HttpContextUtils
package com.example.student_flower.util;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 16:17
*
* 獲取springboot環(huán)境中的request/response對象
*/
public class HttpContextUtils {
// 獲取request
public static HttpServletRequest getRequest(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
return request;
}
// 獲取response
public static HttpServletResponse getResponse(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = servletRequestAttributes.getResponse();
return response;
}
}
El表達式解析 SpelUtil
package com.example.student_flower.util;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 15:35
*/
/**
* EL表達式解析
*/
public class SpelUtil {
/**
* 用于SpEL表達式解析.
*/
private static SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于獲取方法參數(shù)定義名字.
*/
private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 解析表達式
*/
public static String generateKeyBySpEL(String key, String[] params, ProceedingJoinPoint joinPoint) {
StringBuilder spELString = new StringBuilder();
if (params != null && params.length > 0) {
spELString.append("'" + key + "'");
for (int i = 0; i < params.length; i++) {
spELString.append("+#" + params[i]);
}
} else {
return key;
}
// 通過joinPoint獲取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer獲取方法形參名數(shù)組
String[] paramNames = nameDiscoverer.getParameterNames(method);
// 解析過后的Spring表達式對象
Expression expression = parser.parseExpression(spELString.toString());
// spring的表達式上下文對象
EvaluationContext context = new StandardEvaluationContext();
// 通過joinPoint獲取被注解方法的形參
Object[] args = joinPoint.getArgs();
// 給上下文賦值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
return expression.getValue(context).toString();
}
}
controller使用注解:
package com.example.student_flower.controller;
import com.example.student_flower.common.anotation.MyAnotation;
import com.example.student_flower.service.StudentFlowerService;
import com.sun.istack.internal.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 發(fā)現(xiàn)更多精彩 關注公眾號:木子的晝夜編程
* 一個生活在互聯(lián)網(wǎng)底層,做著增刪改查的碼農(nóng),不諳世事的造作
* @create 2021-09-11 10:35
*/
@RestController
public class StudentFlowerController {
@Autowired
StudentFlowerService studentFlowerService;
/**
*
* @param classroomId 教師ID
* @param studentId 學生ID
*/
@MyAnotation(redisKey = "/test/sendflower", params = {"classroomId", "studentId"})
@GetMapping(value = "/test/sendflower/{classroomId}/{studentId}")
public void sendFlower(@NotNull @PathVariable("classroomId") Long classroomId , @NotNull @PathVariable("studentId") Long studentId){
studentFlowerService.SendFlower(classroomId,studentId);
}
}
測試類(這里用了MockMvc直接測試controller)
package com.example.student_flower;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import java.util.concurrent.TimeUnit;
@SpringBootTest
@AutoConfigureMockMvc
class StudentFlowerTests {
@Autowired
protected MockMvc mockMvc;
@Test
void sendFlower() throws Exception {
final Long classroomId = 7L;
final Long studengId = 102L;
Thread thread1 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});
Thread thread3 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread3.start();
// 睡會兒 等三個線程跑完 很low? 做測試湊活用吧
Thread.sleep(TimeUnit.SECONDS.toMillis(20));
}
}
去掉controller注解測試 會插入多條,加上MyAnotation注解只會生成一條
四 、嘮嘮
4.1 項目
主要用到了自定義注解、RedissonClient的redis鎖、AOP等知識
可能么有寫過這種場景代碼的人會覺得比較亂:木有關系全部代碼已經(jīng)提交到github上了,
地址:https://github.com/githubforliming/student_flower

4.2 redis服務
貼心的我把redis的windows免安裝包都放到項目里了
test/java/soft 解壓 雙擊redis-server.exe 即可運行
默認沒密碼

4.3 其他問題
支持參數(shù)是對象的自定義key
@MyAnotation(redisKey = "/test/sendflower", params = {"p.id"})
@PostMapping(value = "/test/sendflower02")
public void sendFlower(@RequestBody Person p){
// xxx
}
到此這篇關于Java注解方式之防止重復請求的文章就介紹到這了,更多相關Java 注解方式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java注解的Retention和RetentionPolicy實例分析
這篇文章主要介紹了Java注解的Retention和RetentionPolicy,結合實例形式分析了Java注解Retention和RetentionPolicy的基本功能及使用方法,需要的朋友可以參考下2019-09-09
java集合Collection實現(xiàn)類解析ArrayList?LinkedList及Vector
這篇文章主要為大家介紹了java集合Collection實現(xiàn)類解析ArrayList?LinkedList及Vector,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-03-03
jxls2.4.5如何動態(tài)導出excel表頭與數(shù)據(jù)
這篇文章主要介紹了jxls2.4.5如何動態(tài)導出excel表頭與數(shù)據(jù)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
無感NullPointerException的值相等判斷方法
當我們需要去判斷一個?入?yún)?查庫?返回的開關變量(通常是個Integer類型的)時,常常會寫如下的if-else判斷語句。但又會為在生產(chǎn)環(huán)境看到的「NullPointerException」感到困擾,遇到這個問題如何處理呢,下面小編通過本文給大家詳細講解,需要的朋友參考下吧2023-02-02

