springboot自定义注解使用AOP防止重复提交

4 篇文章 0 订阅
1 篇文章 0 订阅

这两天对防止重复提交做了一个回顾,发现之前博文中有很多都不全面,这里要说一声抱歉了,经过我重新整理一番,现重新发布。。。

【希望大家多多指正】

现附上我测试的demo地址:Github

 

对于重复提交问题,可能大多数人并没太注意,由于问题本身难被发现,导致人们的忽视。但这个问题一旦发生,就可能是致命的问题,特别是对于电商项目,或者金融类等会有致命性错误。

这两天在网上看了很多资料,,前端或者后端都有很好的实现方法,这里只总结了下后端处理方法。

这里我使用redis分布式锁,springAOP切面,自定义注解方式实现。

首先思路是:

  1. 在前端调接口时候,需要传token授权口令到后台接口;

  2. 接口在根据token和接口地址生成对应的key值,在通过UUID生成唯一value;

  3. 将key-value锁添加到redis缓存中并设置自定义的过期时间(如果能添加锁成功,则表示不是重复提交,否则为重复提交)

  4. 最后在接口执行完之后一定要释放锁哦。

现在启动你的redis,开始你的疯狂之旅吧。。。。。。

 

对于redis配置,以及pom文件,在这里直接粘贴出来:

#redis
spring.redis.open=true
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=6000ms
spring.redis.jedis.pool.max-active=8  
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-idle=8  
spring.redis.jedis.pool.min-idle=0 

 

<properties>
        <java.version>1.8</java.version>
        <mybatis-plus-boot-starter>3.0.6</mybatis-plus-boot-starter>
        <pagehelper-starter.version>1.2.10</pagehelper-starter.version>
        <pagehelper.version>5.1.8</pagehelper.version>
        <spring-data-commons.version>2.2.6.RELEASE</spring-data-commons.version>
        <spring-boot-starter-data-jpa>2.0.4.RELEASE</spring-boot-starter-data-jpa>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
        <druid.version>1.0.28</druid.version>
        <mysql.version>8.0.11</mysql.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus-boot-starter}</version>
        </dependency>

        <!--SpringData工具包-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>${spring-data-commons.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring-boot-starter-data-jpa}</version>
        </dependency>

        <!--MyBatis分页插件starter-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper-starter.version}</version>
        </dependency>
        <!--MyBatis分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>


        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.version}</version>
        </dependency>

        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- 在springboot中引入spring-boot-starter-data-redis依赖时,
			默认使用的时Lettuce,有时可能我们不想使用Lettuce而是使用Jedis来操作redis,
			这就需要我们在引入spring-boot-starter-data-redis依赖时做一些额外的依赖配置. -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.45</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api    logger -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12    logger -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
            <!-- <scope>test</scope> -->
        </dependency>


    </dependencies>

1.首先自定义不重复提交注解NoRepeatSubmit

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 防止重复提交自定义注解
 * @author zhang
 *
 */
@Target(ElementType.METHOD) // 说明了Annotation所修饰的对象范围
@Retention(RetentionPolicy.RUNTIME) // 用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
@Documented // 是一个标记注解,没有成员 (文档化)
public @interface NoRepeatSubmit {

	/**
     * 设置请求锁定时间
     *
     * @return
     */
    int lockTime() default 10;
	
}

2.第二就是切面处理RepeatSubmitAspect

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.zhang.common.annotation.NoRepeatSubmit;
import com.zhang.common.utils.R1;
import com.zhang.common.utils.RedisUtils;
import com.zhang.common.utils.RequestUtils;

import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;
import java.util.UUID;


/**
 * 防止重复提交处理
 * @author zhang
 *
 */
@Aspect
@Component
public class RepeatSubmitAspect {

	private static final Logger LOGGER = LoggerFactory.getLogger(RepeatSubmitAspect.class);

    @Autowired
    private RedisUtils redisUtils;

    @Pointcut("@annotation(noRepeatSubmit)")
    public void pointCut(NoRepeatSubmit noRepeatSubmit) {
    }

