Feign is used for remote call in the project, and some scenarios need to dynamically configure the header
The initial approach is to realize dynamic header configuration through @ RequestHeader setting parameters
For example:
@GetMapping(value = "/test", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE}) String access(@RequestHeader("Auth") String auth, @RequestBody Expression expression);
Although this method can achieve the dynamic configuration of header, it will reduce the interface availability when there are too many parameters, so you want to set the header by passing bean s
First, the solution:
public class HeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { byte[] bytes = requestTemplate.requestBody().asBytes(); Identity identity = JSONObject.parseObject(bytes, Identity.class); requestTemplate.header("Auth", identity.getSecret()); } } /** * configuration Specify Interceptor **/ @FeignClient(name = "test", url = "127.0.0.1:8300", configuration = HeaderInterceptor.class) public interface GolangTestHandle2 { @GetMapping(value = "/handler", consumes = {MediaType.APPLICATION_JSON_UTF8_VALUE}) String handle(Identity identity); }
The user-defined Interceptor implements the RequestInterceptor interface. The callback method apply provides the RequestTemplate object, which encapsulates all the information of the request. Finally, the interface is specified through configuration, and then you can play whatever you like (for example, get the interface parameters through the body and set the header dynamically)
It is worth noting that the HeaderInterceptor will take effect globally if it is injected into the spring boot container. That is to say, if the configuration is not specified in time, it will also take effect on the global feign interface. Why? Here's a brief explanation
First, Feign creates a springcontext context for each feign class
spring obtains feign factory instance by calling getObject
@Override public Object getObject() throws Exception { return getTarget(); }
Internal call to FeignClientFatoryBean.getTarget() method
<T> T getTarget() { //Get feign context FeignContext context = this.applicationContext.getBean(FeignContext.class); //Build feign Builder Feign.Builder builder = feign(context); ... }
Build Builder according to feign (feign context)
protected Feign.Builder feign(FeignContext context) { FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); Logger logger = loggerFactory.create(this.type); // @formatter:off Feign.Builder builder = get(context, Feign.Builder.class) // required values .logger(logger) //Default springEncoder .encoder(get(context, Encoder.class)) //Default OptionalDecoder .decoder(get(context, Decoder.class)) //Default springmvccontract .contract(get(context, Contract.class)); // @formatter:on //Configure the context of the feign configureFeign(context, builder); return builder; }
During the construction process, register the basic configuration items for feign class through FeignClientFactoryBean.configureUsingConfiguration, including the registration of Interceptor
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) { Logger.Level level = getOptional(context, Logger.Level.class); if (level != null) { builder.logLevel(level); } Retryer retryer = getOptional(context, Retryer.class); if (retryer != null) { builder.retryer(retryer); } ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class); if (errorDecoder != null) { builder.errorDecoder(errorDecoder); } Request.Options options = getOptional(context, Request.Options.class); if (options != null) { builder.options(options); } //Get interceptors from feign context Map<String, RequestInterceptor> requestInterceptors = context .getInstances(this.contextId, RequestInterceptor.class); if (requestInterceptors != null) { builder.requestInterceptors(requestInterceptors.values()); } if (this.decode404) { builder.decode404(); } }
Context ID is the specific feign class ID, and RequestInterceptor is the specific interface. That is to say, get all RequestInterceptor instances through context.getInstances and register them in builder
public <T> Map<String, T> getInstances(String name, Class<T> type) { AnnotationConfigApplicationContext context = getContext(name); //Using beannamesfortypeincludingancesters if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type); } return null; }
The beannamesfortypeincludingancessors method is used to get the instances in the factory. This method will not only find them in the factory of feign, but also find the corresponding instances through the parent spring Factory (similar to the factory of springmvc)
It's also because of this method. Even if you don't configure configuration in FeignClient, your Interceptor will take effect globally if it is injected into the container through @ Component and other methods. So if you point to make your Interceptor part take effect, it's better not to inject it into the Spring container