当前位置:网站首页>Customization of openfeign

Customization of openfeign

2022-06-23 03:22:00 eeaters

feign Custom use

  • Project background
  • Basic patterns and processes
  • introduce OpenFeign
  • Feign The custom of
    • Encoder( The signature of the )
    • RequestInterceptor(Header Pass on )
    • Decoder( Unified decoding )

Project background

The company's original model was to provide customers with unified functions ; But demand cannot meet the needs of all customers ; Because all walks of life are very voluminous , The customer feels that they are not valued , The customer will quit , So the company changed its game : The function is open to the outside world , Customers don't think your requirements are simple , you can you up, no can no bibi;

Basic patterns and processes

Is to provide the current business capabilities , Expose one maven Dependence , Customer developers can have default business functions by introducing dependencies ;

The basic process is : Front end calls ( Can be customized ) → Customer service ( As the back-end , Mainly for the first layer ) → Call the midrange gateway ( New layer 1 Gateway ) → Internal service

introduce OpenFeign

  • The internal services of the company and the calls between services use OpenFeign
  • Use OpenFeign Time code is simple enough
  • Company developers use OpenFeign Thief slip ( Although they don't know contextId What is it? , Encounter two services with the same name FeignClient I can't help it )

It may be based on the above reasons , It may also be that the project time is tight , The person who claps the board just slaps his head , So use OpenFeign The matter was settled happily ;

Feign The custom of

But there are some problems in the process of using , Because I used to OpenFeign I also have a certain understanding , Therefore, it solves some problems encountered in the process of use OpenFeign The problem of ; Take a brief note of ;

Encoder( The signature of the )

If the interface is exposed to the public, it will be signed , You need to have a key when connecting to a third party /token And so on. ; Both identify the caller as much as possible , To prevent malicious attacks

Purpose of use

Use FeignClient when , Parameters need a package layer when called , Look directly at the entity class

public class AppBaseRequest {
    private String ver;
    private String partnerId;
    private String appId;
    // The object you think you are just passing is actually a property field in the object 
    private String requestBody;
    private String sign;
}

Solution

Because the first time I met the parameter transfer, I changed the parameter completely , therefore debug It was a coincidence , It may not be a good plan , But it works well

@Configuration
@EnableConfigurationProperties(SignatureProperties.class)
public class AppletAutoConfiguration {

    @Bean
    public EdenSignature edenSignature(SignatureProperties signatureProperties) {
        return new DefaultEdenSignature(signatureProperties);
    }
    
    // default SpringEncoder Injection has ConditionOnMissionBean;  With this, the default is gone 
    @Bean
    public AppletBossEncoder feignEncoder(ObjectFactory<HttpMessageConverters> messageConverters, EdenSignature edenSignature){
        return new AppletBossEncoder(messageConverters, edenSignature);
    }
}



public class AppletBossEncoder extends SpringEncoder {
​
    // The extra lot was drawn ; If RestTemplate Calls can also be used to 
    private final EdenSignature edenSignature;
​
    public AppletBossEncoder(ObjectFactory<HttpMessageConverters> messageConverters,
                             EdenSignature edenSignature) {
        super(messageConverters);
        this.edenSignature = edenSignature;
    }
​
    @Override
    public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
        if (!AppBaseRequest.class.getName().equals(bodyType.getTypeName())) {
            //requestBody The original type is  T ;  After signing, it must be AppBaseRequest
            requestBody = edenSignature.sign(requestBody);
            bodyType = requestBody.getClass();
        }
        super.encode(requestBody, bodyType, request);
    }
}

RequestInterceptor(Header Pass on )

Because a lot of information on the front end was put into header What's going on inside ; One more layer of services , that header May be lost ; because ServletRequest It is stored in a local thread variable ; Then we'll just take it out and pass it back

The code is simple ; But there is one detail to understand : Feign When initializing, first find your own context Config ; If so, use your own , If not, just call parent Contextual ( The parent context is the global configuration ) ; The following code must be placed in a global configuration ; namely : This Bean You need to mark on the class @Configuration

    @Bean
    public RequestInterceptor headerPassInterceptor() {
        return requestTemplate -> {
            HttpServletRequest servletRequest = RequestUtils.currentServletRequest();
            // Allow to return null ;  Prevent this feign The call is not passed from the front end ;  Such as running a scheduled task , There is no context at all ServletRequest
            if (servletRequest != null) {
                Enumeration<String> headerNames = servletRequest.getHeaderNames();
                while (headerNames.hasMoreElements()) {
                    String headerName = headerNames.nextElement();
                    if (!EXCLUDE_HEADER.contains(headerName)) {
                        Enumeration<String> headValue = servletRequest.getHeaders(headerName);
                        requestTemplate.header(headerName, Collections.list(headValue));
                    }
                }
            }
        };
    }

Decoder( Unified decoding )

