Spring Cloud 실전 팁 (feign GET 전송 pojo, 계승 인터페이스 방법 파라미터 주석 등 문제 해결)

21050 단어 springBootSpringCloud
spring 클 라 우 드 실천
프로젝트 구조
config 설정 센터
포트: 8888, 설정 파일 을 직접 읽 기 편 하고 생산 환경 에서 git 를 읽 을 수 있 습 니 다.application - dev. properties 는 전역 설정 입 니 다.먼저 설정 센터 를 시작 합 니 다. 모든 서비스의 설정 (등록 센터 의 주소 포함) 은 설정 센터 에서 읽 습 니 다.
eureka 등록 센터
포트: 8761, / metadata 터미널 에서 metadata 정보 설정 을 실현 합 니 다.
zuul 게 이 트 웨 이
포트: 8080, token 해석 을 보 여 주 며 label 을 얻 고 header 를 넣 어 뒤로 전달 합 니 다.
코어 프레임 핵심 패키지
핵심 jar 패키지, 모든 마이크로 서 비 스 는 이 패 키 지 를 참조 하여 AutoConfig 를 사용 하여 설정 면제, 생산 환경 에서 spring - cloud 의 사용 을 모 의 합 니 다.
starter spring cloud 및 core 프레임 워 크 의존 간소화 starter
provider 서비스 공급 자
api 서비스 공급 자 SDK
서비스 소비 자 는 서비스 제공 자가 제공 하 는 SDK 패키지 (api 프로젝트) 를 통 해 만 호출 할 수 있 도록 강제 합 니 다. 서비스 소비자 의 행 위 를 더욱 편리 하 게 제어 할 수 있 습 니 다. 가능 한 최적화 를 제공 하고 SDK 는 캐 시 를 직접 읽 습 니 다 (SDK 는 캐 시 만 읽 고 저장 을 늦 추 는 지 마이크로 서비스 에 있 는 지).어떤 서비스 소비자 가 있 는 지, info 터미널 에서 가방 의존 을 표시 하고 등록 센터 를 통 해 모든 서 비 스 를 옮 겨 다 니 는 지 통계 합 니 다.dubbo 의 부 드 러 운 이동 을 편리 하 게 하고 인터페이스 에 feign 주 해 를 추가 합 니 다.
서비스 공급 자 마이크로 서비스
포트: 18090, 서비스 제공 자, 특수 논리 가 없습니다.
consumer 서비스 소비자
포트: 18090, 서비스 제공 자 호출.
Restful API 설계 규범
/ 버 전 / 액세스 제어 / 도 메 인 대상 / 버 전 / 액세스 제어 / 도 메 인 대상 / action / 동작
판본
버 전 은 마이크로 서비스 등급 입 니 다. 즉, 하나의 API 가 v3 버 전이 존재 하지 않 습 니 다. 다른 API 는 v1 버 전의 문제 일 뿐 모든 API 버 전 을 업그레이드 하여 함께 업그레이드 하려 면 이전 버 전 v1 - v3 를 사용 할 수 있 도록 해 야 합 니 다.원칙적으로 이전 버 전 을 호 환 해 야 합 니 다. 현재 / v3 이면 / v2 요 구 는 정상적으로 사용 할 수 있 습 니 다.
잔재주
아래 swagger 주 해 는 상기 요 구 를 실현 할 수 있 습 니 다. 경로 의 {version} 은 한정 되 지 않 았 습 니 다. 사실은 임의의 내용 일 수 있 습 니 다. swagger 문 서 를 통 해 약속 할 수 있 습 니 다.
// v1 api     
@ApiOperation("    ")
@RequestMapping(value = "/v1/pb/product", method = RequestMethod.GET)
@Deprecated
List selectAll(@RequestParam("offset") Integer offset, @RequestParam("limit") Integer limit);
 
//ProviderApiAutoConfig.CURRENT_VERSION="v2"        v1  
@ApiOperation("             ")
@RequestMapping(value = "/{version}/pb/product", method = RequestMethod.GET)
@ApiImplicitParam(name = "version", paramType = "path", allowableValues = ProviderApiAutoConfig.CURRENT_VERSION, required = true)
Response> selectAllGet(Page page);
  