    @Around("pointCut(noRepeatSubmit)")
    public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
        int lockSeconds = noRepeatSubmit.lockTime();
        String threadName = Thread.currentThread().getName();// 获取当前线程名称
        HttpServletRequest request = RequestUtils.getRequest();
        Assert.notNull(request, "request can not null");

        String token = request.getHeader("Authorization");// 前端调后台接口传 登录时已获取到的授权口令Authorization
        String path = request.getServletPath();// 前端调接口地址
        LOGGER.info("token;当前请求线程名称threadName;path:, token = [{}], threadName = [{}] , path = [{}]", token, threadName, path);
        
        String key = getKey(token, path);// 调接口时生成临时key
        String clientId = getClientId();// 调接口时生成临时value

        
        // 此处为自测通过ProceedingJoinPoint获取当前请求的方法名称
        // 方法一
        Signature signature = pjp.getSignature();
        MethodSignature ms = (MethodSignature)signature;
        Method method = ms.getMethod();
        LOGGER.info("获取方法一, method = [{}]", method);
        // 方法二
        Signature sig = pjp.getSignature();
		MethodSignature msig = null;
		if (!(sig instanceof MethodSignature)) {
			throw new IllegalArgumentException("该注解只能用于方法");
		}
		msig = (MethodSignature) sig;
		Object target = pjp.getTarget();
		Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
		LOGGER.info("获取方法二, currentMethod = [{}]", currentMethod);
		
		
        boolean isSuccess = redisUtils.tryLock(key, clientId, lockSeconds);// 用于添加锁,如果添加成功返回true,失败返回false
        LOGGER.info("tryLock 当前请求线程名称threadName, threadName = [{}], key = [{}], clientId = [{}]", threadName, key, clientId);
        
        if (isSuccess) {
            LOGGER.info("tryLock success 线程名称threadName:, threadName = [{}], key = [{}], clientId = [{}]",threadName, key, clientId);
            // 获取锁成功
            Object result;

            try {
                // 执行进程
                result = pjp.proceed();// aop代理链执行的方法
            } finally {
            	// 据key从redis中获取value
            	String value = redisUtils.get(key);
        		LOGGER.info("releaseLock start when 'value = clientId', key = [{}], value = [{}], clientId = [{}]", key, value, clientId);
        		if (clientId.equals(redisUtils.get(key))) {
            		LOGGER.info("releaseLock start, key = [{}], value = [{}], clientId = [{}]", key, value, clientId);
            		// 解锁
                	redisUtils.releaseLock(key, clientId);
                	LOGGER.info("releaseLock success 线程名称threadName:, threadName = [{}], key = [{}], clientId = [{}]",threadName, key, clientId);
				}
            }
            return result;
        } else {
            // 添加锁失败,认为是重复提交的请求
        	LOGGER.info("重复请求,请稍后再试    线程名称threadName:" + threadName);
        	LOGGER.info("tryLock fail 线程名称threadName:, threadName = [{}], key = [{}]",threadName, key);
            return R1.error(403, "重复请求,请稍后再试");
        }

    }

    private String getKey(String token, String path) {
        return token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }

}

3.第三是redis工具类和RequestUtils工具类以及接口统一返回格式R1工具类

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类
 * @author zhang
 * @date 2020-04-24 10:28:40
 */
@Component
public class RedisUtils {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final Long RELEASE_SUCCESS = 1L;
    //    private static final String LOCK_SUCCESS = "OK";
    private static final Long LOCK_SUCCESS1 = 1L;
    private static final String SET_IF_NOT_EXIST = "NX";
    /** 设置过期时间单位, EX = seconds; PX = milliseconds */
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    // if get(key) == value return del(key)
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    /**  不设置过期时长 */
    public final static long NOT_EXPIRE = -1;
    /**  默认过期时长,单位:秒 */
    public final static long DEFAULT_EXPIRE = 60 * 60 * 24;

    Gson gson = new Gson();