This is a problem encountered in previous projects ; We have connected more than a dozen services , Many of their return messages are different ; such as

  • The error codes are code,statusCode,status;
  • The returned entity classes are result,data
  • The returned error messages are message, Yes errorMessage

So ,

  • Every time an old project executes a remote call, it must judge the success in the business code ? When it succeeds, the entity class is retrieved ,
  • The new project draws a layer , Package names vary , Is to deal with this judgment , Only successful entity objects are returned

Uncle can bear it , My aunt couldn't bear it ; Just read the decoded code , Try to change it :

The effect of hope

A lot of services have been connected ; But I can use one Result<T> Accept , Isn't it more comfortable to use .( Actually write code , I found it could be a little more awesome )

Uniform code extraction

Because every one of them FeignClient May have different return values , So a Decoder I can't go all the way to fill in

public abstract class AbstractCustomDecoder<T> extends Decoder.Default {
​
    protected static final String DEFAULT_SUCCESS_CODE = "200";
​
    private final Class<T> tClass;
​
    protected AbstractCustomDecoder() {
        tClass = retrieveAnnotationType();
        if (tClass == null || ResponseEntity.class.equals(tClass)) {
            throw new EdenAbstractException();
        }
    }
​
​
    @Override
    public Object decode(Response response, Type type) throws IOException {
        return customDecoder(response, type);
    }
​
    protected Object customDecoder(Response response, Type type) throws IOException {
        Class<?> rawClass = ResolvableType.forType(type).getRawClass();
        String body = Util.toString(response.body().asReader());
​
        T obj = JSONObject.parseObject(body, tClass);
        if (rawClass.equals(tClass)) {
            return obj;
        }
​
        Object data = getData(obj).get();
        if (type instanceof ParameterizedTypeImpl) {
            ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) type;
            if (parameterizedType.getActualTypeArguments() != null) {
                Type typeArgument = parameterizedType.getActualTypeArguments()[0];
                data = JSONObject.parseObject(JSONObject.toJSONString(getData(obj).get()), typeArgument);
            }
        }
        if (String.class.equals(type)) {
            return String.valueOf(data);
        }
​
        String code = getCode(obj).get();
        String message = getMessage(obj).get();
        Boolean status = getStatus(obj).get();
​
        return convertResult(rawClass, message, data, status, code);
​
    }
​
    protected Object convertResult(Class<?> rawClass, String message, Object data, Boolean status,String code) {
        if (Result.class.equals(rawClass)) {
            return new Result<>(status, message, data, code);
        }
        return JSONObject.parseObject(JSONObject.toJSONString(data), rawClass);
    }
​
​
​
    protected abstract Supplier<Object> getData(T obj);
    protected abstract Supplier<String> getCode(T obj);
    protected abstract Supplier<String> getMessage(T obj);
​
    protected Supplier<Boolean> getStatus(T obj) {
        return () -> {
            String code = getCode(obj).get();
            return DEFAULT_SUCCESS_CODE.equals(code);
        };
    }
​
    /**
     *  Retrieve the actual type corresponding to the generic 
     * @return
     */
    private Class<T> retrieveAnnotationType(){
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) {
                if (actualTypeArgument instanceof Class) {
                    return (Class) actualTypeArgument;
                }
            }
        }
        return null;
    }
​
}

Demo Of config

Be sure to pay attention to , The method here has @Bean; But there is no class @Configuration; In this way, the user can configure it

public class CustomDecoderConfig {
​
​
    @Data
    public static class BaiduResult<T>{
        private String status;
        private T result;
    }
​
​
    @Bean
    public Decoder decoder() {
        return new AbstractCustomDecoder<BaiduResult>() {
            @Override
            protected Supplier<Object> getData(BaiduResult obj) {
                return obj::getResult;
            }
​
            @Override
            protected Supplier<String> getCode(BaiduResult obj) {
                return obj::getStatus;
            }
​
            @Override
            protected Supplier<String> getMessage(BaiduResult obj) {
                return () -> JSONObject.toJSONString(obj.getResult());
            }
​
            @Override
            protected Supplier<Boolean> getStatus(BaiduResult obj) {
                return () -> "OK".equals(obj.getStatus());
            }
​
            @Override
            protected Object convertResult(Class<?> rawClass, String message, Object data, Boolean status, String code) {
                if (BaiduResult.class.equals(rawClass)) {
                    BaiduResult cityResponse = new BaiduResult();
                    cityResponse.setStatus(code);
                    cityResponse.setResult(data);
                    return cityResponse;
                }
                return super.convertResult(rawClass, message, data, status, code);
            }
        };
    }
}

Effect display

