2

The situation is that a vendor supplied an XML schema for their XML documents that they can submit to my service. I didn't like their schema and so I wrote my own schema along with an XSLT to transform the received XML. My schema was used with JAXB's xjc tool to generate .java files that bind some pojos into a suitable object model. If it were not for the fact that a transformation step were needed, this would be trivial to implement in Spring MVC.

The received XML must first be transformed before being mapped onto the JAXB classes. Roughly analogous to the following snippet:

@RequestMapping(value="/receiveXml", method=RequestMethod.POST )
public ResponseEntity<String> receiveXml( @RequestBody String vendorXmlPayload )         {      
  // 1.  Make sure vendorXmlPayload adheres to vendor's schema
  vendorSchema.newValidator().validate(new StreamSource(new StringReader(vendorXmlPayload)));

  // 2.  Transform xml payload to my schema
  StringWriter sw = new StringWriter();
  transformer.transform(new StreamSource(new StringReader(vendorXmlPayload)), new StreamResult(sw))

  // 3.  Validate transformed XML against my schema
  mySchema.newValidator().validate(new StreamSource(new StringReader(sw.toString())));

  // 4.  Unmarshall to JAXB-annotated classes
  DomainObject obj = (DomainObject) unmarshaller.unmarshal(new StreamSource(new StringReader(sw.toString())));


  (errors != null) ? return ... HttpStatus.BAD_REQUEST : return ..... HttpStatus.OK
}    

Are there some elegant Spring annotations to condense all of that on the MVC Controller? Namely is there a way to perform the transform & unmarshal with the @RequestBody annotation or something? Perhaps like this fictitious snippet:

@RequestMapping(value="/receiveXml", method=RequestMethod.POST )
@Transform(transformer="myTransform.xslt")
public ResponseEntity<String> receiveXml( @RequestBody DomainObj domainObj)         
{
  // Process my DomainObj as I normally would
  (errors != null) ? return ... HttpStatus.BAD_REQUEST : return ..... HttpStatus.OK
}

@InitBinder doesn't quite look like it fits this scenario. Most "Spring MVC XSLT" searches deal with transforming output rather than input.

4

1 回答 1

3

我怀疑是否有这样的开箱即用的东西,但你应该能够按照这些思路构建可重用的东西:

为自己定义一个新的注释,比如@XmlWithTransform,它接受一个参数,比如 xslt 位置,你可以通过这种方式在控制器上指定它:

@RequestMapping(value="/receiveXml", method=RequestMethod.POST )
    public ResponseEntity<String> receiveXml( @XmlWithTransform(usingxslt="anxsl.xsl")     CustomType customType )

现在编写一个HandlerMethodArgumentResolver可以接受请求正文 xml 的自定义,使用注释的 xslt 参数对其进行转换,并将其绑定到指定为参数的类型,如下所示:

import java.io.StringReader;

import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.springframework.core.MethodParameter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class XsltTransformingHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver{

    Unmarshaller unmarshaller;
    @Override
    public boolean supportsParameter(MethodParameter parameter){
        return (parameter.getMethodAnnotation(XmlWithTransform.class)!=null);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        XmlWithTransform xmlWithTransform = parameter.getMethodAnnotation(XmlWithTransform.class);
        Class<?> parameterType = parameter.getParameterType();
        String xsltLocation = xmlWithTransform.usingxstl();
        ServletServerHttpRequest servletRequest = new ServletServerHttpRequest(webRequest.getNativeRequest(HttpServletRequest.class));
        String xmlFromVendor = IOUtils.toString(servletRequest.getBody(), "UTF-8");

        String xmlInternal = transform(xmlFromVendor, xsltLocation);
        return unmarshaller.unmarshal(new StreamSource(new StringReader(xmlInternal)));
    }
}

并使用 Spring MVC 注册此参数解析器:

<mvc:annotation-driven> 
   <mvc:argument-resolvers>
        <bean class="XsltTransformingHandlerMethodArgumentResolver"></bean>
   </mvc:argument-resolvers>
</mvc:annotation-driven>
于 2012-09-25T02:32:10.797 回答