springboot工程中使用tcp协议

文章目录

一、概述

在上文JAVA TCP协议初体验 中,我们使用java实现了tcp协议的一个雏形,实际中大部分项目都已采用springboot,那么,怎么在springboot中整合tcp协议呢?如何实现服务器controller通过tcp协议下发命令到tcp client执行,并且在controller中获取执行结果?

二、实现思路

为了方便演示,本文我们将TcpClient、TcpServer放在同一工程,具体做法为:

  1. 拷贝之前文章的TcpClient、TcpServer代码
  2. 提取服务器、客户端启动代码,使用@PostConstruct注解修饰
  3. 开发controller,定义客户端发送消息、服务器发送消息

三、代码结构

四、代码放送

https://gitcode.com/00fly/tcp-show/tree/main/springboot-tcp

或者使用下面的备份文件恢复成原始的项目代码

如何恢复,请移步查阅:神奇代码恢复工具

java 复制代码
//goto docker\docker-compose.yml
version: '3.7'
services:
  springboot-tcp:
    image: registry.cn-shanghai.aliyuncs.com/00fly/springboot-tcp:1.0.0
    container_name: springboot-tcp
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 200M
        reservations:
          cpus: '0.05'
          memory: 200M
    ports:
    - 8080:8081
    restart: on-failure
    logging:
      driver: json-file
      options:
        max-size: '5m'
        max-file: '1'
//goto docker\restart.sh
#!/bin/bash
docker-compose down && docker-compose up -d && docker stats
//goto docker\stop.sh
#!/bin/bash
docker-compose down
//goto docker\wait-for.sh
#!/bin/sh

TIMEOUT=15
QUIET=0

echoerr() {
  if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}

usage() {
  exitcode="$1"
  cat << USAGE >&2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit "$exitcode"
}

wait_for() {
  for i in `seq $TIMEOUT` ; do
    nc -z "$HOST" "$PORT" > /dev/null 2>&1

    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 0 ] ; then
        exec "$@"
      fi
      exit 0
    fi
    sleep 1
  done
  echo "Operation timed out" >&2
  exit 1
}

while [ $# -gt 0 ]
do
  case "$1" in
    *:* )
    HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
    PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -t)
    TIMEOUT="$2"
    if [ "$TIMEOUT" = "" ]; then break; fi
    shift 2
    ;;
    --timeout=*)
    TIMEOUT="${1#*=}"
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    *)
    echoerr "Unknown argument: $1"
    usage 1
    ;;
  esac
done

if [ "$HOST" = "" -o "$PORT" = "" ]; then
  echoerr "Error: you need to provide a host and port to test."
  usage 2
fi

wait_for "$@"
//goto Dockerfile
#基础镜像
FROM adoptopenjdk/openjdk8-openj9:alpine-slim

COPY docker/wait-for.sh /
RUN chmod +x /wait-for.sh && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo 'Asia/Shanghai' >/etc/timezone

#引入运行包
COPY target/*.jar /app.jar

#指定交互端口
EXPOSE 8081

CMD ["--server.port=8081"]

#项目的启动方式
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Xshareclasses", "-Xquickstart", "-jar", "/app.jar"]
//goto 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.fly</groupId>
	<artifactId>springboot-tcp</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
		<relativePath />
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<docker.hub>registry.cn-shanghai.aliyuncs.com</docker.hub>
		<java.version>1.8</java.version>
		<skipTests>true</skipTests>
	</properties>

	<dependencies>
		<!-- Compile -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-tomcat</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jetty</artifactId>
		</dependency>
		<dependency>
			<groupId>com.github.xiaoymin</groupId>
			<artifactId>knife4j-spring-boot-starter</artifactId>
			<version>2.0.8</version>
		</dependency>

		<!-- 异步日志,需要加入disruptor依赖 -->
		<dependency>
			<groupId>com.lmax</groupId>
			<artifactId>disruptor</artifactId>
			<version>3.4.2</version>
		</dependency>

		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.5</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

		<!-- Test -->
		<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>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

	<!-- 阿里云maven仓库 -->
	<repositories>
		<repository>
			<id>public</id>
			<name>aliyun nexus</name>
			<url>https://maven.aliyun.com/repository/public/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>public</id>
			<name>aliyun nexus</name>
			<url>https://maven.aliyun.com/repository/public/</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

	<build>
		<finalName>${project.artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>

			<!-- 添加docker-maven插件 -->
			<plugin>
				<groupId>io.fabric8</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.40.3</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>build</goal>
							<!--<goal>push</goal>-->
							<!--<goal>remove</goal>-->
						</goals>
					</execution>
				</executions>
				<configuration>
					<!-- 连接到带docker环境的linux服务器编译image -->
					<!-- <dockerHost>http://192.168.182.10:2375</dockerHost> -->

					<!-- Docker 推送镜像仓库地址 -->
					<pushRegistry>${docker.hub}</pushRegistry>
					<images>
						<image>
							<name>
								${docker.hub}/00fly/${project.artifactId}:${project.version}</name>
							<build>
								<dockerFileDir>${project.basedir}</dockerFileDir>
							</build>
						</image>
					</images>
				</configuration>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<excludes>
					<exclude>**/*.java</exclude>
				</excludes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<includes>
					<include>**/**</include>
				</includes>
				<filtering>false</filtering>
			</resource>
		</resources>
	</build>
</project>
//goto src\main\java\com\fly\BootApplication.java
package com.fly;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(BootApplication.class, args);
    }
}
//goto src\main\java\com\fly\core\config\AsyncThreadPoolConfig.java
package com.fly.core.config;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * 异步线程池配置
 * 
 * @author 00fly
 * @version [版本号, 2023年10月22日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@Configuration
@EnableAsync
public class AsyncThreadPoolConfig implements AsyncConfigurer
{
    @Bean
    ThreadPoolTaskExecutor taskExecutor()
    {
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Math.max(processors, 5));
        executor.setMaxPoolSize(Math.max(processors, 5) * 2);
        executor.setQueueCapacity(10000);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("asyncTask-");
        
        // ThreadPoolExecutor类有几个内部实现类来处理这类情况:
        // AbortPolicy 丢弃任务,抛运行时异常
        // CallerRunsPolicy 执行任务
        // DiscardPolicy 忽视,什么都不会发生
        // DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        return executor;
    }
    
    @Override
    public Executor getAsyncExecutor()
    {
        return taskExecutor();
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
    {
        return (ex, method, params) -> {
            log.info("Exception message - {}", ex.getMessage());
            log.info("Method name - {}", method.getName());
            for (Object param : params)
            {
                log.info("Parameter value - {}", param);
            }
        };
    }
}
//goto src\main\java\com\fly\core\config\Knife4jConfig.java
package com.fly.core.config;

import java.util.Collections;
import java.util.List;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;

import io.swagger.annotations.ApiOperation;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

/**
 * Knife4jConfig
 *
 */