// ProviderApiAutoConfig.COMPATIBLE_VERSION="v2,v1" swagger-ui      version    ,   v2
@ApiOperation(value = "               ")
@ApiImplicitParam(name = "version", paramType = "path", allowableValues = ProviderApiAutoConfig.COMPATIBLE_VERSION, required = true)
@RequestMapping(value = "/{version}/pb/product/action/search", method = RequestMethod.POST)
Response> selectAll(@RequestBody Page page);

액세스 제어
게 이 트 웨 이 에서 통일 적 으로 접근 제어 에 사용 되 며, 접근 통 제 는 pb - Public 로 나 뉘 어 모든 요청 은 pt - proctected 에 접근 할 수 있 습 니 다.
action
명사 + 요청 방법 으로 표현 할 수 없 는 확장 가능 한 / 도 메 인 대상 / action / 동 사 는 POST 방법 이 어야 합 니 다. 예 를 들 어 POST / users / action / login
문제 설명
주로 API (SDK) 를 사용 하여 게 으 름 을 피 우 고 Restful API 경로 의 버 전에 서 가 져 온 일련의 문제 때문이다.
spring MVC 는 계승 인터페이스 에서 방법 적 매개 변수 에 대한 주 해 를 지원 하지 않 습 니 다 (계승 클래스, 방법 적 주 해 를 지원 합 니 다)
API 에서 편 의 를 위해 restTemplate 대신 feign 을 사용 하여 수 동 으로 호출 합 니 다.가 져 온 문제: springMVC 주 해 는 게 으 름 을 피 우려 면 feign 인터페이스 에 한 번 만 쓰 고 이 인 터 페 이 스 를 계승 하면 됩 니 다.예 를 들 어 feign 인터페이스 정 의 는 다음 과 같다.
@FeignClient(ProviderApiAutoConfig.PLACE_HOLD_SERVICE_NAME)
public interface ProductService {
    //    spring mvc        
    public class Page extends PageRequest {
    }
    @RequestMapping(value = "/{version}/pt/product", method = RequestMethod.POST)
    Response insert(@RequestBody Product product);
}

service 구현 클래스 방법 매개 변 수 는 @ RequestBody 주 해 를 다시 써 야 합 니 다. 방법 상의 @ RequestMapping 주 해 는 생략 할 수 있 습 니 다.
@RestController
public class ProductServiceImpl implements ProductService {
    @Override
    public Response insert(@RequestBody Product product) {
        product.setId(1L);
        return new Response(product);
    }
}

해결 방법, @ Configuration 설정 클래스 에 다음 코드 를 추가 하여 spring 기본 Argument Resolvers 를 확장 합 니 다.
public static MethodParameter interfaceMethodParameter(MethodParameter parameter, Class annotationType) {
    if (!parameter.hasParameterAnnotation(annotationType)) {
        for (Class> itf : parameter.getDeclaringClass().getInterfaces()) {
            try {
                Method method = itf.getMethod(parameter.getMethod().getName(), parameter.getMethod().getParameterTypes());
                MethodParameter itfParameter = new MethodParameter(method, parameter.getParameterIndex());
                if (itfParameter.hasParameterAnnotation(annotationType)) {
                    return itfParameter;
                }
            } catch (NoSuchMethodException e) {
                continue;
            }
        }
    }
    return parameter;
}
    
