This is the ninth day of my participation in the August More text Challenge. For details, see: August More Text Challenge

The premise is introduced

  • Feign is a service consumer invocation framework in SpringCloud, usually used in combination with the ribbon, hystrix, etc.

  • For legacy reasons, there are projects where the entire system is not a SpringCloud project, or even a Spring project, and the consumer’s focus is simply on making HTTP calling code easier to write.

  • With a relatively heavy framework like HttpClient or OKHTTP, the amount of coding and learning curve can be a challenge for beginners, but with The RestTemplate in Spring, there is no configured solution, so I wonder if I can leave the Spring Cloud. Use Feign independently.

Content abstract

Feign makes Java HTTP clients easier to write. Feign was inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign was originally designed to reduce the complexity of the unified binding Denominator to the HTTP API, without distinguishing between Restful support and non-compliant support. Feign aims to connect to the HTTP API with minimal resources and code. With customizable decoders and error handling, you can write any HTTP API.

Maven rely on

< the dependency > < groupId > com.net flix. Feign < / groupId > < artifactId > feign - core < / artifactId > < version > 8.18.0 < / version > </dependency> <dependency> <groupId>com.netflix.feign</groupId> <artifactId>feign-jackson</artifactId> < version > 8.18.0 < / version > < / dependency > < the dependency > < groupId > IO. Making. Lukehutch < / groupId > < artifactId > fast -- the classpath scanner < / artifactId > < version > 2.18.1 < / version > < / dependency > < the dependency > < the groupId > com.net flix. Feign < / groupId > < artifactId > feign - Jackson < / artifactId > < version > 8.18.0 < / version > < / dependency >Copy the code

Defining configuration classes

RemoteService service = Feign.builder()
            .options(new Options(1000.3500))
            .retryer(new Retryer.Default(5000.5000.3))
			.encoder(new JacksonEncoder())
            .decoder(new JacksonDecoder())
            .target(RemoteService.class, "http://127.0.0.1:8085");
Copy the code
  • The options method specifies the connection timeout period and response timeout period
  • The retryer method specifies the retry policy
  • The target method binds an interface to the server address.
  • The return type is the type of the bound interface.

Custom interface

Define a random service interface for a remote call and declare the associated interface parameters and request address.

Use @requestLine to specify the HTTP protocol and URL


public class User{
   String userName;
}

public interface RemoteService {
    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("POST /users/list")
    User getOwner(User user);
	@RequestLine("POST /users/list2")
    @Headers({ "Content-Type: application/json", "Accept: application/json", "request-token: {requestToken}", "UserId: {userId}", "UserName: {userName}" })
    public User getOwner(@RequestBody User user,
        @Param("requestToken") String requestToken,
        @Param("userId") Long userId,
        @Param("userName") String userName);
}
Copy the code

Service provider

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value="users")
public class UserController {
    @RequestMapping(value="/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
    @ResponseBody
    public User list(@RequestBody User user) throws InterruptedException{
        System.out.println(user.getUsername());
        user.setId(100L);
        user.setUsername(user.getUsername().toUpperCase());
        returnuser; }}Copy the code

call

The feign wrapped interface is called in the same way as the local method, directly getting the return value provided by the remote service.

String result = service.getOwner(new User("scott"));
Copy the code

Two problems with native Feign

  1. Native Feign can only resolve one interface at a time, generating the corresponding request proxy object, which can be cumbersome if there are more than one calling interface in a package.

  2. The call proxy generated by Feign is just a generic object that should be registered with Spring so that we can inject it at any time using @AutoWired.

Solution:

  1. For multiple parses, you can specify the scan package path and then parse the classes in the package in turn.

  2. Implement the BeanFactoryPostProcessor interface to extend Spring container functionality.

Define an annotation class

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignApi {
/** * Address of the service called *@return* /
String serviceUrl(a);
}
Copy the code

Generate the Feign proxy and register it with the Spring implementation class:

import feign.Feign;
import feign.Request;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class FeignClientRegister implements BeanFactoryPostProcessor{

	// The path of the scanned interface
    private String  scanPath="com.xxx.api";

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        List<String> classes = scan(scanPath);
        if(classes==null) {return ;
        }
        System.out.println(classes);
        Feign.Builder builder = getFeignBuilder();
        if(classes.size()>0) {for(String claz : classes) { Class<? > targetClass =null;
                try {
                    targetClass = Class.forName(claz);
                    String url=targetClass.getAnnotation(FeignApi.class).serviceUrl();
                    if(url.indexOf("http://")! =0){
                        url="http://"+url;
                    }
                    Object target = builder.target(targetClass, url);
                    beanFactory.registerSingleton(targetClass.getName(), target);
                } catch (Exception e) {
                    throw newRuntimeException(e.getMessage()); }}}}public Feign.Builder getFeignBuilder(a){
        Feign.Builder builder = Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .options(new Request.Options(1000.3500))
                .retryer(new Retryer.Default(5000.5000.3));
        return builder;
    }

    public List<String> scan(String path){
        ScanResult result = newFastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class<? > aClass) -> { }).scan();if(result! =null) {return result.getNamesOfAllInterfaceClasses();
        }
        return  null; }}Copy the code
Example of writing a call interface:
import com.xiaokong.core.base.Result;
import com.xiaokong.domain.DO.DeptRoom;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import com.xiaokong.register.FeignApi;

import java.util.List;

@FeignApi(serviceUrl = "http://localhost:8085")
public interface RoomApi {
    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("GET /room/selectById? id={id}")
    Result<DeptRoom> selectById(@Param(value="id") String id);
    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("GET /room/test")
    Result<List<DeptRoom>> selectList();
}
Copy the code
Example of an interface:
@Service
public class ServiceImpl{
    // Insert the interface into the bean to be used
    @Autowired
    private RoomApi roomApi;
    @Test
    public void demo(a){
        Result<DeptRoom> result = roomApi.selectById("1"); System.out.println(result); }}Copy the code

Notes:

  1. If the interface returns a complex nested object, then it is important to explicitly specify the generic type, because when Feign resolves the complex object, it needs to get the generic type inside the object returned by the interface through reflection in order to be resolved correctly using Jackson. If the type is not explicitly specified, Jackson converts the JSON object to a LinkedHashMap type.

  2. If you are using Spring and need to call someone else’s interface over HTTP, you can use this tool to simplify calling and parsing.