    public void set(String key, Object value, long expire){
        stringRedisTemplate.opsForValue().set(key, toJson(value));
        if(expire != NOT_EXPIRE){
            stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
    }

    public void set(String key, Object value){
        set(key, value, DEFAULT_EXPIRE);
    }

    public <T> T get(String key, Class<T> clazz, long expire) {
        String value = stringRedisTemplate.opsForValue().get(key);
        if(expire != NOT_EXPIRE){
            stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value == null ? null : fromJson(value, clazz);
    }

    public <T> T get(String key, Class<T> clazz) {
        return get(key, clazz, NOT_EXPIRE);
    }

    public String get(String key, long expire) {
        String value = stringRedisTemplate.opsForValue().get(key);
        if(expire != NOT_EXPIRE){
            stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return value;
    }

    public String get(String key) {
        return get(key, NOT_EXPIRE);
    }

    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }


    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     *
     * 支持重复,线程安全
     *
     * @param lockKey   加锁键
     * @param clientId  加锁客户端唯一标识(采用UUID)
     * @param seconds   锁过期时间
     * @return
     */
    public boolean tryLock(String lockKey, String clientId, int seconds) {
        return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            //此方法报错,查看源码jedis.set方法不能如此传参
//            String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);

//            SetParams setParams = new SetParams();// 设置key的过期时间单位
//            setParams.px(seconds); // (EX = seconds; PX = milliseconds)
//            String result = jedis.set(lockKey, clientId, setParams);

            long result = jedis.setnx(lockKey, clientId);// jedis.setnx该方式赋值(成功返回1,否则返回0)
            if (LOCK_SUCCESS1 == result) {
                return true;
            }

            return false;
        });
    }

    /**
     * 与 tryLock 相对应,用作释放锁
     *
     * @param lockKey
     * @param clientId
     * @return
     */
    public boolean releaseLock(String lockKey, String clientId) {
        return stringRedisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                    Collections.singletonList(clientId));
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        });
    }

    /**
     * Object转成JSON数据(Gson方式)
     */
    private String toJson(Object object){
        if(object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String){
            return String.valueOf(object);
        }
        return gson.toJson(object);
    }

    /**
     * JSON数据转成Object(Gson方式)
     */
    private <T> T fromJson(String json, Class<T> clazz){
        return gson.fromJson(json, clazz);
    }
}
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class RequestUtils {

    public static HttpServletRequest getRequest() {
        ServletRequestAttributes ra= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return ra.getRequest();
    }

}

import java.util.ArrayList;
import java.util.Map;

import org.apache.http.HttpStatus;
/**
 * 接口统一返回格式
 * @author zhang
 *
 */
public class R1<T> {

    private static final long serialVersionUID = 1L;

    private int code;

    private T data;

    private String message;

    /**
     * 无参构造
     */
    public R1() {
        this.code = 0;
        this.message = "success";
        this.data = (T) new ArrayList<Object>();
    }

    /**
     * 有参构造
     * @param code
     * @param data
     * @param message
     */
    public R1(int code, T data, String message) {
        this.code = code;
        this.data = data;
        this.message = message;
    }

    /**
     * ok————无参返回
     * @return
     */
    public static <T> R1<T> ok() {
        return new <T> R1<T>();
    }

    /**
     * ok————指定提示语
     * @param message
     * @return
     */
    public static <T> R1<T> ok(String message) {
        R1<T> r = new R1<T>();
        r.message = message;
        r.data = (T) new ArrayList<Object>();
        return r;
    }

    /**
     * ok————指定data(承载数据)
     * @param map
     * @return
     */
    public static <T> R1<T> ok(Map<String, Object> map) {
        R1<T> r = new R1<T>();
        r.data = (T) map;
        return r;
    }

    /**
     * error————无参返回
     * @return
     */
    public static <T> R1<T> error() {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    }