@EnableKnife4j
@Configuration
@EnableSwagger2WebMvc
@ConditionalOnWebApplication
@Import(BeanValidatorPluginsConfiguration.class)
public class Knife4jConfig
{
    /**
     * 开发、测试环境接口文档打开
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    @Bean
    Docket createRestApi()
    {
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
            .enable(true)
            .select()
            .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
            .paths(PathSelectors.any()) // 包下的类,生成接口文档
            .build()
            .securitySchemes(security());
    }
    
    private ApiInfo apiInfo()
    {
        return new ApiInfoBuilder().title("数据接口API").description("接口文档").termsOfServiceUrl("http://00fly.online/").version("1.0.0").build();
    }
    
    private List<ApiKey> security()
    {
        return Collections.singletonList(new ApiKey("token", "token", "header"));
    }
}
//goto src\main\java\com\fly\core\config\WebClientConfig.java
package com.fly.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

/**
 * 配置WebClient
 */
@Configuration
public class WebClientConfig
{
    @Bean
    WebClient webClient()
    {
        return WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build();
    }
}
//goto src\main\java\com\fly\core\config\WebMvcConfig.java
package com.fly.core.config;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 
 * mvc配置
 * 
 * @author 00fly
 * @version [版本号, 2021年4月23日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Configuration
@ConditionalOnWebApplication
public class WebMvcConfig implements WebMvcConfigurer
{
    @Override
    public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
    {
        converters.add(stringHttpMessageConverter());
        converters.add(mappingJackson2HttpMessageConverter());
    }
    
    @Override
    public void configureContentNegotiation(final ContentNegotiationConfigurer configurer)
    {
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
        configurer.ignoreUnknownPathExtensions(false);
        configurer.favorPathExtension(true);
        configurer.favorParameter(false);
        final Map<String, MediaType> mediaTypes = new ConcurrentHashMap<>(3);
        mediaTypes.put("atom", MediaType.APPLICATION_ATOM_XML);
        mediaTypes.put("html", MediaType.TEXT_HTML);
        mediaTypes.put("json", MediaType.APPLICATION_JSON);
        configurer.mediaTypes(mediaTypes);
    }
    
    @Bean
    StringHttpMessageConverter stringHttpMessageConverter()
    {
        return new StringHttpMessageConverter();
    }
    
    @Bean
    MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter()
    {
        final MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        final List<MediaType> list = new ArrayList<>();
        list.add(MediaType.APPLICATION_JSON);
        list.add(MediaType.APPLICATION_XML);
        list.add(MediaType.TEXT_PLAIN);
        list.add(MediaType.TEXT_HTML);
        list.add(MediaType.TEXT_XML);
        messageConverter.setSupportedMediaTypes(list);
        return messageConverter;
    }
    
    /**
     * 等价于mvc中<mvc:view-controller path="/" view-name="redirect:index" /><br>
     * 等价于mvc中<mvc:view-controller path="/index" view-name="index.html" />
     * 
     * @param registry
     */
    @Override
    public void addViewControllers(final ViewControllerRegistry registry)
    {
        registry.addViewController("/").setViewName("redirect:index");
        registry.addViewController("/index").setViewName("index.html");
    }
}
//goto src\main\java\com\fly\core\exception\GlobalExceptionHandler.java
package com.fly.core.exception;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.fly.core.JsonResult;

import lombok.extern.slf4j.Slf4j;

/**
 * 统一异常处理器
 * 
 * @author 00fly
 * @version [版本号, 2018-09-11]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler
{
    @ExceptionHandler(Exception.class)
    public JsonResult<?> handleBadRequest(Exception exception)
    {
        // JSR303参数校验异常
        if (exception instanceof BindException)
        {
            BindingResult bindingResult = ((BindException)exception).getBindingResult();
            if (null != bindingResult && bindingResult.hasErrors())
            {
                List<String> errMsg = new ArrayList<>();
                bindingResult.getFieldErrors().stream().forEach(fieldError -> {
                    errMsg.add(fieldError.getDefaultMessage());
                });
                Collections.sort(errMsg);
                return JsonResult.error(StringUtils.join(errMsg, ","));
            }
        }
        if (exception instanceof MethodArgumentNotValidException)
        {
            BindingResult bindingResult = ((MethodArgumentNotValidException)exception).getBindingResult();
            if (null != bindingResult && bindingResult.hasErrors())
            {
                // stream写法优化
                return JsonResult.error(bindingResult.getFieldErrors().stream().map(e -> e.getDefaultMessage()).sorted().collect(Collectors.joining(",")));
            }
        }
        
        // 其余情况
        log.error("Error: handleBadRequest StackTrace : {}", exception);
        return JsonResult.error(StringUtils.defaultString(exception.getMessage(), "系统异常,请联系管理员"));
    }
}
//goto src\main\java\com\fly\core\JsonResult.java
package com.fly.core;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 
 * 结果对象
 * 
 * @author 00fly
 * @version [版本号, 2021年5月2日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Data
@Accessors(chain = true)
@ApiModel(description = "Json格式消息体")
public class JsonResult<T>
{
    @ApiModelProperty(value = "数据对象")
    private T data;
    
    @ApiModelProperty(value = "是否成功", required = true, example = "true")
    private boolean success;
    
    @ApiModelProperty(value = "错误码")
    private String errorCode;
    
    @ApiModelProperty(value = "提示信息")
    private String message;
    
    public JsonResult()
    {
        super();
    }
    
    public static <T> JsonResult<T> success(T data)
    {
        JsonResult<T> r = new JsonResult<>();
        r.setData(data);
        r.setSuccess(true);
        return r;
    }
    
    public static <T> JsonResult<T> success(T data, String msg)
    {
        JsonResult<T> r = new JsonResult<>();
        r.setData(data);
        r.setMessage(msg);
        r.setSuccess(true);
        return r;
    }
    
    public static JsonResult<?> success()
    {
        JsonResult<Object> r = new JsonResult<>();
        r.setSuccess(true);
        return r;
    }
    
    public static JsonResult<Object> error(String code, String msg)
    {
        JsonResult<Object> r = new JsonResult<>();
        r.setSuccess(false);
        r.setErrorCode(code);
        r.setMessage(msg);
        return r;
    }
    
    public static JsonResult<Object> error(String msg)
    {
        return error("500", msg);
    }
}
//goto src\main\java\com\fly\core\runner\WebStartedRunner.java
package com.fly.core.runner;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import com.fly.core.utils.SpringContextUtils;

@Component
@Configuration
@ConditionalOnWebApplication
public class WebStartedRunner
{
    @Bean
    @ConditionalOnWebApplication
    CommandLineRunner init()
    {
        return args -> {
            if (SystemUtils.IS_OS_WINDOWS)// 防止非windows系统报错,启动失败
            {
                String url = SpringContextUtils.getServerBaseURL();
                if (StringUtils.containsNone(url, "-")) // junit port:-1
                {
                    Runtime.getRuntime().exec("cmd /c start /min " + url);
                    Runtime.getRuntime().exec("cmd /c start /min " + url + "/doc.html");
                }
            }
        };
    }
}
//goto src\main\java\com\fly\core\utils\JsonBeanUtils.java
package com.fly.core.utils;

import java.io.IOException;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * JsonBean转换工具
 * 
 * @author 00fly
 *
 */
public class JsonBeanUtils
{
    private static ObjectMapper objectMapper = new ObjectMapper();
    
    /**
     * bean转json字符串
     * 
     * @param bean
     * @return
     * @throws IOException
     */
    public static String beanToJson(Object bean)
        throws IOException
    {
        return beanToJson(bean, false);
    }
    
    /**
     * bean转json字符串
     * 
     * @param bean
     * @param pretty 是否格式美化
     * @return
     * @throws IOException
     */
    public static String beanToJson(Object bean, boolean pretty)
        throws IOException
    {
        String jsonText = objectMapper.writeValueAsString(bean);
        if (pretty)
        {
            return objectMapper.readTree(jsonText).toPrettyString();
        }
        return objectMapper.readTree(jsonText).toString();
    }
    
    /**
     * json字符串转bean
     * 
     * @param jsonText
     * @return
     * @throws IOException
     */
    public static <T> T jsonToBean(String jsonText, Class<T> clazz)
        throws IOException
    {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
        return objectMapper.readValue(jsonText, clazz);
    }
    
    /**
     * json字符串转bean
     * 
     * @param jsonText
     * @param clazz
     * @param ingoreError 是否忽略无法识别字段
     * @return
     * @throws IOException
     */
    public static <T> T jsonToBean(String jsonText, Class<T> clazz, boolean ingoreError)
        throws IOException
    {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, !ingoreError);
        return objectMapper.readValue(jsonText, clazz);
    }
    
    /**
     * json字符串转bean
     * 
     * @param jsonText
     * @return
     * @throws IOException
     */
    public static <T> T jsonToBean(String jsonText, JavaType javaType)
        throws IOException
    {
        return objectMapper.readValue(jsonText, javaType);
    }
    
    /**
     * json字符串转bean
     * 
     * @param jsonText
     * @return
     * @throws IOException
     */
    public static <T> T jsonToBean(String jsonText, TypeReference<T> typeRef)
        throws IOException
    {
        return objectMapper.readValue(jsonText, typeRef);
    }
}
//goto src\main\java\com\fly\core\utils\SpringContextUtils.java
package com.fly.core.utils;

import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.servlet.ServletContext;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import lombok.extern.slf4j.Slf4j;

/**
 * Spring Context 工具类
 */
@Slf4j
@Component
public class SpringContextUtils implements ApplicationContextAware
{
    private static ApplicationContext applicationContext;
    
