SpringMVCにおけるWebDataBinderの応用と原理

5774 ワード

SpringMVCにおけるWebDataBinderの応用と原理
   
 Controllerメソッドのパラメータタイプは、基本タイプであってもよいし、カプセル化された通常のJavaタイプであってもよい.この通常のJavaタイプが注釈を宣言していない場合は、各プロパティがRequestで対応するリクエストパラメータを検索する必要があることを意味します.クライアントがどのようなタイプのリクエストパラメータを送信しても、最終的にはサービス側にバイトで送信されることはよく知られています.サービス側がRequestのgetParameterメソッドで取得したパラメータも文字列形式の結果である.したがって,文字列形式のパラメータをサービス側が本当に必要とするタイプに変換する変換ツールが必要であり,springではこの変換ツールがWebDataBinderである.
   WebDataBinderは自分で作成する必要はありません.パラメータタイプに対応するプロパティエディタPropertyEditorを登録するだけです.PropertyEditorは文字列をその本当のデータ型に変換することができ、そのvoid setAsText(String text)方法はデータ変換の過程を実現する.
  具体的には、WebDataBinderを使用して自己実装またはspringに付属のPropertyEditorを登録するInitBinderメソッドをControllerに宣言します.次のようにします.
  
    @InitBinder
    public void initBinder(WebDataBinder binder) throws Exception {
        binder.registerCustomEditor(Long.class, new CustomNumberEditor(Long.class, true));
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }

  
  注釈なしで処理される通常のJavaタイプのパラメータ解析器は、ModelAttributeMethodProcessorです.解析メソッドに参加するコードは次のとおりです.
	public final Object resolveArgument(
			MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest request, WebDataBinderFactory binderFactory)
			throws Exception {

		String name = ModelFactory.getNameForParameter(parameter);
		Object target = (mavContainer.containsAttribute(name)) ?
				mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);

		WebDataBinder binder = binderFactory.createBinder(request, target, name);
		if (binder.getTarget() != null) {
			bindRequestParameters(binder, request);
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors()) {
				if (isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
		}

		mavContainer.addAllAttributes(binder.getBindingResult().getModel());
		return binder.getTarget();
	}

  要求が来るたびに、WebDataBinderFactoryを使用してbinderオブジェクトを作成し、このbinderから最終的に解析されたパラメータオブジェクトを取得します.WebDataBinderFactoryは、InvocabeHandlerMethodで定義されています.つまり、異なるControllerメソッドには異なるWebDataBinderFactoryがあります.実際にbinderを作成すると同時にbinderも初期化され、この初期化プロセスではControllerのInitBinderメソッドが実行されます.InitBinderDataBinderFactoryはbinderを初期化する方法を実現した:
	public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
		for (InvocableHandlerMethod binderMebinderMethod thod : this.binderMethods) {
			if (isBinderMethodApplicable(binderMethod, binder)) {
				Object returnValue = binderMethod.invokeForRequest(request, null, binder);
				if (returnValue != null) {
					throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
				}
			}
		}
	}
   
      上記の方法のbinderMethodsは、Controllerで定義されたInitBinderメソッドであり、binderMethodはControllerの他の方法と同様にInvocableHandlerMethodである.上のコードから分かるように、InitBinderメソッドは複数を宣言することができ、WebDataBinderFactoryがbinderを初期化すると、各InitBinderメソッドがそれぞれ呼び出されます.初期化の過程でbinder.registerCustomEditorを使用し,パラメータタイプ変換時に使用するためにBeanWrapperImplに転送されたPropertyEditorを間接的に登録した.
      先ほどのModelAttributeMethodProcessorでパラメータを解析したとき、binderを作成した後にbindRequestParametersを呼び出して要求パラメータのバインドを実現したことを覚えています.そのサブクラスのS e v e r t M e d l AttributeMethodProcessorはこの方法を書き換えました.
	protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
		ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
		ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
		servletBinder.bind(servletRequest);
	}

    
     親クラスでも子クラスでも、実はbinderのbindメソッドが呼び出されています.サーブレットRequestDataBinderのbindメソッドを次に示します
	public void bind(ServletRequest request) {
		MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
		MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
		if (multipartRequest != null) {
			bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
		}
		addBindValues(mpvs, request);
		doBind(mpvs);
	}

   
 この方法は依存注入の過程と非常に似ており,依存注入は属性に基づいてコンテナ内で条件を満たすオブジェクトを見つけ,現在のbeanに設定する.上記の方法はコンテナで検索するのではなく、Requestから取得します.すなわち、Requestのリクエストパラメータをbinderのtargetに注入します.このときタイプ変換を行うのが登録されたばかりのPropertyEditorであり,InitBinderメソッドは毎回実行されるため,利用者はコントローラごとに同じタイプのパラメータに対して異なるパラメータ変換方式を定義することができる.    bindRequestParametersメソッドの処理を経て、binderのtarget(HandlerMethodのパラメータ)にはRequestの要求パラメータが含まれています.
      では、もう一つ質問があります.
InvocableHandlerMethodのWebDataBinderFactoryはどのように来ていますか?その作成プロセスは、RequestMappingHandlerAdapter(本明細書のすべての論理プロセスは、RequestMappingHandlerAdapterを使用すると仮定する):
	private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
		Class> handlerType = handlerMethod.getBeanType();
		Set methods = this.dataBinderFactoryCache.get(handlerType);
		if (methods == null) {
			methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
			this.dataBinderFactoryCache.put(handlerType, methods);
		}
		List binderMethods = new ArrayList();
		for (Method method : methods) {
			InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
			binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
			binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
			binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
			binderMethods.add(binderMethod);
		}
		return createDataBinderFactory(binderMethods);
	}