    /**
     * error————指定message
     * @param message
     * @return
     */
    public static <T> R1<T> error(String message) {
        return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, message);
    }

    /**
     * error————指定code, message
     * @param code
     * @param message
     * @return
     */
    public static <T> R1<T> error(int code, String message) {
        R1<T> r = new R1<T>();
        r.code = code;
        r.message = message;
        r.data = (T) new ArrayList<Object>();
        return r;
    }

    /**
     * ok————指定data(承载数据)
     * @param data
     * @return
     */
    public R1<T> ok(T data) {
        R1<T> r = new R1<T>();
        r.data = data;
        return r;
    }

    /**
     * 指定data(承载数据)
     * @param data
     * @return
     */
    public static <T> R1<T> data(T data) {
        return data(data, "success");
    }

    public static <T> R1<T> data(T data, String message) {
        return data(HttpStatus.SC_OK, data, message);
    }

    public static <T> R1<T> data(int code, T data, String message) {
        return new R1(code, data, data == null ? "暂无承载数据" : message);
    }

    public int getCode() {
        return this.code;
    }

    public T getData() {
        return this.data;
    }

    public String getmessage() {
        return this.message;
    }

    public void setCode(final int code) {
        this.code = code;
    }

    public void setData(final T data) {
        this.data = data;
    }

    public void setmessage(final String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "R1 [code=" + code + ", data=" + data + ", message=" + message + "]";
    }

}

4.第四写一个提交接口用于测试

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.zhang.common.annotation.NoRepeatSubmit;
import com.zhang.common.utils.R1;
/**
 * 测试noReatSubmit
 * @author zhang
 *
 */
@RestController
@RequestMapping("/noReatSubmit")
public class NoReatSubmitController {

	@PostMapping("submit")
    @NoRepeatSubmit(lockTime = 30)
    public R1<?> submit(@RequestBody UserBean userBean) {
        try {
            // 模拟业务场景
        	System.out.println("模拟业务场景开始...,当前请求线程名:" + userBean.getThreadName());
            Thread.sleep(1500);
        } catch (InterruptedException e) {
        	System.out.println("模拟业务场景失败");
            e.printStackTrace();
        }
        System.out.println("模拟业务场景成功,当前请求线程名称:" + userBean.getThreadName());
        return new R1<Object>(200, userBean.getUserId(), "成功");
      
    }

    public static class UserBean {
        private String userId;
        
        private String threadName;

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId == null ? null : userId.trim();
        }

		public String getThreadName() {
			return threadName;
		}

		public void setThreadName(String threadName) {
			this.threadName = threadName;
		}

		@Override
		public String toString() {
			return "UserBean [userId=" + userId + ", threadName=" + threadName + "]";
		}

    }
	
}

我这边是多线程模拟多次请求,以HTTP方式请求接口测试。

5.第五亮出HTTP请求方法工具类HttpClientUtil

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
/**
 * http请求方式
 * @author zhang
 *
 */
public class HttpClientUtil {

