Feign的功能
主要功能是讓開(kāi)發(fā)人員只使用簡(jiǎn)單注解就能像調(diào)用一般RPC一樣調(diào)用HTTP請(qǐng)求,在此基礎(chǔ)上荧止,框架擴(kuò)展了原SpringMVC參數(shù)只能為一個(gè)類的功能。
基本思路
代理實(shí)現(xiàn)->bean的注冊(cè)->多參數(shù)實(shí)現(xiàn)
代碼實(shí)現(xiàn)
注解定義
需要定義三個(gè)注解。
第一個(gè)“HttpConsumer”奈揍,用于消費(fèi)端注冊(cè)服務(wù)绅项。
/**
* 標(biāo)注需要代理為http請(qǐng)求發(fā)送
*
* @author kun
* @data 2022/1/15 17:50
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpConsumer {
/**
* 域名
*
* @return 域名
*/
String domain();
/**
* 端口紊册,默認(rèn)80
*
* @return 端口號(hào)
*/
String port() default "80";
}
第二個(gè)“HttpRequest”,用于服務(wù)提供方定義服務(wù)快耿。
/**
* 用于標(biāo)記可以通過(guò)http訪問(wèn)的服務(wù)
*
* @author kun
* @data 2022/1/15 17:55
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpRequest {
/**
* http請(qǐng)求url
*
* @return url
*/
String value();
}
第三個(gè)“MultiRequestBody”囊陡,用于實(shí)現(xiàn)多變量傳參。
/**
* 標(biāo)注方法參數(shù)
*
* @author kun
* @data 2022/1/15 17:57
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiRequestBody {
/**
* 解析參數(shù)時(shí)用到JSON中的key
*
* @return JSON格式參數(shù)
*/
String value();
}
動(dòng)態(tài)代理
創(chuàng)建代理的目的是將方法轉(zhuǎn)化為http調(diào)用掀亥。
/**
* HttpConsumer注解代理類
*
* @author kun
* @data 2022/1/15 19:30
*/
public class HttpConsumerInterceptor implements MethodInterceptor {
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String HTTP_HEAD = "http://";
private final Class<?> proxyKlass;
private final HttpDomain httpDomain;
private final OkHttpClient okHttpClient;
public HttpConsumerInterceptor(Class<?> proxyKlass, HttpDomain httpDomain, OkHttpClient okHttpClient) {
this.proxyKlass = proxyKlass;
this.httpDomain = httpDomain;
this.okHttpClient = okHttpClient;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
HttpRequest httpRequest = method.getAnnotation(HttpRequest.class);
if (Objects.isNull(httpRequest)) {
throw new IllegalStateException("method[" + method.getName() + "] must annotated with @HttpRequest!");
}
HttpRequest klassAnnotation = proxyKlass.getAnnotation(HttpRequest.class);
String namespace = Objects.isNull(klassAnnotation) ? null : klassAnnotation.value();
String url = getUrl(httpRequest.value(), namespace);
Request request = buildRequest(method, args, url);
Call call = okHttpClient.newCall(request);
Response response = call.execute();
ResponseBody body = response.body();
if (Objects.isNull(body)) {
return null;
}
byte[] bytes = body.bytes();
String res = new String(bytes, StandardCharsets.UTF_8);
if (StringUtils.isEmpty(res)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(res, method.getReturnType());
} catch (Throwable t) {
Map<?, ?> map = OBJECT_MAPPER.readValue(res, Map.class);
Object err = map.get("error");
if (Objects.nonNull(err)) {
throw new RuntimeException(err.toString());
}
throw new RuntimeException(t);
}
}
private String getUrl(String requestPath, String namespace) {
if (Objects.isNull(namespace)) {
return HTTP_HEAD + httpDomain.getDomain() + ":" + httpDomain.getPort() + "/" + requestPath;
}
return HTTP_HEAD + httpDomain.getDomain() + ":" + httpDomain.getPort() + "/" + namespace + "/" + requestPath;
}
private Request buildRequest(Method method, Object[] args, String url) throws JsonProcessingException {
Request.Builder builder = new Request.Builder();
Map<String, Object> paramMap = new HashMap<>(4);
Parameter[] parameters = method.getParameters();
for (int i=0; i<parameters.length; i++) {
Parameter parameter = parameters[i];
MultiRequestBody multiRequestBody = parameter.getAnnotation(MultiRequestBody.class);
if (Objects.isNull(multiRequestBody)) {
throw new IllegalStateException("method[" + method.getName() + "] param must annotated with @MultiRequest!");
}
paramMap.put(multiRequestBody.value(), args[i]);
}
// 將參數(shù)構(gòu)建為一個(gè)大JSON
String param = OBJECT_MAPPER.writeValueAsString(paramMap);
RequestBody requestBody = RequestBody.create(param, JSON);
builder.post(requestBody);
builder.url(url);
return builder.build();
}
}
自定義FactoryBean
使用FactoryBean生成bean
/**
* @author kun
* @data 2022/1/16 14:50
*/
public class HttpConsumerProxyFactoryBean implements FactoryBean<Object> {
private final Class<?> proxyKlass;
private final HttpDomain httpDomain;
private final OkHttpClient okHttpClient;
public HttpConsumerProxyFactoryBean(Class<?> proxyKlass, HttpDomain httpDomain, OkHttpClient okHttpClient) {
this.proxyKlass = proxyKlass;
this.httpDomain = httpDomain;
this.okHttpClient = okHttpClient;
}
@Override
public Object getObject() {
Enhancer enhancer = new Enhancer();
if (proxyKlass.isInterface()) {
enhancer.setInterfaces(new Class[]{proxyKlass});
} else {
enhancer.setSuperclass(proxyKlass);
}
HttpConsumerInterceptor httpConsumerInterceptor = new HttpConsumerInterceptor(proxyKlass, httpDomain, okHttpClient);
enhancer.setCallback(httpConsumerInterceptor);
return enhancer.create();
}
@Override
public Class<?> getObjectType() {
return proxyKlass;
}
@Override
public boolean isSingleton() {
return true;
}
}
注冊(cè)bean
使用BeanDefinition方式注冊(cè)bean
/**
* @author kun
* @data 2022/1/16 13:59
*/
@EnableConfigurationProperties(HttpConsumerProperties.class)
public class HttpConsumerPostProcessor implements BeanClassLoaderAware, EnvironmentAware, BeanFactoryPostProcessor, ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(HttpConsumerPostProcessor.class);
private ClassLoader classLoader;
private ApplicationContext context;
private ConfigurableEnvironment environment;
private ConfigurableListableBeanFactory beanFactory;
private final Map<String, BeanDefinition> httpClientBeanDefinitions = new HashMap<>(4);
private OkHttpClient okHttpClient;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
this.beanFactory = configurableListableBeanFactory;
this.okHttpClient = buildOkHttpClient(environment);
postProcessBeanFactory(beanFactory, (BeanDefinitionRegistry) beanFactory);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
private OkHttpClient buildOkHttpClient(ConfigurableEnvironment environment) {
HttpConsumerProperties properties = BinderUtils.bind(environment, HttpConsumerProperties.PREFIX, HttpConsumerProperties.class);
OkHttpClient.Builder builder = new OkHttpClient.Builder();
ThreadPoolExecutor executor = new ThreadPoolExecutor(properties.getCoreThreads(), properties.getMaxThreads(),
properties.getKeepAliveTime(), TimeUnit.SECONDS, new SynchronousQueue<>(), Util.threadFactory("OkHttp Custom Dispatcher", false));
Dispatcher dispatcher = new Dispatcher(executor);
dispatcher.setMaxRequests(properties.getMaxRequests());
dispatcher.setMaxRequestsPerHost(properties.getMaxRequests());
builder.dispatcher(dispatcher);
builder.connectTimeout(properties.getConnectTimeOut(), TimeUnit.SECONDS);
builder.readTimeout(properties.getReadTimeOut(), TimeUnit.SECONDS);
builder.writeTimeout(properties.getWriteTimeOut(), TimeUnit.SECONDS);
builder.connectionPool(new ConnectionPool(properties.getMaxIdleConnections(), properties.getConnectionKeepAliveTime(), TimeUnit.SECONDS));
return builder.build();
}
private void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory, BeanDefinitionRegistry registry) {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
String beanClassName = definition.getBeanClassName();
// 當(dāng)用@Bean 返回的類型是Object時(shí)撞反,beanClassName是null
if (Objects.isNull(beanClassName)) {
continue;
}
Class<?> clazz = ClassUtils.resolveClassName(definition.getBeanClassName(), this.classLoader);
ReflectionUtils.doWithFields(clazz, this::parseElement, this::annotatedWithHttpConsumer);
}
for (String beanName : httpClientBeanDefinitions.keySet()) {
if (context.containsBean(beanName)) {
throw new IllegalArgumentException("[HttpConsumer Starter] Spring context already has a bean named " + beanName
+ ", please change @HttpConsumer field name.");
}
registry.registerBeanDefinition(beanName, httpClientBeanDefinitions.get(beanName));
logger.info("registered HttpConsumer factory bean \"{}\" in spring context.", beanName);
}
}
private void parseElement(Field field) {
Class<?> interfaceClass = field.getType();
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("field [" + field.getName() + "] annotated with @HttpConsumer must be interface!");
}
HttpConsumer httpConsumer = AnnotationUtils.getAnnotation(field, HttpConsumer.class);
HttpDomain httpDomain = HttpDomain.from(httpConsumer);
// 支持占位符${}
httpDomain.setDomain(beanFactory.resolveEmbeddedValue(httpDomain.getDomain()));
httpDomain.setPort(beanFactory.resolveEmbeddedValue(httpDomain.getPort()));
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(HttpConsumerProxyFactoryBean.class)
.addConstructorArgValue(interfaceClass)
.addConstructorArgValue(httpDomain)
.addConstructorArgValue(okHttpClient);
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.setPrimary(true);
beanDefinition.setAutowireCandidate(true);
httpClientBeanDefinitions.put(field.getName(), beanDefinition);
}
private boolean annotatedWithHttpConsumer(Field field) {
return field.isAnnotationPresent(HttpConsumer.class);
}
}
多參數(shù)適配
主要是多SpringMVC的參數(shù)進(jìn)行配置
/**
* 參數(shù)解析
*
* @author kun
* @data 2022/1/15 21:06
*/
public class MultiRequestBodyResolver implements HandlerMethodArgumentResolver {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String JSON_REQUEST_BODY = "JSON_REQUEST_BODY";
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(MultiRequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
String requestBody = getRequestBody(nativeWebRequest);
OBJECT_MAPPER.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
JsonNode rootNode = OBJECT_MAPPER.readTree(requestBody);
if (StringUtils.isEmpty(rootNode)) {
throw new IllegalArgumentException("requestBody must not empty!");
}
MultiRequestBody multiRequestBody = methodParameter.getParameterAnnotation(MultiRequestBody.class);
if (Objects.isNull(multiRequestBody)) {
throw new IllegalArgumentException("param must annotated with @MultiRequestBody!");
}
String key = !StringUtils.isEmpty(multiRequestBody.value()) ? multiRequestBody.value() : methodParameter.getParameterName();
JsonNode value = rootNode.get(key);
if (Objects.isNull(value)) {
return null;
}
Class<?> paramType = methodParameter.getParameterType();
return OBJECT_MAPPER.readValue(value.toString(), paramType);
}
/**
*
* 獲取請(qǐng)求體的JSON字符串
*/
private String getRequestBody(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSON_REQUEST_BODY, NativeWebRequest.SCOPE_REQUEST);
if (!StringUtils.isEmpty(jsonBody)) {
return jsonBody;
}
try (BufferedReader reader = servletRequest.getReader()) {
jsonBody = IOUtils.toString(reader);
webRequest.setAttribute(JSON_REQUEST_BODY, jsonBody, NativeWebRequest.SCOPE_REQUEST);
return jsonBody;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* JSON解析數(shù)據(jù)
*
* @author kun
* @data 2022/1/15 21:03
*/
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
/**
* 參數(shù)解析器
*
* @param list 解析器
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
list.add(new MultiRequestBodyResolver());
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
LinkedList<MediaType> mediaTypes = new LinkedList<>();
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
jacksonConverter.setSupportedMediaTypes(mediaTypes);
jacksonConverter.setDefaultCharset(StandardCharsets.UTF_8);
list.add(jacksonConverter);
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
編寫為Spring starter
/**
* 服務(wù)調(diào)用方生效
*
* @author kun
* @data 2022/1/16 15:36
*/
@ConditionalOnClass(OkHttpClient.class)
public class HttpClientAuthConfig {
@Bean
@ConditionalOnMissingBean
public HttpConsumerPostProcessor httpConsumerPostProcessor() {
return new HttpConsumerPostProcessor();
}
}
/**
* 服務(wù)提供方生效
*
* @author kun
* @data 2022/1/16 15:40
*/
@ConditionalOnClass(WebMvcConfigurer.class)
public class SpringMvcConfigurerAutoConfig {
@Bean
@ConditionalOnMissingBean
public WebMvcConfig webMvcConfig() {
return new WebMvcConfig();
}
@Bean
@ConditionalOnMissingBean
public HttpRequestValidator httpRequestValidator() {
return new HttpRequestValidator();
}
}
測(cè)試
服務(wù)提供方
/**
* @author kun
* @data 2022/1/22 14:25
*/
@HttpRequest("demo")
public interface DemoHttpService {
@HttpRequest("checkSuccess")
String checkSuccess(@MultiRequestBody("param1") String param1, @MultiRequestBody("param2") String param2);
}
服務(wù)調(diào)用方
/**
* @author kun
* @data 2022/1/22 14:37
*/
@Configuration
public class HttpConfig {
@HttpConsumer(domain = "localhost", port = "8080")
private DemoHttpService demoHttpService;
}
/**
* @author kun
* @data 2022/1/22 14:40
*/
@Component
public class DemoConsumer {
@Resource
private DemoHttpService demoHttpService;
public String checkSuccess() {
return demoHttpService.checkSuccess("param1", "param2");
}
}