    private static String SERVER_BASE_URL = null;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException
    {
        log.info("###### execute setApplicationContext ######");
        SpringContextUtils.applicationContext = applicationContext;
    }
    
    public static ApplicationContext getApplicationContext()
    {
        return applicationContext;
    }
    
    public static <T> T getBean(Class<T> clazz)
    {
        Assert.notNull(applicationContext, "applicationContext is null");
        return applicationContext.getBean(clazz);
    }
    
    /**
     * execute @PostConstruct May be SpringContextUtils not inited, throw NullPointerException
     * 
     * @return
     */
    public static String getActiveProfile()
    {
        Assert.notNull(applicationContext, "applicationContext is null");
        String[] profiles = applicationContext.getEnvironment().getActiveProfiles();
        return StringUtils.join(profiles, ",");
    }
    
    /**
     * can use in @PostConstruct
     * 
     * @param context
     * @return
     */
    public static String getActiveProfile(ApplicationContext context)
    {
        Assert.notNull(context, "context is null");
        String[] profiles = context.getEnvironment().getActiveProfiles();
        return StringUtils.join(profiles, ",");
    }
    
    /**
     * get web服务基准地址,一般为 http://${ip}:${port}/${contentPath}
     * 
     * @return
     * @throws UnknownHostException
     * @see [类、类#方法、类#成员]
     */
    public static String getServerBaseURL()
        throws UnknownHostException
    {
        ServletContext servletContext = getBean(ServletContext.class);
        Assert.notNull(servletContext, "servletContext is null");
        if (SERVER_BASE_URL == null)
        {
            String ip = InetAddress.getLocalHost().getHostAddress();
            SERVER_BASE_URL = "http://" + ip + ":" + getProperty("server.port") + servletContext.getContextPath();
        }
        return SERVER_BASE_URL;
    }
    