	private static RequestConfig requestConfig;
	private static SSLConnectionSocketFactory sslsf;
	static {
		SSLContext sslContext = null;
		try {
			sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
				// 信任所有证书 https://blog.csdn.net/sleeping_/article/details/50500351
				@Override
				public boolean isTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
						throws java.security.cert.CertificateException {
					return true;
				}
			}).build();
		} catch (Exception e) {
			e.printStackTrace();
		}
		sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
	}

	/**
	 * get方式請求(有参)
	 * @param url
	 * @param param
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doGet(String url, Map<String, Object> param, String Authorization, String webAuthorization) {
		// 创建Httpclient对象
		CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
		String resultString = "";
		CloseableHttpResponse response = null;
		try {
			// 创建uri
			URIBuilder builder = new URIBuilder(url);
			if (param != null) {
				for (String key : param.keySet()) {
					if (param.get(key).getClass().isArray()) {
						String[] arr = (String[]) param.get(key);
						for (String string : arr) {
							builder.addParameter(key, string);
						}
					}else{
						builder.addParameter(key, param.get(key).toString());
					}
				}
			}
			URI uri = builder.build();
			// 创建http GET请求
			HttpGet httpGet = new HttpGet(uri);
			httpGet.addHeader("Connection", "close");
			httpGet.addHeader("Authorization", Authorization);
			httpGet.addHeader("Web-Authorization", webAuthorization);
			httpGet.setConfig(requestConfig);
			// 执行请求
			response = httpclient.execute(httpGet);
			// 判断返回状态是否为200
			if (response.getStatusLine().getStatusCode() == 200) {
				resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				httpclient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}

	/**
	 * get方式请求(无参)
	 * @param url
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doGet(String url, String Authorization, String webAuthorization) {
		return doGet(url, null, Authorization, webAuthorization);
	}

	/**
	 * post方式请求(有参)
	 * @param url
	 * @param param
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doPost(String url, Map<String, String> param, String Authorization, String webAuthorization) {
		// 创建Httpclient对象
		CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// 创建Http Post请求
			HttpPost httpPost = new HttpPost(url);
			httpPost.addHeader("Connection", "close");
			httpPost.addHeader("Authorization", Authorization);
			httpPost.addHeader("Web-Authorization", webAuthorization);
			httpPost.setConfig(requestConfig);
			// 创建参数列表
			if (param != null) {
				List<NameValuePair> paramList = new ArrayList<>();
				for (String key : param.keySet()) {
					paramList.add(new BasicNameValuePair(key, param.get(key)));
				}
				// 模拟表单
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, "utf-8");
				httpPost.setEntity(entity);
			}
			// 执行http请求
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}

	/**
	 * post方式请求(无参)
	 * @param url
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doPost(String url, String Authorization, String webAuthorization) {
		return doPost(url, null, Authorization, webAuthorization);
	}

	/**
	 * delete方式请求(有参)
	 * @param url
	 * @param param
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doDelete(String url, String Authorization, String webAuthorization) {
		// 创建Httpclient对象
		CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
		String resultString = "";
		CloseableHttpResponse response = null;
		try {
			// 创建http delete请求
			HttpDelete httpDelete = new HttpDelete(url);
			httpDelete.addHeader("Connection", "close");
			httpDelete.addHeader("Authorization", Authorization);
			httpDelete.addHeader("Web-Authorization", webAuthorization);
			httpDelete.setConfig(requestConfig);
			// 执行请求
			response = httpclient.execute(httpDelete);
			// 判断返回状态是否为200
			if (response.getStatusLine().getStatusCode() == 200) {
				resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (response != null) {
					response.close();
				}
				httpclient.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}
	
	/**
	 * put方式请求(有参)
	 * @param url
	 * @param param
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doPut(String url, String json, String Authorization, String webAuthorization) {
		// 创建Httpclient对象
		CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// 创建Http Post请求
			HttpPut httpPut = new HttpPut(url);
			httpPut.addHeader("Connection", "close");
			httpPut.addHeader("Authorization", Authorization);
			httpPut.addHeader("Web-Authorization", webAuthorization);
			httpPut.setConfig(requestConfig);
			// 创建请求内容
			StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
			httpPut.setEntity(entity);
			// 执行http请求
			response = httpClient.execute(httpPut);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}
	
	/**
	 * 请求的参数类型为json
	 * post方式请求 (有参)
	 * @param url
	 * @param json
	 * @param Authorization 口令授权
	 * @return
	 */
	public static String doPostJson(String url, String json, String Authorization, String webAuthorization) {
		// 创建Httpclient对象
		CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
		CloseableHttpResponse response = null;
		String resultString = "";
		try {
			// 创建Http Post请求
			HttpPost httpPost = new HttpPost(url);
			System.out.println("创建Http Post请求");
			httpPost.addHeader("Connection", "close");
			httpPost.addHeader("Authorization", Authorization);
			httpPost.addHeader("Web-Authorization", webAuthorization);
			httpPost.setConfig(requestConfig);
			// 创建请求内容
			StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
			System.out.println("请求内容:" + entity.toString());
			httpPost.setEntity(entity);
			// 执行http请求
			response = httpClient.execute(httpPost);
			resultString = EntityUtils.toString(response.getEntity(), "utf-8");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				response.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return resultString;
	}
	
}

6.第六步创建线程进行调接口测试RunnableDemo

