Spring MVCカスタムメソッドパラメータ解析実戦

9211 ワード

読者に適しています.Spring MVC/Bootを使ってウェブアプリプロジェクトを開発していますが、http requestパラメータ解析方式はフレーム自体が提供する解析器の開発者には適用されません.
ケース:        Postインターフェース:/api/v 1/orders        引数:type:string、limit:number        追加の要求:requestのQueryStringとbodyの中でパラメータを同時に携帯する場合、QueryStringを優先的に使用する.たとえば
curl -X POST 'https://company.com/api/v1/orders?type=Food' -d 'type=Dig&limit=100'
            この時解析したパラメータはtype=Food&limit=100であるべきです.
開発案:
1.  Controllerを作成し,HttpServletRequestを用いてパラメータを自主的に解析した.キーコードは以下の通りです
@RestController
public class OrderController {

    @PostMapping("/api/v1/orders")
    public List queryOrders(HttpServletRequest request) {
        String queryStr = request.getQueryString();
        String body = request.getReader().lines.stream()
                        .collect(Collectors.joining(System.lineSeparator()));
    /*             map              ,     */
        Map paramMap = parseParam(queryStr, body);
        String type = paramMap.get("type");
        int limit = paramMap.get("limit");
    /**
     * do your business
     */
        return orderList;
    }

/**
 * other methods
 */

}
よさそうです.しかし、もしたくさんのインターフェースがあれば?インタフェースのパラメータが増えたら?インタフェースのパラメータが変化したら?インタフェースパラメータがチェックされていますか?インターフェースごとにこのように書いていますが、その前の3行は同じですか?各インターフェースのパラメータ名は、mapのkeyでのみ指定できます.パラメータタイプは、mapから取得して強回転します.パラメータチェックは手書きのみです.私の話を聞いて、これはいい案ではないと思いますか?
私達の理想的なインターフェースの方法はこうであるべきです.
@RestController
public class OrderController {

    @PostMapping("/api/v1/orders")
    public List queryOrders(@Valid QueryOrdersParam param) {
        return orderService.queryOrders(param.getType(), param.getLimit());
    }

/**
 * other methods
 */

}
パラメータビームはこう定義されています.
@Data
@Builder
public class QueryOrdersParam {
    @NotBlank(message = "Type must be given")
    private String type;
    @Max(value = 1000)
    private int limit=100;
}
さわやかに見えますか?パラメータBeanはjavax.validationを使ってフレームのチェックを依頼できることを知っています.Controller法のパラメータはフレームを通して自動的に注入できる.しかし、フレーム解析要求パラメータをビームに注入する過程は私たちの需要とは違っています.どうすればいいですか?
やってくる、少年.HandlerMethodAgMentResolaverで調べてみますか?
2.まずこのインターフェースの内容を見てみます.
public interface HandlerMethodArgumentResolver {

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null}
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}
・supports Parameeter    この方法はブール値を返して、このパラマーが処理する必要があるかどうかを識別します.
・reolveAgment    この方法はパラメータを具体的に扱う方法です.方法の参照は、ターゲットパラメータに関するメタデータparameterを含み、現在要求されているModelAndView容器mavContinerは、現在の要求webRequestは、最後にbinderを作成するための工場クラスの例がある.
じゃ、まだ何を待っていますか?
@Slf4j
@Component
public class ControllerArgumentsResolver implements HandlerMethodArgumentResolver {

    /**
     *     true               
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return true;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, 
                    ModelAndViewContainer mavContainer,
                    NativeWebRequest webRequest, 
                    WebDataBinderFactory binderFactory) throws Exception {
        return null;
    }
}
このインターフェースの実現には自分で着手し、Spring MVCフレームをcontent.xmlに注入する必要があります.

    
            
    
Spring Bootフレームであれば符号注入が必要です.
@Configuration
public class ArgumentResolversConfig extends WebMvcConfigurerAdapter{
    
    @Override
    public void addArgumentResolvers(List argumentResolvers) {
        super.addArgumentResolvers(argumentResolvers);
        argumentResolvers.add(new ControllerArgumentResolver());
    }

}
自分たちのパラメータ解析器を配置してから、インターフェースをテストしてみましょう.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
@WebAppConfiguration
@Slf4j
public class ControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

     @Before
    public void setup() {
        /*   MockMvc */
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void testQueryOrders() {
        mockMvc.perform(MockMvcRequestBuilers.post("/api/v1/orders?type=Dig").content("type=Food&limit=10").accept(MedisType.APPLICATION_HSON).andExpect(MockMvcResultMatchers.status.isOk()));
    }

}
うん、やっぱりControllerの方法で取ったパラメータBeanはNullです.
3.パラメータをNullに変えることができるなら、それを作成できない理由がないでしょう.このインターフェース自体はどのような実現がありますか?私達が使えるものがありますか?スクリーニングを経て、私たちはModelAttributeMethodProcessorという実現類が私たちが探しているものに近いように見えます.私たちのパラメータ解析器を完璧にしましょう.
@Slf4j
@Component
public class ControllerArgumentsResolver implements HandlerMethodArgumentResolver {

    /**
     *          @RequestAttribute                         。
     *   ,          @Valid        。
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Valid.class) && parameter
                .hasParameterAnnotation(RequestAttribute.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, 
                WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        String name = parameter.getParameterName();
        Object attribute = mavContainer.containsAttribute(name) ?
                mavContainer.getModel().get(name) :
                this.createAttribute(parameter);
/*             Controller           */
        String requestStr = request.getQueryString();
        String body = request.getReader().stream().lines().collect(Collectors.joining(System.lineSeparator()));
        Map paramMap = parseRequest(requestStr, body);

        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
/*               */
            binder.bind(new MutablePropertyValues(map));
/*      validation   */
            validateIfApplicable(binder, parameter);
/*         BindException */
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }

        Map bindingResultModel = binder.getBindingResult().getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        log.debug("Found argument {} ---> {}", parameter.getParameterName(), attribute);
/*   binder           Bean       */
        return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
    }

}
はい、今はControllerの中の方法のパラメータを変えて、私達のカスタムコメントを追加します.
@RestController
public class OrderController {

    @PostMapping("/api/v1/orders")
    public List queryOrders(@RequestAttribute @Valid QueryOrdersParam param) {
        return orderService.queryOrders(param.getType(), param.getLimit());
    }

/**
 * other methods
 */

}
それからテストクラスを走ります.はい、通過します.大功が成る
深さ:        他の多くのシーンは、カスタムパラメータ解析器に適用され、Controller内の方法を開発するのに便利です.例えば、すべてまたはほとんどのインターフェースは、user有効性チェックを行い、方法内でuserオブジェクトを取得する必要があります.私たちはスクリーンショットを実現することによってアメリカの有効性を検証できます.通常この時にアメリカのオブジェクトを入手しました.スクリーンでアメリカのオブジェクトをrequest atributeに入れて、もう一つのパラメータ解析器をrequest atributeから取り出すことができます.このように、私達はControllerの方法声明の中で直接に有効なuserオブジェクトを得ることができます.開発効率を大幅に向上させ、コードの可読性も向上させることができます.
        こんなにたくさん話しましたから、自分で試してみてもいいです.少なくともgithubに行って作者に注目してみましょう.