    /**
     * getProperty
     * 
     * @param key eg:server.port
     * @return
     * @see [类、类#方法、类#成员]
     */
    public static String getProperty(String key)
    {
        return applicationContext.getEnvironment().getProperty(key, "");
    }
}
//goto src\main\java\com\fly\tcp\base\InitManager.java
package com.fly.tcp.base;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

/**
 * 初始化管理类
 */
@Service
public class InitManager
{
    TcpServer server = new TcpServer();
    
    TcpClient client = new TcpClient("CLIENT_1");
    
    public TcpServer getServer()
    {
        return server;
    }
    
    public TcpClient getClient()
    {
        return client;
    }
    
    /**
     * 启动TcpServer、TcpClient
     */
    @PostConstruct
    private void init()
    {
        if (server.startServer("0.0.0.0", 8000))
        {
            new Thread(server).start();
        }
        // docker环境下优先使用docker-compose中environment值
        String serverIp = StringUtils.defaultIfBlank(System.getenv().get("TCP_SERVER"), "127.0.0.1");
        if (client.connectServer(serverIp, 8000))
        {
            new Thread(client).start();
        }
    }
}
//goto src\main\java\com\fly\tcp\base\TcpClient.java
package com.fly.tcp.base;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.fly.core.utils.JsonBeanUtils;
import com.fly.tcp.entity.Command;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TcpClient implements Runnable
{
    private String ip;
    
    private int port;
    
    private Socket socket;
    
    private DataOutputStream dataOutputStream;
    
    private String clientName;
    
    private boolean isClientCoreRun = false;
    
    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
    
    private ExecutorService executor = Executors.newFixedThreadPool(2);
    
    public TcpClient(String clientName)
    {
        super();
        this.clientName = clientName;
    }
    
    public String getClientName()
    {
        return clientName;
    }
    
    /**
     * 
     * @param ip 服务端IP
     * @param port 服务端PORT
     * @return
     */
    public boolean connectServer(String ip, int port)
    {
        try
        {
            this.ip = ip;
            this.port = port;
            socket = new Socket(InetAddress.getByName(ip), port);
            log.info("****** TcpClient will connect to Server {}:{}", ip, port);
            scheduler.scheduleAtFixedRate(this::checkConnection, 0, 10, TimeUnit.SECONDS);
            isClientCoreRun = true;
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            dataOutputStream.writeUTF(clientName);
            dataOutputStream.flush();
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
            isClientCoreRun = false;
        }
        return isClientCoreRun;
    }
    
    /**
     * 检查TCP连接
     */
    private void checkConnection()
    {
        if (socket == null || socket.isClosed())
        {
            log.error("Connection lost, attempting to reconnect");
            reconnect();
        }
    }
    
    private void reconnect()
    {
        try
        {
            socket = new Socket(InetAddress.getByName(ip), port);
            log.info("****** TcpClient will connect to Server {}:{}", ip, port);
            isClientCoreRun = true;
            executor.execute(new ReceiveMsg());
            dataOutputStream = new DataOutputStream(socket.getOutputStream());
            dataOutputStream.writeUTF(clientName);
            dataOutputStream.flush();
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
            isClientCoreRun = false;
        }
    }
    
    /**
     * 发送报文
     */
    public void sendMsg(String msg)
    {
        try
        {
            dataOutputStream.writeUTF(msg);
            dataOutputStream.flush();
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
            closeClientConnect();
        }
    }
    
    /**
     * 断开客户端与服务端的连接
     */
    public void closeClientConnect()
    {
        if (dataOutputStream != null)
        {
            try
            {
                dataOutputStream.close();
                isClientCoreRun = false;
                if (socket != null)
                {
                    socket.close();
                }
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
            }
        }
    }
    
    @Override
    public void run()
    {
        executor.execute(new ReceiveMsg());
    }
    
    class ReceiveMsg implements Runnable
    {
        private DataInputStream dataInputStream;
        
        public ReceiveMsg()
        {
            try
            {
                // 数据输入流
                dataInputStream = new DataInputStream(socket.getInputStream());
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
            }
        }
        
        @Override
        public void run()
        {
            try
            {
                // server停止后, 会影响接受消息线程工作
                while (isClientCoreRun)
                {
                    String msg = dataInputStream.readUTF();
                    log.info("{} get msg: {}", clientName, msg);
                    if (msg.contains("commandId"))
                    {
                        Command cmd = JsonBeanUtils.jsonToBean(msg, Command.class);
                        sendMsg("hello Command!" + JsonBeanUtils.beanToJson(cmd));
                    }
                }
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                // 防止重连失败
                closeClientConnect();
            }
        }
    }
}
//goto src\main\java\com\fly\tcp\base\TcpServer.java
package com.fly.tcp.base;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import com.fly.core.utils.SpringContextUtils;
import com.fly.tcp.service.ResultCallBack;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TcpServer implements Runnable
{
    private ServerSocket serverSocket;
    
    private boolean isServerCoreRun = false;
    
    private Map<String, NewClient> allClient = new HashMap<>();
    
    public boolean startServer(String ip, int port)
    {
        try
        {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(ip, port));
            isServerCoreRun = true;
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
            isServerCoreRun = false;
        }
        return isServerCoreRun;
    }
    