import com.alibaba.fastjson.JSONObject;
import com.zhang.common.utils.HttpClientUtil;
import com.zhang.modules.test.controller.NoReatSubmitController.UserBean;
/**
 * 创建线程测试接口
 * @author zhang
 *
 */
public class RunnableDemo implements Runnable {
	
	private Thread t;
	private String threadName;

	public RunnableDemo(String name) {
		threadName = name;
		System.out.println("Creating: " + threadName);
	}

	public String threadName() {
		return threadName;
	}
	
	public void run() {
		System.out.println("Running, threadName = [{}]: " + threadName);
		UserBean userBean = new UserBean();
		userBean.setUserId("123123123");
		userBean.setThreadName(threadName);
		String url="http://localhost:9999/zhang/noReatSubmit/submit";
		HttpClientUtil.doPostJson(url, JSONObject.toJSONString(userBean), "youToken", null);
		System.out.println("Thread " + threadName + " exiting.");
	}

	public void start() {
		System.out.println("Starting " + threadName);
		if (t == null) {
			t = new Thread(this, threadName);
			t.start();
		}
	}
}

7.第七步,写一个main函数,加上线程启动项目测试

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import com.zhang.modules.test.controller.RunnableDemo;


@SpringBootApplication 
@ComponentScan(basePackages={"com.zhang"})
public class TestProjectApplication { 

	public static void main(String[] args) {
		SpringApplication.run(TestProjectApplication.class, args);
		System.out.println("启动成功!");

		// 开启线程
	    RunnableDemo R1 = new RunnableDemo( "Thread-1");
	    RunnableDemo R2 = new RunnableDemo( "Thread-2");
//	    RunnableDemo R3 = new RunnableDemo( "Thread-3");
//	    RunnableDemo R4 = new RunnableDemo( "Thread-4");
	    R1.start();
	    R2.start();
//	    R3.start();
//	    R4.start();
	    
	}
}

测试结果(控制台日志)如下:

参考链接:

https://www.toutiao.com/i6768274855004471821/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&from=groupmessage&timestamp=1582519759&app=news_article&utm_source=weixin&utm_medium=toutiao_android&req_id=202002241249180101310981964407620D&group_id=6768274855004471821   ——   Spring Boot 使用 AOP 防止重复提交

https://www.cnblogs.com/peida/archive/2013/04/24/3036689.html   ——   深入理解Java:注解(Annotation)自定义注解入门

https://www.cnblogs.com/linjiqin/p/8003838.html   ——   Redis分布式锁的正确实现方式

http://redisdoc.com/   ——   Redis 命令参考

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于SpringBoot自定义注解AOP的问题,我可以为您提供一些基本的介绍和示例代码。 首先,AOP(Aspect-Oriented Programming)是一种编程范式,它可以在不修改业务逻辑代码的情况下,对应用程序进行横切关注点的切面处理。而Spring AOP作为Spring框架的一部分,提供了一种基于代理模式的AOP实现。 在使用Spring AOP的过程中,自定义注解可以作为切点表达式的一部分,通过对注解的解析,实现对被注解的方法或类的切面处理。下面是一个简单的示例代码,演示如何通过自定义注解实现对方法的AOP处理: 首先,定义一个自定义注解: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation { String value() default ""; } ``` 然后,在需要被拦截的方法上添加该注解: ```java @Service public class MyService { @MyAnnotation("myAnnotation") public void doSomething() { System.out.println("do something..."); } } ``` 接下来,使用AspectJ的@Aspect注解定义一个切面类,并在该类中定义一个切点,用于匹配被@MyAnnotation注解的方法: ```java @Aspect @Component public class MyAspect { @Pointcut("@annotation(com.example.demo.annotation.MyAnnotation)") public void myAnnotationPointcut() {} @Before("myAnnotationPointcut()") public void beforeMyAnnotation() { System.out.println("before myAnnotation..."); } } ``` 最后,启动SpringBoot应用程序,调用MyService的doSomething方法,就可以看到输出结果: ```java before myAnnotation... do something... ``` 以上就是一个简单的SpringBoot自定义注解AOP的示例。通过使用自定义注解,可以更加方便地实现对应用程序的切面处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值