欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > SpringBoot使用泛型出入参+策略模式+反射+缓存实现统一POST接口入口

SpringBoot使用泛型出入参+策略模式+反射+缓存实现统一POST接口入口

2024/11/30 6:42:12 来源:https://blog.csdn.net/weixin_43933728/article/details/140834485  浏览:    关键词:SpringBoot使用泛型出入参+策略模式+反射+缓存实现统一POST接口入口

简介

某些情况下需要统一入口,如:提供给第三方调用的接口等。减少接口对接时的复杂性。

代码实现

  1. GenericController.java
    统一入口,通过bean name进行调用service层invoke方法
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;import java.lang.reflect.InvocationTargetException;
import java.util.Map;@Slf4j
@RestController
@RequestMapping("api")
@RequiredArgsConstructor
public class GenericController {private final ObjectMapper objectMapper;@PostMapping("/{serviceName}")public Object invokeService(@PathVariable String serviceName, @RequestBody(required = false) Map<String, Object> requestBody) throws InvocationTargetException, IllegalAccessException {
//        log.info("{}接口入参:{}", serviceName, requestBody);// 从缓存获取ServiceInfoServiceInfo<?> serviceInfo = ServiceCacheUtils.cache.get(serviceName);//这里可以进行判空,但是没必要。没有的就让它抛异常。// 将请求参数转换为具体的类型Object requestObject = objectMapper.convertValue(requestBody, serviceInfo.getRequestType());// 调用Service的invoke方法并获取返回值Object responseObject = serviceInfo.invoke(requestObject);
//        log.info("{}接口出参:{}", serviceName, responseObject);return responseObject;}}
  1. GenericService.java
/*** 通用接口*/
public interface GenericService<T> {/*** 通用方法*/Object invoke(T request);
}
  1. UserGenericServiceImpl.java
    实现通用接口用户service层类
public class UserGenericServiceImpl implements GenericService<UserDTO> {@Overridepublic User invoke(UserDTO dto) {log.info("UserDTO:{}", dto);User user = new User();user.setId(1L);user.setName("Meta39");return user;}}
  1. HelloWorldGenericServiceImpl.java
    实现通用接口打招呼service层类
@Slf4j
@Service("helloWorld")
public class HelloWorldGenericServiceImpl implements GenericService<GenericServiceDTO> {@Overridepublic String invoke(GenericServiceDTO dto) {log.info("GenericServiceDto: {}", dto);return "Hello World";}}
  1. ServiceInfo.java
    缓存存储反射调用的类和请求参数、返回参数实体类
@Getter
@AllArgsConstructor
public class ServiceInfo<T> {private final GenericService<T> service;private final Method method;private final Class<T> requestType;public Object invoke(Object requestObject) throws IllegalAccessException, InvocationTargetException {return method.invoke(service, requestObject);}
}
  1. GenericServiceDto.java
    第1个数据传输对象看看泛型入参是否可用
@Data
public class GenericServiceDTO {private String name;
}
  1. UserDTO.java
    第2个数据传输对象看看泛型入参是否可用
@Data
public class UserDTO {private Integer id;
}
  1. ServiceCacheUtils.java
    把 spring bean 放入缓存并存储对应的请求类型,这样就可以知道每个 GenericService 接口的实现类具体的泛型请求类型是什么。
/*** 缓存创建的 bean* @since 2024-07-16*/
public abstract class ServiceCacheUtils {private ServiceCacheUtils() {}public static ConcurrentHashMap<String, ServiceInfo<?>> cache = new ConcurrentHashMap<>();}
  1. ApplicationContextUtils.java
    获取 bean 工具类
/*** 获取Bean*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware {//构造函数私有化,防止其它人实例化该对象private ApplicationContextUtils() {}private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {ApplicationContextUtils.applicationContext = applicationContext;}//通过name获取 Bean.(推荐,因为bean的name是唯一的,出现重名的bean启动会报错。)public static Object getBean(String name) {return applicationContext.getBean(name);}//通过class获取Bean.(确保bean的name不会重复。因为可能会出现在不同包的同名bean导致获取到2个实例)public static <T> T getBean(Class<T> clazz) {return applicationContext.getBean(clazz);}//通过name,以及Clazz返回指定的Bean(这个是最稳妥的)public static <T> T getBean(String name, Class<T> clazz) {return applicationContext.getBean(name, clazz);}public static String[] getBeanNamesForType(Class<?> type) {return applicationContext.getBeanNamesForType(type);}}
  1. ServiceCacheApplicationRunner.java
    SpringBoot启动完成后立马把实现了GenericService的类的bean name 和ServiceInfo存储的请求参数类型写入缓存
/*** 启动后把所有实现了 GenericService 接口的类写入缓存。这样在调用方法的时候就可以直接获取类进行方法调用。** @since 2024-07-16*/
@Component
public class ServiceCacheApplicationRunner implements ApplicationRunner {@Override@SuppressWarnings("unchecked")public void run(ApplicationArguments args) throws NoSuchMethodException {String[] beanNames = ApplicationContextUtils.getBeanNamesForType(GenericService.class);for (String beanName : beanNames) {GenericService<Object> service = (GenericService<Object>) ApplicationContextUtils.getBean(beanName);Type[] genericInterfaces = service.getClass().getGenericInterfaces();ParameterizedType parameterizedType = (ParameterizedType) genericInterfaces[0];Class<Object> requestType = (Class<Object>) parameterizedType.getActualTypeArguments()[0];Method method = service.getClass().getMethod("invoke", requestType);// 显式类型转换ServiceInfo<Object> serviceInfo = new ServiceInfo<>(service, method, requestType);//写入缓存ServiceCacheUtils.cache.put(beanName, serviceInfo);}}}

测试

helloWorld

在这里插入图片描述
控制台输出

2024-07-31 23:22:47.204  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.s.s.i.HelloWorldGenericServiceImpl   : GenericServiceDto: GenericServiceDTO(name=哈哈哈哈哈)
2024-07-31 23:22:47.212  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.springboot3demo.filter.GlobalFilter  : 请求内容:
method: POST
uri: /api/helloWorld
request: { "name":"哈哈哈哈哈" }
2024-07-31 23:22:47.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-1] c.f.springboot3demo.filter.GlobalFilter  : 响应内容:
status: 200
response: Hello World

user

在这里插入图片描述
控制台输出

2024-07-31 23:24:46.199  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.s.s.impl.UserGenericServiceImpl      : UserDTO:UserDTO(id=1)
2024-07-31 23:24:46.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.springboot3demo.filter.GlobalFilter  : 请求内容:
method: POST
uri: /api/user
request: { "id":1 }
2024-07-31 23:24:46.213  INFO 10348 --- [spring-boot3-demo] [omcat-handler-4] c.f.springboot3demo.filter.GlobalFilter  : 响应内容:
status: 200
response: {"id":1,"name":"Meta39"}

注意事项

这种方式实现的统一入口,暂时发现一个弊端,没法使用Spring validation 参数校验框架,否则会抛异常。但是可以通过Spring Assert在代码里判断。

@Slf4j
@Service("helloWorld")
public class HelloWorldGenericServiceImpl implements GenericService<GenericServiceDTO> {@Overridepublic String invoke(GenericServiceDTO dto) {log.info("GenericServiceDto: {}", dto);//如下所示Assert.notNull(dto, "请求体不能为空");return "Hello World";}}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com