Spring Cloud 中使用Feign解決參數註解沒法繼承的問題

Spring Cloud 中使用Feign解決參數註解沒法繼承的問題

在使用Feign的時候,一般先寫一個接口類,而後再寫實現類,根據官網的例子接下來編寫一個簡單的Feign的請求例子spring

@FeignClient("spring-cloud-eureka")
public interface FeignDemoApi {
    
    @RequestMapping("/testFeign")
    public String testSpringMvc(@RequestBody User user);
}

而後實現類以下mvc

@RestController
public class FeignDemoApiImpl implements FeignDemoApi{
    @Override
    public String testSpringMvc(User user) {
        return user.getName();
    }
}

而後測試類編寫以下app

@RequestMapping("/testSpringmvc")
    public void test6(){
        User user =new User();
        user.setName("test");
        user.setAge(18);
        feignDemoApi.testSpringMvc(user);
    }

發如今客戶端進行接收的時候發現接收到的User爲nullide

而後從網上查資料才知道須要在實現類的入參中也加入@RequestBody 註解,這樣才能接收到參數。可是不由有個疑問,咱們查看@RequestMapping@RequestBody這兩個註解的代碼中都沒有@Inherited這個可支持繼承的註解,那麼@RequestMapping 爲何能發揮做用?而@RequestBody 卻不能發揮做用呢?測試

@RequestMapping 註解

而後通過查資料瞭解到,SpringMvc在初始化時候會將帶有@Controller初始化進Spring容器中,其實例化的類型爲RequestMappingInfo類,此時在SpringMvc中會加載@Controller 類註解下的全部帶有@ RequestMapping 的方法,將其@RequestMapping中的屬性值也加載進來,若是在本類方法上找不到@RequestMapping註解信息的話,那麼就會尋找父類相同方法名的@RequestMapping的註解。具體在AnnotatedElementUtils類中的searchWithFindSemantics方法中有下面的一段。表示在父類中尋找註解。this

// Search on interfaces
for (Class<?> ifc : clazz.getInterfaces()) {
	T result = searchWithFindSemantics(ifc, annotationType, annotationName,
			containerType, processor, visited, metaDepth);
	if (result != null) {
		return result;
	}
}

此時就能知道爲何在子類中沒有加@RequestMapping註解,可是卻享有父類@RequestMapping註解的效果了。.net

@RequestBody 註解

在上面的註解中咱們瞭解到雖然@RequestMapping不支持繼承,可是子類享有一樣效果的緣由就是在判斷的時候若是子類沒有就去父類找,可是在測試中咱們發現@RequestBody是沒有享受此效果的,因此我猜想在判斷是否有註解的時候只是判斷本類有沒有此註解而沒有判斷父類。3d

通過查詢資料,發如今SpringMvc中使用RequestResponseBodyMethodProcessor 來進行入參和出參的解析,其中使用supportsParameter 來判斷是否有@RequestBody的註解code

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

咱們發現事實如咱們猜想的同樣。blog

解決方案

咱們也能夠像@RequestMapping註解同樣進行判斷若是本類沒有的話,那麼就對其父類進行判斷。建立一個配置類而後將自定義的ArgumentResolvers放入到RequestMappingHandlerAdapter

@Configuration
public class MyWebMvcConfigu implements BeanFactoryAware {
    private ConfigurableBeanFactory beanFactory;
    @Autowired
    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

	//判斷其父類是否有註解
    public static <A extends Annotation> MethodParameter interfaceMethodParameter(MethodParameter parameter,
            Class<A> 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<HandlerMethodArgumentResolver> list = new ArrayList<>(requestMappingHandlerAdapter.getArgumentResolvers());
       
        // RequestBody 支持接口註解
        list.add(0, new RequestResponseBodyMethodProcessor(requestMappingHandlerAdapter.getMessageConverters()) {
            @Override
            public boolean supportsParameter(MethodParameter parameter) {
                return super.supportsParameter(interfaceMethodParameter(parameter, RequestBody.class));
            }

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

        // 修改ArgumentResolvers, 支持接口註解
        requestMappingHandlerAdapter.setArgumentResolvers(list);
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
    }
}

而後咱們就能夠發現再從新調用之後子類沒有加入@RequestBody註解也可以接收到實體類了

參考文章

相關文章
相關標籤/搜索