    /**
     * 关闭服务
     *
     * #1 断开与所有客户端的连接,并将客户端容器中的所有已连接的客户端清空。 #2 关闭服务器套接字
     */
    public void closeServer()
    {
        try
        {
            isServerCoreRun = false;
            for (Map.Entry<String, NewClient> all : this.allClient.entrySet())
            {
                all.getValue().isNewClientRun = false;
                all.getValue().socket.close();
            }
            allClient.clear();
            serverSocket.close();
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }
    
    /**
     * 向客户端发送报文
     */
    public void sendMsg(String clientName, String msg)
    {
        if (allClient.containsKey(clientName))
        {
            allClient.get(clientName).sendMsg(msg);
        }
    }
    
    @Override
    public void run()
    {
        try
        {
            log.info("TcpServer will start");
            while (isServerCoreRun)
            {
                // 阻塞式等待客户端连接
                Socket socket = serverSocket.accept();
                String clientName = new DataInputStream(socket.getInputStream()).readUTF();
                String clientIP = socket.getInetAddress().getHostAddress();
                int clientPort = socket.getPort();
                String clientConnectDateTime = DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss");
                NewClient newClient = new NewClient(socket, clientName, clientIP, clientPort, clientConnectDateTime);
                allClient.put(clientName, newClient);
                log.info("**** add new client ===> {}", allClient.keySet());
                new Thread(newClient).start();
            }
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }
    
    /**
     * 客户端线程(被观察者)
     */
    class NewClient extends Observable implements Runnable
    {
        // 客户端套接字
        private Socket socket;
        
        // 数据输入流
        private DataInputStream dataInputStream;
        
        // 数据输出流
        private DataOutputStream dataOutputStream;
        
        // 客户端运行(收、发报文)状态
        private boolean isNewClientRun = true;
        
        // 客户端的名称
        private String clientName;
        
        // 客户端的IP地址
        private String clientIP;
        
        // 构造方法初始化成员属性
        public NewClient(Socket socket, String clientName, String clientIP, int clientPort, String clientConnectDateTime)
        {
            this.socket = socket;
            this.clientName = clientName;
            this.clientIP = clientIP;
            try
            {
                // 注册观察者
                addObserver(SpringContextUtils.getBean(ResultCallBack.class));
                
                // 创建客户端数据输入、输出流
                dataInputStream = new DataInputStream(socket.getInputStream());
                dataOutputStream = new DataOutputStream(socket.getOutputStream());
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
        
        @Override
        public void run()
        {
            try
            {
                // 客户端在运行才能收发报文
                while (this.isNewClientRun)
                {
                    // 获取到客户端发送的报文
                    String msg = dataInputStream.readUTF();
                    if (StringUtils.isNotBlank(msg))
                    {
                        log.info("clientName: {}, clientIP: {}, send msg ===> {}", clientName, clientIP, msg);
                    }
                    
                    // 通知观察者处理
                    if (StringUtils.startsWith(msg, "hello Command"))
                    {
                        setChanged();
                        notifyObservers(msg);
                        continue;
                    }
                    // 向客户端传送数据
                    int index = 0;
                    for (String key : allClient.keySet())
                    {
                        index++;
                        if (StringUtils.equals(key, clientName))
                        {
                            allClient.get(key).sendMsg("from server " + msg + StringUtils.repeat("-----", index));
                        }
                    }
                }
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
        
        /**
         * 断开当前客户端的连接释放资源
         */
        public void closeCurrentClient()
        {
            try
            {
                // 结束客户端的运行状态
                isNewClientRun = false;
                // 断开数据输出出流
                if (dataOutputStream != null)
                {
                    dataOutputStream.close();
                }
                // 断开数据输入出流
                if (dataInputStream != null)
                {
                    dataInputStream.close();
                }
                // 断开客户端套解析
                if (socket != null)
                {
                    socket.close();
                }
                // 将该客户端从客户端容器中删除
                allClient.remove(clientName);
                log.info("**** remove client ===> {}", allClient.keySet());
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
            }
        }
        
        /**
         * 发送报文
         */
        public void sendMsg(String msg)
        {
            try
            {
                // 发送报文
                dataOutputStream.writeUTF(msg);
                // 清空报文缓存
                dataOutputStream.flush();
            }
            catch (IOException e)
            {
                log.error(e.getMessage());
                closeCurrentClient();
            }
        }
    }
}
//goto src\main\java\com\fly\tcp\DemoController.java
package com.fly.tcp;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncTask;

import com.fly.core.JsonResult;
import com.fly.core.utils.JsonBeanUtils;
import com.fly.tcp.base.InitManager;
import com.fly.tcp.entity.Command;
import com.fly.tcp.service.ResultCallBack;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Api(tags = "tcp应用接口")
@RestController
@RequestMapping("/tcp")
public class DemoController
{
    @Autowired
    InitManager initManager;
    
    @Autowired
    ResultCallBack resultCallBack;
    
    @Autowired
    ThreadPoolTaskExecutor executor;
    
    @ApiOperation("client发消息")
    @ApiImplicitParam(name = "msg", example = "忽如一夜春风来", required = true)
    @PostMapping("/client/sendMsg")
    public String sendMsgFromClient(String msg)
    {
        initManager.getClient().sendMsg(msg);
        return msg;
    }
    
    /**
     * 客户端业务处理结果更多是通过回调实现的
     * 
     * @param cmd
     * @return
     * @throws IOException
     */
    @ApiOperation("server发消息Callable")
    @ApiImplicitParam(name = "msg", example = "千树万树梨花开", required = true)
    @PostMapping("/server/sendMsg/callable")
    public Callable<JsonResult<?>> sendMsgFromServer01(String msg)
        throws IOException
    {
        return () -> {
            Command command = new Command().setClientId(initManager.getClient().getClientName()).setText(msg);
            initManager.getServer().sendMsg(initManager.getClient().getClientName(), JsonBeanUtils.beanToJson(command));
            String result;
            for (int i = 0; i < 50; i++)
            {
                result = resultCallBack.queryResult(command.getCommandId());
                if (StringUtils.isNotBlank(result))
                {
                    return JsonResult.success(result, "获取处理结果成功");
                }
                else
                {
                    TimeUnit.MILLISECONDS.sleep(100);
                }
            }
            return JsonResult.error("响应超时,请重试");
        };
    }
    
    /**
     * 客户端业务处理结果更多是通过回调实现的(WebAsyncTask升级版callable,增加超时异常等处理)
     * 
     * @param cmd
     * @return
     * @throws IOException
     */
    @ApiOperation("server发消息webAsyncTask")
    @ApiImplicitParam(name = "msg", example = "千树万树梨花开", required = true)
    @PostMapping("/server/sendMsg/webAsyncTask")
    public WebAsyncTask<String> sendMsgFromServer02(String msg)
        throws IOException
    {
        WebAsyncTask<String> webAsyncTask = new WebAsyncTask<String>(2000L, executor, () -> {
            Command command = new Command().setClientId(initManager.getClient().getClientName()).setText(msg);
            initManager.getServer().sendMsg(initManager.getClient().getClientName(), JsonBeanUtils.beanToJson(command));
            String result;
            for (int i = 0; i < 100; i++)
            {
                result = resultCallBack.queryResult(command.getCommandId());
                if (StringUtils.isNotBlank(result))
                {
                    return result;
                }
                else
                {
                    TimeUnit.MILLISECONDS.sleep(50);
                }
            }
            return "响应超时,请重试";
        });
        webAsyncTask.onCompletion(() -> log.info("调用完成"));
        webAsyncTask.onError(() -> {
            log.error("业务处理出错");
            return "error";
        });
        webAsyncTask.onTimeout(() -> {
            log.info("业务处理超时");
            return "Time Out";
        });
        return webAsyncTask;
    }
    
    /**
     * 客户端业务处理结果更多是通过回调实现的
     * 
     * @param cmd
     * @return
     * @throws IOException
     * @throws TimeoutException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @ApiOperation("server发消息")
    @ApiImplicitParam(name = "msg", example = "千树万树梨花开", required = true)
    @PostMapping("/server/sendMsg")
    public DeferredResult<String> sendMsgFromServer(String msg)
        throws IOException, InterruptedException, ExecutionException, TimeoutException
    {
        String clientName = initManager.getClient().getClientName();
        Command command = new Command().setClientId(clientName).setText(msg);
        initManager.getServer().sendMsg(clientName, JsonBeanUtils.beanToJson(command));
        
        // 异步返回结果
        DeferredResult<String> deferredResult = new DeferredResult<>(20000L, "失败");
        deferredResult.onCompletion(() -> log.info("调用完成"));
        deferredResult.onTimeout(() -> {
            log.info("调用超时");
            deferredResult.setResult("调用超时");
        });
        resultCallBack.processResult(deferredResult, command.getCommandId());
        return deferredResult;
    }
}
//goto src\main\java\com\fly\tcp\entity\Command.java
package com.fly.tcp.entity;

import java.util.UUID;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class Command
{
    /**
     * 命令id
     */
    private String commandId = UUID.randomUUID().toString();
    
    /**
     * 客户端id
     */
    private String clientId;
    
    /**
     * 具体命令内容
     */
    private String text;
}
//goto src\main\java\com\fly\tcp\service\ResultCallBack.java
package com.fly.tcp.service;

import java.io.IOException;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import com.fly.core.utils.JsonBeanUtils;
import com.fly.tcp.entity.Command;

import lombok.extern.slf4j.Slf4j;

/**
 * 结果回调处理
 */
@Slf4j
@Service
public class ResultCallBack implements Observer
{
    Map<String, String> result = new ConcurrentHashMap<>();
    
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    /**
     * 观察者接受数据处理
     */
    @Override
    public void update(Observable observable, Object msg)
    {
        log.info("### accept #### {}", msg);
        try
        {
            if (msg instanceof String)
            {
                String json = StringUtils.substringAfter((String)msg, "hello Command!");
                Command command = JsonBeanUtils.jsonToBean(json, Command.class);
                log.info("{}", command);
                result.put(command.getCommandId(), json);
            }
        }
        catch (IOException e)
        {
            log.error(e.getMessage());
        }
    }
    
    /**
     * 获取命令处理结果
     * 
     * @param commandId
     * @return
     */
    public String queryResult(String commandId)
    {
        return result.get(commandId);
    }
    
    /**
     * 业务线程处理业务,DeferredResult可以通过任何线程来计算返回一个结果
     * 
     * @param deferredResult
     * @param commandId
     * @throws TimeoutException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public void processResult(DeferredResult<String> deferredResult, String commandId)
        throws InterruptedException, ExecutionException, TimeoutException
    {
        processResult02(deferredResult, commandId);
    }
    
    /**
     * 无超时设置,不推荐
     * 
     * @param deferredResult
     * @param commandId
     */
    void processResult01(DeferredResult<String> deferredResult, String commandId)
    {
        executorService.execute(() -> {
            while (!result.containsKey(commandId))
            {
                try
                {
                    log.info("waitting......");
                    TimeUnit.MILLISECONDS.sleep(20);
                }
                catch (InterruptedException e)
                {
                }
            }
            deferredResult.setResult(result.get(commandId));
        });
    }
    
    /**
     * 有超时设置(推荐)
     * 
     * 
     * @param deferredResult
     * @param commandId
     * @throws InterruptedException
     * @throws ExecutionException
     * @throws TimeoutException
     */
    private void processResult02(DeferredResult<String> deferredResult, String commandId)
        throws InterruptedException, ExecutionException, TimeoutException
    {
        Future<String> future = executorService.submit(() -> {
            while (!result.containsKey(commandId))
            {
                log.info("waitting......");
                TimeUnit.MILLISECONDS.sleep(20);
            }
            return result.get(commandId);
        });
        String result = future.get(1000L, TimeUnit.MILLISECONDS);
        deferredResult.setResult(result);
    }
}
//goto src\main\resources\application-dev.yml
#设置日志级别
logging: 
  level:
    org:
      springframework:
        web: INFO
//goto src\main\resources\application.yml
server:
  port: 8081
  servlet:
    context-path: /
    session:
      timeout: 1800
spring:
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
  profiles:
    active:
    - test

#必须启用下面配置,否则接口排序失效
knife4j:
  enable: true

#设置日志级别
logging: 
  level:
    root: INFO
//goto src\test\java\com\fly\controller\ApiControllerTest.java
package com.fly.controller;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ApiControllerTest
{
    private static RestTemplate restTemplate;
    
    static
    {
        // 配置proxy,connectTimeout,readTimeout等参数
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        requestFactory.setConnectTimeout(1000);
        requestFactory.setReadTimeout(1000);
        restTemplate = new RestTemplate(requestFactory);
    }
    
    @Test
    public void testJsonRequestBody2()
        throws IOException
    {
        String url = "http://192.168.114.250:8080/api/post";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        
        Resource resource = new ClassPathResource("data/json");
        String text = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8);
        HttpEntity<String> requestEntity = new HttpEntity<>(text, headers);
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, requestEntity, String.class);
        log.info("******  ResponseEntity body: {}", responseEntity.getBody());
    }
}
//goto src\test\java\com\fly\executor\ExecutorServiceTest.java
package com.fly.executor;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ExecutorServiceTest
{
    private WebClient webClient = WebClient.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build();
    
    private ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new BasicThreadFactory.Builder().namingPattern("t-%03d").daemon(true).priority(Thread.MAX_PRIORITY).build());
    
    /**
     * 带超时条件线程池调用
     * 
     * @throws TimeoutException
     * @throws ExecutionException
     * @throws InterruptedException
     * 
     * @throws IOException
     */
    @Test
    public void testTimeout()
        throws InterruptedException, ExecutionException, TimeoutException
    {
        String ip = "192.168.0.1";
        Future<String> future = executorService.submit(() -> {
            return webClient.get()
                .uri(uriBuilder -> uriBuilder.scheme("http").host(ip).port("2375").path("/_ping").build())// URI
                .acceptCharset(StandardCharsets.UTF_8)
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(String.class)
                .block();
        });
        log.info("return: {}", future.get(1000L, TimeUnit.MILLISECONDS));
    }
}
//goto src\test\resources\log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
	<Properties>
		<Property name="LOG_EXCEPTION_CONVERSION_WORD">%xwEx</Property>
		<Property name="LOG_LEVEL_PATTERN">%5p</Property>
		<Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
		<Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{%pid}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
		<Property name="FILE_LOG_PATTERN">%d{${LOG_DATEFORMAT_PATTERN}} ${LOG_LEVEL_PATTERN} %pid --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
	</Properties>
	<Appenders>
		<Console name="Console" target="SYSTEM_OUT" follow="true">
			<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" />
		</Console>
	</Appenders>
	<Loggers>
		<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
		<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
		<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
		<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
		<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
		<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
		<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
		<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
		<Root level="info">
			<AppenderRef ref="Console" />
		</Root>
	</Loggers>
</Configuration>

五、运行界面



具体的部署和测试不再详细阐述。

六. 主要技术点

  1. 观察者模式
  2. 命令模式
  3. 线程池应用
  4. 异步Callable、WebAsyncTask、DeferredResult

有任何问题和建议,都可以向我提问讨论,大家一起进步,谢谢!

-over-

相关推荐
ChineHe2 分钟前
Golang并发编程篇001_并发编程相关概念解释
开发语言·后端·golang
自由的疯15 分钟前
java 怎么判断事务有无提交成功
java·后端·架构
bcbnb36 分钟前
Socket 抓包工具与实战,从抓取到定位(Socket 的命令、分析)
后端
用户83562907805138 分钟前
用Python轻松转换Excel表格为HTML格式
后端·python
用户084059812901 小时前
高版本的jdk在使用maven时,如何编译成低版本的class
后端
摇滚侠1 小时前
Spring Boot 3零基础教程,整合Redis,笔记12
spring boot·redis·笔记
荣淘淘1 小时前
互联网大厂Java求职面试全景实战解析(涵盖Spring Boot、微服务及云原生技术)
java·spring boot·redis·jwt·cloud native·microservices·interview
吃饭最爱1 小时前
spring高级知识概览
spring boot
这里有鱼汤1 小时前
炒股的尽头真的是玄学?我用八字+AI做了个实验,结果震惊
后端
hrrrrb1 小时前
【Spring Security】认证(二)
java·后端·spring