Java注解方式之防止重复请求

目录
  • 一、 前情提要
  • 二、技术设计
    • 2.1 库表设计
    • 2.2 业务逻辑
    • 2.3 代码编写
    • 2.4 测试
    • 2.5 问题所在
  • 三、解决方案
  • 四 、唠唠
    • 4.1 项目
    • 4.2 redis服务
    • 4.3 其他问题

自定义注解方式防止前端同一时间多次重复提交

一、 前情提要

有这样一个业务,上课的时候老师给表现好的学生送小花花,

每节课都能统计出某个学生收到的花的总数。

按照产品需求,前端点击送花按钮后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 业务逻辑

业务逻辑很简单,针对某一堂课的某一个学生,老师第一次送花就新增一条记录,之后老师给这个学生送花就在原有的记录基础上增加送花数量即可。

如果前端能保证一堂课,一个学生,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));
    }

}

执行完看一下数据库结果:

这肯定是有问题的 多三条要出问题的,要扣钱绩效的

三、解决方案

解决方案有很多,我今天介绍一种自定义注解的方式(其实就是用了分布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 发现更多精彩  关注公众号:木子的昼夜编程  分享一个生活在互联网底层做着增删改查的码农的感悟与学习
 *
 * 关于自定义注解 后边有机会专门写一写 先会用
 * @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

4.2 redis服务

贴心的我把redis的windows免安装包都放到项目里了

test/java/soft 解压 双击redis-server.exe 即可运行

默认没密码

4.3 其他问题

支持参数是对象的自定义key

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

到此这篇关于Java注解方式之防止重复请求的文章就介绍到这了,更多相关Java 注解方式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java的文档注释之生成帮助文档的实例

    示例: /** * Title: Person类<br/> * Description:通过Person类说明Java中的文档注释<br/> * Company: *** * @author *** * @version 1.0 */ public class Person { /** * 这个是Person类的构造方法 * @param name Person 的名字 * */ public Person(String name) { //执行语句: } /** * 这是read

  • 如何在java中正确使用注释

    Java提供了3种类型的注释: 单行注释(C++风格) 在Java中最简单的注释是单行注释.它以两个正斜杠开始并到行尾结束.例如: // this is a single-line comment x = 1; // a single-line comment after code 多行注释(C风格) Java同样提供跨越多行的注释类型.这种类型的注释以紧跟着一个星号的正斜杠开始,并以紧跟着一个正斜杠的星号结束.这种类型注释的开始和结束分界符可以在同一行里也可以在不同的行上.例如: /* Thi

  • 美化java代码,从合理注释开始

    请停止代码注释 "干净的代码应该像写好的散文一样" - Robert C. Martin 不良代码的通病就是有很多注释.这是凌乱的源代码最明显的迹象. 每个程序员的目标应该是编写干净和富有表现力的代码,以避免代码注释.每个变量,函数和类的目的应该隐含在其名称和结构中. 当其他人读取您的代码时,他们不应该阅读注释以了解你的代码正在做什么.命名良好的类和函数应该引导读者通过你的代码,就像一本写得很好的小说一样.当读者看到一个新的类或功能时,他们不应该对他们在里面看到的东西感到困惑难以理解.

  • Java如何获取属性的注释信息详解

    前言 注解是JavaSE5.0开始提供的一项新特性,利用此特性可以通过特定的注解标签为程序提供一些描述性信息.这些描述性信息可以在编译或运行时为编译器.运行环境提供附加的信息,从而简化开发.本文将详细介绍Java获取属性注释信息的相关内容,下面来一起看看详细的实现代码 实例代码 1.数据模型 package com.example.demo; import java.util.List; /** * Description: * * @author jack * @date 2021/7/13

  • Java注释代码执行方法解析

    直接上代码: @Test public void testUnicode() { String a = "Hello"; // \u000d a="world"; System.out.println(a); // \u000a a="hello world!"; System.out.println(a); } 猜一猜,最后会输出什么? world hello world! 是的,没看错,那二行看似"注释掉的代码",被执行了

  • Java注解方式之防止重复请求

    目录 一. 前情提要 二.技术设计 2.1 库表设计 2.2 业务逻辑 2.3 代码编写 2.4 测试 2.5 问题所在 三.解决方案 四 .唠唠 4.1 项目 4.2 redis服务 4.3 其他问题 自定义注解方式防止前端同一时间多次重复提交 一. 前情提要 有这样一个业务,上课的时候老师给表现好的学生送小花花, 每节课都能统计出某个学生收到的花的总数. 按照产品需求,前端点击送花按钮后30秒内是不能再次送花的(信任的基础) (上课老师送花行为都进行统计了,可见互联网是多么可怕) 二.技术设

  • Spring注解方式防止重复提交原理详解

    Srping注解方式防止重复提交原理分析,供大家参考,具体内容如下 方法一: Springmvc使用Token 使用token的逻辑是,给所有的url加一个拦截器,在拦截器里面用java的UUID生成一个随机的UUID并把这个UUID放到session里面,然后在浏览器做数据提交的时候将此UUID提交到服务器.服务器在接收到此UUID后,检查一下该UUID是否已经被提交,如果已经被提交,则不让逻辑继续执行下去-** 1 首先要定义一个annotation: 用@Retention 和 @Targ

  • java并发访问重复请求过滤问题

    问题描述 前段时间遇到个问题,自己内部系统调用出现重复请求导致数据混乱. 发生条件:接受到一个请求,该请求没有执行完成又接受到相同请求,导致数据错误(如果是前一个请求执行完成,马上又接受相同请求不会有问题) 问题分析:是由于数据库的脏读导致 问题解决思路 1.加一把大大的锁 (是最简单的实现方式,但是性能堪忧,而且会阻塞请求) 2.实现请求拦截 (可以共用,但是怎么去实现却是一个问题,怎么用一个优雅的方式实现,并且方便复用) 3.修改实现 (会对原有代码做改动,存在风险,最主要的是不能共用) 最

  • Java后台防止客户端重复请求、提交表单实现原理

    这篇文章主要介绍了Java后台防止客户端重复请求.提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言 在Web / App项目中,有一些请求或操作会对数据产生影响(比如新增.删除.修改),针对这类请求一般都需要做一些保护,以防止用户有意或无意的重复发起这样的请求导致的数据错乱. 常见处理方案 1.客户端 例如表单提交后将提交按钮设为disable 等等方法... 2.服务端 前端的限制仅能解决少部分问题,且不够彻底,后端自有的

  • Java 限制前端重复请求的实例代码

    目录 背景及用途 实现步骤 背景及用途 前端页面出现卡顿,用户反复点击操作按钮,导致后台接口短时间内多次提交 实现步骤 设置切面,增加注解,导致在规定时间内该接口不可重复调用 设置一个接口 NoRepeatSubmit import java.lang.annotation.*; /** * xzj_2022_8_2 * 重复请求限制切面 */ @Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上 @Retention(Retentio

  • Spring中注解方式的异步请求

    一.Servlet3.0异步请求 @WebServlet(value = "/async", asyncSupported = true) public class HelloAsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //

  • 前端取消请求及取消重复请求方式

    目录 一.前言 二.项目准备 三.原生方法 四.promise 五.借助Promise.race 六.总结 一.前言 今天,我想跟大家分享几种前端取消请求的几种方式. 相信大家在平时的开发中,肯定或多或少的会遇到需要取消重复请求的场景,比如最常见的,我们在使用tab栏时,我们都会使用一个盒子去存放内容,然后在切换tab栏时,会清除掉原来的内容,然后替换上新的内容,这个时候,如果我们的数据是通过服务从后端获取的,就会存在一个问题,由于获取数据是需要一定的时间的,就会存在当我们切换tab栏到新的ta

  • 浅谈Java注解和动态代理

    本文主要介绍Java中与注解和动态代理有关的部分知识,接下来我们看看具体内容. Annotation(注解) 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行. 1. 三个基本的Annotation: Override:限定重写父类方法, 该注解只能用于方法 Deprecated:用于表示某个程序元素(类, 方法等)已过时 SuppressWarnings:抑制编译器警告. 2.自定义Annotati

  • 详解SpringBoot AOP 拦截器(Aspect注解方式)

    常用用于实现拦截的有:Filter.HandlerInterceptor.MethodInterceptor 第一种Filter属于Servlet提供的,后两者是spring提供的,HandlerInterceptor属于Spring MVC项目提供的,用来拦截请求,在MethodInterceptor之前执行. 实现一个HandlerInterceptor可以实现接口HandlerInterceptor,也可以继承HandlerInterceptorAdapter类,两种方法一样.这个不在本文

  • 深入理解Java注解类型(@Annotation)

    Java注解是在JDK5时引入的新特性,鉴于目前大部分框架(如spring)都使用了注解简化代码并提高编码的效率,因此掌握并深入理解注解对于一个Java工程师是来说是很有必要的事.本篇我们将通过以下几个角度来分析注解的相关知识点 理解Java注解 实际上Java注解与普通修饰符(public.static.void等)的使用方式并没有多大区别,下面的例子是常见的注解: public class AnnotationDemo { //@Test注解修饰方法A @Test public static

随机推荐