一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java注解方式之防止重復請求

Java注解方式之防止重復請求

2021-12-31 11:00grace.free Java教程

這篇文章主要介紹了關于Java注解方式防止重復請求,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

自定義注解方式防止前端同一時間多次重復提交

 

一、 前情提要

有這樣一個業務,上課的時候老師給表現好的學生送小花花,

每節課都能統計出某個學生收到的花的總數。

按照產品需求,前端點擊送花按鈕后30秒內是不能再次送花的(信任的基礎)

(上課老師送花行為都進行統計了,可見互聯網是多么可怕)

 

二、技術設計

 

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 "學生收到的花數量",
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

 

2.2 業務邏輯

Java注解方式之防止重復請求

業務邏輯很簡單,針對某一堂課的某一個學生,老師第一次送花就新增一條記錄,之后老師給這個學生送花就在原有的記錄基礎上增加送花數量即可。

如果前端能保證一堂課,一個學生,30秒內只能送一次花,這樣設計能99.9999%的保證業務沒問題

 

2.3 代碼編寫

至于創建SpringBoot項目,連接Mybatis 準備在Mybatis篇章寫,這里主要點不是這些。

重要是業務邏輯

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:
  # 數據源配置
  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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @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 {
            // 已經送過花了 原來數量上+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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @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 問題所在

一切看似沒有問題,因為請求頻率還沒有達到可以出錯的速度。

我們寫一個測試用了來模擬前端不可信任的時候(由于某種原因他們送花事件綁定了多次沒有解綁,也就是同一時間發送多次送花請求)

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執行完了");
        });
        Thread thread2 = new Thread(() -> {
            service.SendFlower(classroomId, studengId);
            System.out.println("thread2執行完了");
        });
        Thread thread3 = new Thread(() -> {
            service.SendFlower(classroomId, studengId);
            System.out.println("thread3執行完了");
        });
        thread1.start();
        thread2.start();
        thread3.start();
        // 睡會兒 等三個線程跑完 很low? 做測試湊活用吧
        Thread.sleep(TimeUnit.SECONDS.toMillis(20));
    }

}

執行完看一下數據庫結果:

Java注解方式之防止重復請求

這肯定是有問題的 多三條要出問題的,要扣錢績效的

 

三、解決方案

解決方案有很多,我今天介紹一種自定義注解的方式(其實就是用了分布redis鎖)

方案看似很簡單:

Java注解方式之防止重復請求

自定義注解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 發現更多精彩  關注公眾號:木子的晝夜編程  分享一個生活在互聯網底層做著增刪改查的碼農的感悟與學習
 *
 * 關于自定義注解 后邊有機會專門寫一寫 先會用
 * @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后拼接的動態參數的值
     */
    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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 *
 * 關于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() {
    }

    /**
     * 切面 執行業務邏輯 在實際業務方法執行前 后 都可以進行一些額外的操作
     * 切面的好處就是對你不知不覺
     */
    @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沒有被執行到的時候 多長時間自動釋放鎖(基本不會不執行finnaly 除非那個點機器down了)
        final int lockSeconds = myAnotation.expireTime();
        // 3. 特殊業務自定義key
        String key = myAnotation.redisKey();
        // 自定義redisKey是否使用參數
        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. 執行業務代碼 返回結果
                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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @create 2021-09-11 16:17
 *
 * 獲取springboot環境中的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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @create 2021-09-11 15:35
 */

/**
 * EL表達式解析
 */
public class SpelUtil {

    /**
     * 用于SpEL表達式解析.
     */
    private static SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于獲取方法參數定義名字.
     */
    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獲取方法形參名數組
        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 發現更多精彩  關注公眾號:木子的晝夜編程
 * 一個生活在互聯網底層,做著增刪改查的碼農,不諳世事的造作
 * @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等知識

可能么有寫過這種場景代碼的人會覺得比較亂:木有關系全部代碼已經提交到github上了,

地址:https://github.com/githubforliming/student_flower

Java注解方式之防止重復請求

 

4.2 redis服務

貼心的我把redis的windows免安裝包都放到項目里了

test/java/soft 解壓 雙擊redis-server.exe 即可運行

默認沒密碼

Java注解方式之防止重復請求

 

4.3 其他問題

支持參數是對象的自定義key

    @MyAnotation(redisKey = "/test/sendflower", params = {"p.id"})
    @PostMapping(value = "/test/sendflower02")
    public void sendFlower(@RequestBody Person p){
        // xxx
    }

到此這篇關于Java注解方式之防止重復請求的文章就介紹到這了,更多相關Java 注解方式內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/qq_36291682/article/details/120241581

延伸 · 閱讀

精彩推薦
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 国产精品www夜色影视 | 性欧美videosex18嫩 | 爽好舒服快想要免费看 | 99久久综合精品免费 | 日本免费的一级绿象 | 边摸边吃奶边做爽gif动态图 | 99在线播放 | 韩国办公室激情 | 高黄h文各种play | 国产欧美成人不卡视频 | 国产成人yy精品1024在线 | 日剧整部剧护妻狂魔免费观看全集 | 91污污视频| 成人快插 | 无限好资源第一片免费韩国 | 美女扒开屁股让我桶免费 | 国产精品久久毛片蜜月 | 亚洲精品成人A8198A片漫画 | 调教女警花穿环上班 | 欧美视频一区二区三区四区 | 欧美办公室silkstocking | 国产90后美女露脸在线观看 | 潘金莲西门庆一级淫片aaaaaa | 特黄特级毛片免费视 | 亚州精品永久观看视频 | 免费一级黄 | 91porny紧身翘臀 | 草莓视频幸福宝 | 欧美一区二区三区综合色视频 | 国产精品模特hd在线 | 动漫美女被褥吸奶漫画漫画 | 欧美日韩1区2区 | 免费看a视频 | 特级老女人淫片高清视频 | 日本一区二区视频免费播放 | 青青青久久久 | 日韩日日日 | 欧美伊人久久久久久久久影院 | 大香焦在线观看 | 亚洲欧美国产精品久久久 | 午夜亚洲一区二区福利 |