@PostConstruct
public void modifyArgumentResolvers() {
    List list = new ArrayList<>(adapter.getArgumentResolvers());

    list.add(0, new PathVariableMethodArgumentResolver() {  // PathVariable       
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return super.supportsParameter(interfaceMethodParameter(parameter, PathVariable.class));
        }

        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            return super.createNamedValueInfo(interfaceMethodParameter(parameter, PathVariable.class));
        }
    });

    list.add(0, new RequestHeaderMethodArgumentResolver(beanFactory) {  // RequestHeader       
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return super.supportsParameter(interfaceMethodParameter(parameter, RequestHeader.class));
        }

        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            return super.createNamedValueInfo(interfaceMethodParameter(parameter, RequestHeader.class));
        }
    });

    list.add(0, new ServletCookieValueMethodArgumentResolver(beanFactory) {  // CookieValue       
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return super.supportsParameter(interfaceMethodParameter(parameter, CookieValue.class));
        }

        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            return super.createNamedValueInfo(interfaceMethodParameter(parameter, CookieValue.class));
        }
    });

    list.add(0, new RequestResponseBodyMethodProcessor(adapter.getMessageConverters()) {    // RequestBody       
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return super.supportsParameter(interfaceMethodParameter(parameter, RequestBody.class));
        }

        @Override
        protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {    //   @Valid  
            super.validateIfApplicable(binder, interfaceMethodParameter(methodParam, Valid.class));
        }
    });

    //   ArgumentResolvers,       
    adapter.setArgumentResolvers(list);
}

swagger 는 계승 인터페이스 에서 방법 적 매개 변수 에 대한 주 해 를 지원 하지 않 습 니 다 (계승 클래스, 방법 적 주 해 를 지원 합 니 다)
swagger 자체 확장 점 을 우아 하 게 확장 할 수 있 는 방법 을 찾 지 못 했 습 니 다. 원본 코드 를 수정 할 수 밖 에 없 었 습 니 다. springfox - spring - web 2.8.0 release 소스 패 키 지 를 다운로드 하 십시오.pom. xml 추가


    4.0.0
    io.springfox
    springfox-spring-web
    2.8.0-charles
    jar

    
        1.8
        @
        UTF-8
        UTF-8
        ${java.version}
        ${java.version}
    

    
        
            
                org.springframework.boot
                spring-boot-dependencies
                
                1.5.10.RELEASE
                pom
                import
            
        
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.reflections
            reflections
            0.9.11
        

        
        
            io.springfox
            springfox-swagger2
            2.8.0
            
                
                    io.springfox
                    springfox-spring-web
                
            
        
    


ResolvedMethodParameter 인터페이스 계승 ResolvedMethodParameter 추가
public class ResolvedMethodParameterInterface extends ResolvedMethodParameter {
    public ResolvedMethodParameterInterface(String paramName, MethodParameter methodParameter, ResolvedType parameterType) {
        this(methodParameter.getParameterIndex(),
                paramName,
                interfaceAnnotations(methodParameter),
                parameterType);
    }

    public ResolvedMethodParameterInterface(int parameterIndex, String defaultName, List annotations, ResolvedType parameterType) {
        super(parameterIndex, defaultName, annotations, parameterType);
    }

    public static List interfaceAnnotations(MethodParameter methodParameter) {
        List annotationList = new ArrayList<>();
        annotationList.addAll(Arrays.asList(methodParameter.getParameterAnnotations()));

        if (CollectionUtils.isEmpty(annotationList)) {
            for (Class> itf : methodParameter.getDeclaringClass().getInterfaces()) {
                try {
                    Method method = itf.getMethod(methodParameter.getMethod().getName(), methodParameter.getMethod().getParameterTypes());
                    MethodParameter itfParameter = new MethodParameter(method, methodParameter.getParameterIndex());
                    annotationList.addAll(Arrays.asList(itfParameter.getParameterAnnotations()));
                } catch (NoSuchMethodException e) {
                    continue;
                }
            }
        }

        return annotationList;
    }
}

HandlerMethodResolver 클래스 line 181 을 수정 하고 ResolvedMethodParameter 를 ResolvedMethodParameterInterface 로 대체 하여 deploy 를 다시 포장 하고 swagger 관련 의존 에서 수 정 된 버 전 을 강제로 지정 합 니 다.


    io.springfox
    springfox-swagger2


    io.springfox
    springfox-swagger-ui



    io.springfox
    springfox-spring-web
    2.8.0-charles