@EnableFeignClients(clients = AlaBossDecoderExample.BaiduLbsClient.class)
public class AlaBossDecoderExample {
​
    @FeignClient(contextId = "demo.baiduLbs",
            name = "baiduLbs",
            url = "http://api.map.baidu.com",
            configuration = CustomDecoderConfig.class)
    public interface BaiduLbsClient {
        /**
         *  Native writing 
         */
        @GetMapping("/geocoder")
        CustomDecoderConfig.BaiduResult<LocationInfo>  resultGeocoder(@RequestParam("location") String location, @RequestParam("output") String output);
​
        /**
         * String  Accept 
         */
        @GetMapping("/geocoder")
        String stringGeocoder(@RequestParam("location") String location, @RequestParam("output") String output);
​
        /**
         *  We only accept Data Internal required entity objects 
         */
        @GetMapping("/geocoder")
        LocationInfo dataGeocoder(@RequestParam("location") String location, @RequestParam("output") String output);
​
        /**
         *  Standard acceptance method 
         */
        @GetMapping("/geocoder")
        Result<LocationInfo> standardGeocoder(@RequestParam("location") String location, @RequestParam("output") String output);
    }
​
​
​
    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AlaBossDecoderExample.class,
                FeignAutoConfiguration.class,
                HttpMessageConvertersAutoConfiguration.class
        );
        context.refresh();
​
        AlaBossDecoderExample.BaiduLbsClient lbsClient = context.getBean(AlaBossDecoderExample.BaiduLbsClient.class);
        String rest = lbsClient.stringGeocoder("39.983424,116.322987", "json");
        System.out.println("rest = " + rest);
​
        LocationInfo json = lbsClient.dataGeocoder("39.983424,116.322987", "json");
        System.out.println("json = " + json);
​
        CustomDecoderConfig.BaiduResult<LocationInfo> json1 = lbsClient.resultGeocoder("39.983424,116.322987", "json");
        System.out.println("json1 = " + json1);
​
        Result<LocationInfo> json2 = lbsClient.standardGeocoder("39.983424,116.322987", "json");
        System.out.println("json2 = " + ToStringBuilder.reflectionToString(json2));
​
        context.close();
    }
}

Show the effect

Actually exceeded expectations , A new way to play , Casually accept ….

  • It is physically acceptable
  • The original data format is acceptable
  • The specified data format can be consigned
  • String acceptable
rest = {"formatted_address":" Zhongguancun Street, Haidian District, Beijing 27 Number 1101-08 room ","business":" Zhongguancun , Renmin University of China , Suzhou Street ","cityCode":131,"location":{"lng":116.322987,"lat":39.983424},"addressComponent":{"distance":"7","province":" The Beijing municipal ","city":" The Beijing municipal ","street":" Zhongguancun Street ","district":" Haidian District ","street_number":"27 Number 1101-08 room ","direction":"near"}}
​
json = AlaBossDecoderExample.LocationInfo(location=AlaBossDecoderExample.LocationInfo.Location(lng=116.322987, lat=39.983424), formatted_address= Zhongguancun Street, Haidian District, Beijing 27 Number 1101-08 room , business= Zhongguancun , Renmin University of China , Suzhou Street , cityCode=131, addressComponent=AlaBossDecoderExample.LocationInfo.AddressComponent(city= The Beijing municipal , direction= near , distance=7, district= Haidian District , province= The Beijing municipal , street= Zhongguancun Street , street_number=27 Number 1101-08 room ))
​
json1 = CustomDecoderConfig.BaiduResult(status=OK, result={"formatted_address":" Zhongguancun Street, Haidian District, Beijing 27 Number 1101-08 room ","business":" Zhongguancun , Renmin University of China , Suzhou Street ","cityCode":131,"location":{"lng":116.322987,"lat":39.983424},"addressComponent":{"distance":"7","province":" The Beijing municipal ","city":" The Beijing municipal ","street":" Zhongguancun Street ","district":" Haidian District ","street_number":"27 Number 1101-08 room ","direction":"near"}})
​
json2 = [email protected][status=true,message={"formatted_address":" Zhongguancun Street, Haidian District, Beijing 27 Number 1101-08 room ","business":" Zhongguancun , Renmin University of China , Suzhou Street ","cityCode":131,"location":{"lng":116.322987,"lat":39.983424},"addressComponent":{"distance":"7","province":" The Beijing municipal ","city":" The Beijing municipal ","street":" Zhongguancun Street ","district":" Haidian District ","street_number":"27 Number 1101-08 room ","direction":" near "}},result={"formatted_address":" Zhongguancun Street, Haidian District, Beijing 27 Number 1101-08 room ","business":" Zhongguancun , Renmin University of China , Suzhou Street ","cityCode":131,"location":{"lng":116.322987,"lat":39.983424},"addressComponent":{"distance":"7","province":" The Beijing municipal ","city":" The Beijing municipal ","street":" Zhongguancun Street ","district":" Haidian District ","street_number":"27 Number 1101-08 room ","direction":" near "}},statusCode=OK]
原网站

版权声明
本文为[eeaters]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/01/202201191736271572.html