이렇게 하면 swagger 문 서 를 순조롭게 생산 할 수 있 습 니 다.
feign 은 GET 방법 으로 POJO 를 전달 하 는 것 을 지원 하지 않 습 니 다.
springMVC 는 GET 방법 으로 POJO 를 직접 연결 하 는 것 을 지원 하기 때문에 feign 이 모든 springMVC 효 과 를 덮어 쓰 지 않 았 기 때문에 인터넷 의 많은 변통 방법 이 좋 지 않 습 니 다. 아니면 POJO 를 하나의 단독 속성 으로 분해 하여 방법 매개 변 수 를 Map 으로 만 들 거나 HTTP 협 의 를 위반 해 야 합 니 다.GET 전달 @ RequestBody:https://www.jianshu.com/p/7ce46c0ebe9dhttps://github.com/spring- cloud / spring - cloud - netflix / issues / 1253 해결 방법, feign 차단 기 사용:
public class CharlesRequestInterceptor implements RequestInterceptor {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void apply(RequestTemplate template) {
        // feign     GET     POJO, json body query
        if (template.method().equals("GET") && template.body() != null) {
            try {
                JsonNode jsonNode = objectMapper.readTree(template.body());
                template.body(null);

                Map> queries = new HashMap<>();
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void buildQuery(JsonNode jsonNode, String path, Map> queries) {
        if (!jsonNode.isContainerNode()) {   //     
            if (jsonNode.isNull()) {
                return;
            }
            Collection values = queries.get(path);
            if (null == values) {
                values = new ArrayList<>();
                queries.put(path, values);
            }
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) {   //     
            Iterator it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                } else {  //    
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}

feign 은 경로 의 {version} 을 지원 하지 않 습 니 다.
전형 적 인 Restful API 에 대한 정 의 는 다음 과 같 습 니 다.
@ApiOperation("             ")
@RequestMapping(value = "/{version}/pb/product", method = RequestMethod.GET)
//        api          pt=protected       token       
@ApiImplicitParam(name = "version", paramType = "path", allowableValues = ProviderApiAutoConfig.CURRENT_VERSION, required = true)
Response> selectAllGet(Page page);

우 리 는 경로 의 {version} 에 관심 이 없 기 때문에 방법 매개 변수 에 도 @ PathVariable ("version") 이 없습니다. 이 럴 때 feign 은 멍청 합 니 다. 경로 의 {version} 이 어떤 값 으로 바 뀌 어야 할 지 모 릅 니 다.해결 방법 은 자신의 Contract 를 사용 하여 SpringMvcContract 를 대체 합 니 다. 먼저 SpringMvcContract 코드 를 복사 하여 processAnnotationOnMethod 방법의 코드 를 수정 하고 swagger 주석 에서 {version} 의 값 을 얻 습 니 다.
public class CharlesSpringMvcContract extends Contract.BaseContract
        implements ResourceLoaderAware {
    @Override
    protected void processAnnotationOnMethod(MethodMetadata data,
                                             Annotation methodAnnotation, Method method) {
        if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
                .annotationType().isAnnotationPresent(RequestMapping.class)) {
            return;
        }

        RequestMapping methodMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        // HTTP Method
        RequestMethod[] methods = methodMapping.method();
        if (methods.length == 0) {
            methods = new RequestMethod[]{RequestMethod.GET};
        }
        checkOne(method, methods, "method");
        data.template().method(methods[0].name());

        // path
        checkAtMostOne(method, methodMapping.value(), "value");
        if (methodMapping.value().length > 0) {
            String pathValue = Util.emptyToNull(methodMapping.value()[0]);
            if (pathValue != null) {
                pathValue = resolve(pathValue);
                // Append path from @RequestMapping if value is present on method
                if (!pathValue.startsWith("/")
                        && !data.template().toString().endsWith("/")) {
                    pathValue = "/" + pathValue;
                }
                //   version
                if (pathValue.contains("/{version}/")) {
                    Set apiImplicitParams = AnnotatedElementUtils.findAllMergedAnnotations(method, ApiImplicitParam.class);
                    for (ApiImplicitParam apiImplicitParam : apiImplicitParams) {
                        if ("version".equals(apiImplicitParam.name())) {
                            String version = apiImplicitParam.allowableValues().split(",")[0].trim();
                            pathValue = pathValue.replaceFirst("\\{version\\}", version);
                        }
                    }
                }
                data.template().append(pathValue);
            }
        }

        // produces
        parseProduces(data, method, methodMapping);

        // consumes
        parseConsumes(data, method, methodMapping);

        // headers
        parseHeaders(data, method, methodMapping);

        data.indexToExpander(new LinkedHashMap());
    }
}

그리고 자신의 AutoConfig 에서 spring 의 bean 이 라 고 밝 혔 습 니 다.
@Configuration
@ConditionalOnClass(Feign.class)
public class FeignAutoConfig {
    @Bean
    public Contract charlesSpringMvcContract(ConversionService conversionService) {
        return new CharlesSpringMvcContract(Collections.emptyList(), conversionService);
    }

    @Bean
    public CharlesRequestInterceptor charlesRequestInterceptor(){
        return new CharlesRequestInterceptor();
    }
}

spring cloud 및 feign 팁
dev 환경 에 대한 원본 코드 책임 (application. properties 의 설정 은 모두 dev 환경 주소) 설정 센터 dev 환경 전역 설정 추가:
# dev        override      
spring.cloud.config.overrideNone=true

개발 과정 에서 dev 환경 은 서버 에 마이크로 서 비 스 를 배치 합 니 다. 이 컴퓨터 는 개발 할 때 dev 서버 에 영향 을 주지 않 기 위해 spring. application. name 은 자신의 이름 을 접두사 로 추가 하고 dev 등록 센터 에 등록 해도 dev 서버 에 영향 을 주지 않 습 니 다. 설정 센터 의 지정 이름 은 spring. application. name 에 따라 변 하지 않 습 니 다.시작 매개 변수 추가:
--spring.application.name=charles-framework-provider

bootstrap. properties 설정 템 플 릿
#       
spring.cloud.config.uri=https://config-mo.xxxx.com/config
# config.name           spring.application.name
#                 dev       spring.application.name=charles-framework-provider            
spring.cloud.config.name=framework-provider
spring.cloud.config.profile=${ENV:dev}
#           
spring.cloud.config.fail-fast=true

이렇게 하면 본 기기 의 개발 은 서버 의 마이크로 서 비 스 를 호출 할 수 있 지만 서버 의 마이크로 서 비 스 는 본 기기 (서비스 이름 이 수정 되 었 습 니 다) 를 호출 하지 않 습 니 다.그렇다면 두 명의 연구 개발 이 서로 호출 되 어야 한다 면 어떻게 처리 해 야 합 니까? @FeignClient 주 해 는 서비스 이름 을 쓰 지 말고 place holder 를 사용 하 십시오. 다음 코드 와 유사 합 니 다.
@FeignClient(ProviderApiAutoConfig.PLACE_HOLD_SERVICE_NAME)
public interface ProductService {
}

ProviderApiAutoConfig 의 코드 는 다음 과 같 습 니 다.
@Configuration
//     feign                      API  feign
@ConditionalOnExpression("#{!environment['spring.application.name'].endsWith('" + ProviderApiAutoConfig.SERVICE_NAME + "')}")
@EnableFeignClients(basePackages = "com.github.charlesvhe.springcloud.practice.provider")
public class ProviderApiAutoConfig {
    public static final String SERVICE_NAME = "provider";
    // FeignClient  placeholder              key charles.service.   
    //   charles.service.framework-provider=charles-framework-provider   charles-framework-provider  
    public static final String PLACE_HOLD_SERVICE_NAME = "${charles.service." + SERVICE_NAME + ":" + SERVICE_NAME + "}";

    public static final String CURRENT_VERSION = "v2";
    public static final String COMPATIBLE_VERSION = "v2,v1";

}

이렇게 하면 시작 매개 변수 에서 어떤 서 비 스 를 지정 하고 싶 은 사람의 인 스 턴 스 를 바 꾸 어도 됩 니 다.서비스 가 api 에 의존 할 때 feign 의 api 인 스 턴 스 를 주입 하지 않 습 니 다.
링크 옮 기기:https://www.jianshu.com/p/085c11e5722f
원본 주소: https://github.com/charlesvhe/spring-cloud-practice/tree/refactor

좋은 웹페이지 즐겨찾기