cxf-codegen-plugin 不符合 cxf ValidationFeature
cxf-codegen-plugin does not comply with cxf ValidationFeature
根据 ValidationFeature documentation 为了进行验证,操作输入和输出绑定必须用 @Valid
注释
然而,cxf-codegen-plugin 生成的 webservice 接口没有这些注释,而且我似乎没有找到允许添加它们的命令行参数或插件。
在不违反 Liskov 替换原则的情况下,不能将 @Valid
注释放在 web 服务接口的实现中:JSR-349(Hibernate Validator)的参考实现在这种情况下会产生 HV000151:覆盖另一个方法的方法不得更改参数约束配置
问题:有人知道用@Valid
注释cxf生成的webservice接口方法参数的方法吗?
我知道 Annox plugin 的存在,但这似乎不是一件容易的事。
最简单的解决方案可能是手动将 @Valid
注释添加到 web 服务接口,但我不习惯修改生成的代码
例子
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.www</groupId>
<artifactId>webservice-test-bval</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Test bean validation on web service</name>
<properties>
<org.springframework.boot.version>1.4.2.RELEASE</org.springframework.boot.version>
<com.github.krasa.krasa-jaxb-tools>1.5</com.github.krasa.krasa-jaxb-tools>
<org.apache.cxf.version>3.1.3</org.apache.cxf.version>
<cxf-codegen-plugin.version>3.0.1</cxf-codegen-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${org.springframework.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- CXF dependencies -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
<!-- Schema validation -->
<dependency>
<groupId>com.github.krasa</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>${com.github.krasa.krasa-jaxb-tools}</version>
<exclusions>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${org.springframework.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-codegen-plugin.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/test.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/test.wsdl</wsdlLocation>
<extraargs>
<extraarg>-xjc-XJsr303Annotations</extraarg>
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.krasa</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>${com.github.krasa.krasa-jaxb-tools}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
src/main/resources/wsdl/
test.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.org/test/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="test" targetNamespace="http://www.example.org/test/">
<wsdl:types>
<xsd:schema targetNamespace="http://www.example.org/test/">
<xsd:element name="NewOperation">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in">
<xsd:simpleType>
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="10" />
<xsd:maxInclusive value="20" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="NewOperationResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="out">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:pattern value="[A-Z]+" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="NewOperationRequest">
<wsdl:part element="tns:NewOperation" name="parameters" />
</wsdl:message>
<wsdl:message name="NewOperationResponse">
<wsdl:part element="tns:NewOperationResponse" name="parameters" />
</wsdl:message>
<wsdl:portType name="testWS">
<jaxws:bindings xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
<jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>
<wsdl:operation name="NewOperation">
<wsdl:input message="tns:NewOperationRequest" />
<wsdl:output message="tns:NewOperationResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="testWSSOAP" type="tns:testWS">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="NewOperation">
<soap:operation soapAction="http://www.example.org/test/NewOperation" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="testWS">
<wsdl:port binding="tns:testWSSOAP" name="testWSSOAP">
<soap:address location="http://www.example.org/" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
src/main/java/
org.example.test
package org.example.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.support.SpringBootServletInitializer;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package org.example.test;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.validation.Valid;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
import org.example.test.ObjectFactory;
@WebService(targetNamespace = "http://www.example.org/test/", name = "testWS")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TestWSValid {
@WebMethod(operationName = "NewOperation", action = "http://www.example.org/test/NewOperation")
@Valid @WebResult(name = "NewOperationResponse", targetNamespace = "http://www.example.org/test/", partName = "parameters")
public NewOperationResponse newOperation(
@Valid @WebParam(partName = "parameters", name = "NewOperation", targetNamespace = "http://www.example.org/test/")
NewOperation parameters
);
}
org.example.test.配置
package org.example.test.configuration;
import javax.xml.ws.Endpoint;
import org.apache.cxf.Bus;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.apache.cxf.validation.BeanValidationFeature;
import org.example.test.services.TestWSImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource({ "classpath:META-INF/cxf/cxf.xml", "classpath:META-INF/cxf/cxf-servlet.xml" })
@ComponentScan({ "org.example.test" })
public class ApplicationConfiguration {
@Autowired
private Bus cxfBus;
@Bean
public Endpoint testWSEndpoint() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, new TestWSImpl());
endpoint.setAddress("/testws");
endpoint.publish();
return endpoint;
}
@Bean
public ServletRegistrationBean cxfServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new CXFServlet(), "/services/*");
servlet.setLoadOnStartup(1);
return servlet;
}
@Bean
public Feature validationFeature() {
Feature validationFeature = new BeanValidationFeature();
validationFeature.initialize(cxfBus);
cxfBus.getFeatures().add(validationFeature);
ConstraintViolationInterceptor interceptor = new ConstraintViolationInterceptor();
cxfBus.getInFaultInterceptors().add(interceptor);
cxfBus.getOutFaultInterceptors().add(interceptor);
cxfBus.getProperties().put("exceptionMessageCauseEnabled", true);
return validationFeature;
}
}
package org.example.test.configuration;
import java.text.MessageFormat;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.binding.soap.interceptor.Soap11FaultOutInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
public class ConstraintViolationInterceptor extends AbstractSoapInterceptor {
public ConstraintViolationInterceptor() {
super(Phase.MARSHAL);
getBefore().add(Soap11FaultOutInterceptor.class.getName());
}
private static final String TEMPLATE = "[{0}] {1} : {2}";
@Override
public void handleMessage(SoapMessage message) throws Fault {
Fault fault = (Fault) message.getContent(Exception.class);
Throwable exception = fault.getCause();
if (exception instanceof ConstraintViolationException) {
fault.setMessage(processConstraints((ConstraintViolationException) exception));
}
}
private String processConstraints(ConstraintViolationException exception) {
return exception.getConstraintViolations().stream().map((error) -> {
return MessageFormat.format(TEMPLATE, error.getPropertyPath(), error.getMessage(), error.getInvalidValue());
}).collect(Collectors.joining(System.lineSeparator()));
}
}
org.example.test.服务
package org.example.test.services;
import javax.jws.WebService;
import org.example.test.NewOperation;
import org.example.test.NewOperationResponse;
import org.example.test.ObjectFactory;
import org.example.test.TestWS;
@WebService(endpointInterface = "org.example.test.TestWS", portName = "TestWSPort", serviceName = "TestWS", targetNamespace = "http://www.example.org/test/")
public class TestWSImpl implements TestWS {
@Override
public NewOperationResponse newOperation(NewOperation parameters) {
int in = parameters.getIn();
NewOperationResponse response = new ObjectFactory().createNewOperationResponse();
if (in < 10 || in > 20) {
response.setOut("no no no");
} else {
response.setOut("OK");
}
return response;
}
}
参考上面的项目,可以测试只要TestWSImpl implements TestWS
(生成的class)没有验证发生,但是如果TestWSImpl implements TestWSValid
(class 包含带有 @Valid
添加的生成代码)然后验证按预期工作
Apache CXF 的 wsdl2java 支持是可插入的。有一个 META-INF/tools-plugin.xml 描述符允许您定义自定义生成器 ("frontend profiles")。因此,如果您需要在所有 cxf-generated 网络服务接口上使用 @Valid
注释,您可以 plug-in 自定义 SEIGenerator
。
Apache CXF 使用 Velocity 模板生成 SEI 接口。所以你只需要用自定义模板覆盖默认模板即可。
因此,您可以创建一个简单的 cxf-codegen-plugin
覆盖,而不是使用 Anox 或 Krasa。
所以让我们创建一个单独的项目,您仍然可以将它放在同一个项目中,但放在不同的模块中,但是为了更好的可重用性,我会说一个新项目。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.example.test</groupId>
<artifactId>valid-cxf</artifactId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
</dependencies>
</project>
让我们使用服务加载器来定义新的默认 SEI 生成器。
src/main/resources/META-INF/tools-plugin.xml
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns="http://cxf.apache.org/tools/plugin" name="play" version="" provider="play.typesafe.com">
<frontend name="sample" package="org.apache.cxf.tools.wsdlto.frontend.jaxws" profile="JAXWSProfile">
<container name="JAXWSContainer" package="org.apache.cxf.tools.wsdlto.frontend.jaxws" toolspec="jaxws-toolspec.xml"/>
<processor name="WSDLToJavaProcessor" package="org.apache.cxf.tools.wsdlto.frontend.jaxws.processor"/>
<builder name="JAXWSDefinitionBuilder" package="org.apache.cxf.tools.wsdlto.frontend.jaxws.wsdl11"/>
<generators package="com.example.plugin">
<generator name="CustomSEIGenerator"/>
</generators>
</frontend>
这里我们从包 com.example.plugin
中定义了一个名称为 CustomSEIGenerator
的新 SEIGenerator
接下来让我们定义一个带有我们所有覆盖的速度模板(在我们的例子中只是硬编码 @Valid
Web 服务接口上的注释)这是基于官方 CXF sei.vm
src/main/resources/valid-sei.vm
#if ($intf.packageJavaDoc != "")
/**
$intf.packageJavaDoc
*/
#end
package $intf.PackageName;
#if ($mark-generated == "true")
import javax.annotation.Generated;
#end
import javax.validation.Valid;
#foreach ($import in $intf.Imports)
import ${import};
#end
/**
#if ($intf.classJavaDoc != "")
$intf.classJavaDoc
*
#end
* This class was generated by $fullversion
* $currentdate
* Generated source version: $version
*
*/
#foreach ($annotation in $intf.Annotations)
$annotation
#end
#if ($mark-generated == "true")
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "$currentdate", comments = "$fullversion")
#end
public interface $intf.Name ${sei-superinterface-string}{
#foreach ($method in $intf.Methods)
#if ($method.JavaDoc != "")
/**
${method.JavaDoc}
*/
#end
#foreach ($annotation in $method.Annotations)
$annotation
#end
@Valid
#if ($mark-generated == "true")
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "$currentdate")
#end
public $method.returnValue ${method.Name}(#if($method.ParameterList.size() == 0))#end
#if($method.ParameterList.size() != 0)
#foreach ($param in ${method.ParameterList})
$param
#end
)#end#if($method.Exceptions.size() > 0) throws#foreach($exception in $method.Exceptions) $exception.ClassName#if($method.Exceptions.size() != $velocityCount),#end#end#end;
#end
}
最后让我们创建 CustomSEIGenerator
,它将使用我们的速度模板。
package org.example.test;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.generators.SEIGenerator;
import java.io.Writer;
/**
* Just a sample custom generator which use custom velocity template to generate SEI
*
*/
public class CustomSEIGenerator extends SEIGenerator {
@Override
protected void doWrite(String templateName, Writer outputs) throws ToolException {
if (templateName.endsWith("/sei.vm")) {
templateName = "valid-sei.vm";
}
super.doWrite(templateName, outputs);
}
}
假设你已经构建了插件并覆盖并发布到你的本地maven repo,接下来你只需要向你的项目添加一个依赖信息pom.xml
。
因此,问题中的 pom 将保持不变,只有 cxf-codegen-plugin
会获得新的依赖项。
在你的pom.xml
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-codegen-plugin.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/test.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/test.wsdl</wsdlLocation>
</wsdlOption>
<extraargs>
<extraarg>-fe</extraarg>
<extraarg>sample</extraarg>
</extraargs>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.example.test</groupId>
<artifactId>valid-cxf</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
就是这样,现在您可以完全管理从 wsdl 生成的内容和生成方式。
可以在此 repo 中找到一个完整的示例 https://github.com/babltiga/cxf-valid-sample
郑重声明,以下是我认为最适合 CustomSEIGenerator 实施的内容:
package org.example.test;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.xml.namespace.QName;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.tools.common.ToolContext;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.common.model.JAnnotation;
import org.apache.cxf.tools.common.model.JavaInterface;
import org.apache.cxf.tools.common.model.JavaMethod;
import org.apache.cxf.tools.common.model.JavaModel;
import org.apache.cxf.tools.common.model.JavaParameter;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.generators.SEIGenerator;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.WSDLToJavaProcessor;
public class CustomSEIGenerator extends SEIGenerator {
private static final String VALID_PARAM = "VALID_PARAM";
private static final String VALID_RETURN = "VALID_RETURN";
@Override
public void generate(ToolContext penv) throws ToolException {
JAnnotation validAnno = new JAnnotation(Valid.class);
Map<QName, JavaModel> map = CastUtils.cast((Map<?, ?>) penv.get(WSDLToJavaProcessor.MODEL_MAP));
for (JavaModel javaModel : map.values()) {
Map<String, JavaInterface> interfaces = javaModel.getInterfaces();
for (JavaInterface intf : interfaces.values()) {
intf.addImport(Valid.class.getCanonicalName());
List<JavaMethod> methods = intf.getMethods();
for (JavaMethod method : methods) {
List<JavaParameter> parameters = method.getParameters();
method.addAnnotation(VALID_RETURN, validAnno);
for (JavaParameter param : parameters) {
param.addAnnotation(VALID_PARAM, validAnno);
}
}
}
}
super.generate(penv);
}
}
根据 ValidationFeature documentation 为了进行验证,操作输入和输出绑定必须用 @Valid
然而,cxf-codegen-plugin 生成的 webservice 接口没有这些注释,而且我似乎没有找到允许添加它们的命令行参数或插件。
在不违反 Liskov 替换原则的情况下,不能将 @Valid
注释放在 web 服务接口的实现中:JSR-349(Hibernate Validator)的参考实现在这种情况下会产生 HV000151:覆盖另一个方法的方法不得更改参数约束配置
问题:有人知道用@Valid
注释cxf生成的webservice接口方法参数的方法吗?
我知道 Annox plugin 的存在,但这似乎不是一件容易的事。
最简单的解决方案可能是手动将 @Valid
注释添加到 web 服务接口,但我不习惯修改生成的代码
例子
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example.www</groupId>
<artifactId>webservice-test-bval</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Test bean validation on web service</name>
<properties>
<org.springframework.boot.version>1.4.2.RELEASE</org.springframework.boot.version>
<com.github.krasa.krasa-jaxb-tools>1.5</com.github.krasa.krasa-jaxb-tools>
<org.apache.cxf.version>3.1.3</org.apache.cxf.version>
<cxf-codegen-plugin.version>3.0.1</cxf-codegen-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${org.springframework.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring boot dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- CXF dependencies -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
<!-- Schema validation -->
<dependency>
<groupId>com.github.krasa</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>${com.github.krasa.krasa-jaxb-tools}</version>
<exclusions>
<exclusion>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${org.springframework.boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-codegen-plugin.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/test.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/test.wsdl</wsdlLocation>
<extraargs>
<extraarg>-xjc-XJsr303Annotations</extraarg>
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.github.krasa</groupId>
<artifactId>krasa-jaxb-tools</artifactId>
<version>${com.github.krasa.krasa-jaxb-tools}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
src/main/resources/wsdl/
test.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.org/test/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="test" targetNamespace="http://www.example.org/test/">
<wsdl:types>
<xsd:schema targetNamespace="http://www.example.org/test/">
<xsd:element name="NewOperation">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="in">
<xsd:simpleType>
<xsd:restriction base="xsd:int">
<xsd:minInclusive value="10" />
<xsd:maxInclusive value="20" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="NewOperationResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="out">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:pattern value="[A-Z]+" />
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="NewOperationRequest">
<wsdl:part element="tns:NewOperation" name="parameters" />
</wsdl:message>
<wsdl:message name="NewOperationResponse">
<wsdl:part element="tns:NewOperationResponse" name="parameters" />
</wsdl:message>
<wsdl:portType name="testWS">
<jaxws:bindings xmlns:jaxws="http://java.sun.com/xml/ns/jaxws">
<jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
</jaxws:bindings>
<wsdl:operation name="NewOperation">
<wsdl:input message="tns:NewOperationRequest" />
<wsdl:output message="tns:NewOperationResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="testWSSOAP" type="tns:testWS">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="NewOperation">
<soap:operation soapAction="http://www.example.org/test/NewOperation" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="testWS">
<wsdl:port binding="tns:testWSSOAP" name="testWSSOAP">
<soap:address location="http://www.example.org/" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
src/main/java/
org.example.test
package org.example.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.support.SpringBootServletInitializer;
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package org.example.test;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.validation.Valid;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;
import org.example.test.ObjectFactory;
@WebService(targetNamespace = "http://www.example.org/test/", name = "testWS")
@XmlSeeAlso({ObjectFactory.class})
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface TestWSValid {
@WebMethod(operationName = "NewOperation", action = "http://www.example.org/test/NewOperation")
@Valid @WebResult(name = "NewOperationResponse", targetNamespace = "http://www.example.org/test/", partName = "parameters")
public NewOperationResponse newOperation(
@Valid @WebParam(partName = "parameters", name = "NewOperation", targetNamespace = "http://www.example.org/test/")
NewOperation parameters
);
}
org.example.test.配置
package org.example.test.configuration;
import javax.xml.ws.Endpoint;
import org.apache.cxf.Bus;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.apache.cxf.validation.BeanValidationFeature;
import org.example.test.services.TestWSImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource({ "classpath:META-INF/cxf/cxf.xml", "classpath:META-INF/cxf/cxf-servlet.xml" })
@ComponentScan({ "org.example.test" })
public class ApplicationConfiguration {
@Autowired
private Bus cxfBus;
@Bean
public Endpoint testWSEndpoint() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, new TestWSImpl());
endpoint.setAddress("/testws");
endpoint.publish();
return endpoint;
}
@Bean
public ServletRegistrationBean cxfServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new CXFServlet(), "/services/*");
servlet.setLoadOnStartup(1);
return servlet;
}
@Bean
public Feature validationFeature() {
Feature validationFeature = new BeanValidationFeature();
validationFeature.initialize(cxfBus);
cxfBus.getFeatures().add(validationFeature);
ConstraintViolationInterceptor interceptor = new ConstraintViolationInterceptor();
cxfBus.getInFaultInterceptors().add(interceptor);
cxfBus.getOutFaultInterceptors().add(interceptor);
cxfBus.getProperties().put("exceptionMessageCauseEnabled", true);
return validationFeature;
}
}
package org.example.test.configuration;
import java.text.MessageFormat;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.binding.soap.interceptor.Soap11FaultOutInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
public class ConstraintViolationInterceptor extends AbstractSoapInterceptor {
public ConstraintViolationInterceptor() {
super(Phase.MARSHAL);
getBefore().add(Soap11FaultOutInterceptor.class.getName());
}
private static final String TEMPLATE = "[{0}] {1} : {2}";
@Override
public void handleMessage(SoapMessage message) throws Fault {
Fault fault = (Fault) message.getContent(Exception.class);
Throwable exception = fault.getCause();
if (exception instanceof ConstraintViolationException) {
fault.setMessage(processConstraints((ConstraintViolationException) exception));
}
}
private String processConstraints(ConstraintViolationException exception) {
return exception.getConstraintViolations().stream().map((error) -> {
return MessageFormat.format(TEMPLATE, error.getPropertyPath(), error.getMessage(), error.getInvalidValue());
}).collect(Collectors.joining(System.lineSeparator()));
}
}
org.example.test.服务
package org.example.test.services;
import javax.jws.WebService;
import org.example.test.NewOperation;
import org.example.test.NewOperationResponse;
import org.example.test.ObjectFactory;
import org.example.test.TestWS;
@WebService(endpointInterface = "org.example.test.TestWS", portName = "TestWSPort", serviceName = "TestWS", targetNamespace = "http://www.example.org/test/")
public class TestWSImpl implements TestWS {
@Override
public NewOperationResponse newOperation(NewOperation parameters) {
int in = parameters.getIn();
NewOperationResponse response = new ObjectFactory().createNewOperationResponse();
if (in < 10 || in > 20) {
response.setOut("no no no");
} else {
response.setOut("OK");
}
return response;
}
}
参考上面的项目,可以测试只要TestWSImpl implements TestWS
(生成的class)没有验证发生,但是如果TestWSImpl implements TestWSValid
(class 包含带有 @Valid
添加的生成代码)然后验证按预期工作
Apache CXF 的 wsdl2java 支持是可插入的。有一个 META-INF/tools-plugin.xml 描述符允许您定义自定义生成器 ("frontend profiles")。因此,如果您需要在所有 cxf-generated 网络服务接口上使用 @Valid
注释,您可以 plug-in 自定义 SEIGenerator
。
Apache CXF 使用 Velocity 模板生成 SEI 接口。所以你只需要用自定义模板覆盖默认模板即可。
因此,您可以创建一个简单的 cxf-codegen-plugin
覆盖,而不是使用 Anox 或 Krasa。
所以让我们创建一个单独的项目,您仍然可以将它放在同一个项目中,但放在不同的模块中,但是为了更好的可重用性,我会说一个新项目。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<groupId>org.example.test</groupId>
<artifactId>valid-cxf</artifactId>
<version>1.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${org.apache.cxf.version}</version>
</dependency>
</dependencies>
</project>
让我们使用服务加载器来定义新的默认 SEI 生成器。
src/main/resources/META-INF/tools-plugin.xml
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns="http://cxf.apache.org/tools/plugin" name="play" version="" provider="play.typesafe.com">
<frontend name="sample" package="org.apache.cxf.tools.wsdlto.frontend.jaxws" profile="JAXWSProfile">
<container name="JAXWSContainer" package="org.apache.cxf.tools.wsdlto.frontend.jaxws" toolspec="jaxws-toolspec.xml"/>
<processor name="WSDLToJavaProcessor" package="org.apache.cxf.tools.wsdlto.frontend.jaxws.processor"/>
<builder name="JAXWSDefinitionBuilder" package="org.apache.cxf.tools.wsdlto.frontend.jaxws.wsdl11"/>
<generators package="com.example.plugin">
<generator name="CustomSEIGenerator"/>
</generators>
</frontend>
这里我们从包 com.example.plugin
CustomSEIGenerator
的新 SEIGenerator
接下来让我们定义一个带有我们所有覆盖的速度模板(在我们的例子中只是硬编码 @Valid
Web 服务接口上的注释)这是基于官方 CXF sei.vm
src/main/resources/valid-sei.vm
#if ($intf.packageJavaDoc != "")
/**
$intf.packageJavaDoc
*/
#end
package $intf.PackageName;
#if ($mark-generated == "true")
import javax.annotation.Generated;
#end
import javax.validation.Valid;
#foreach ($import in $intf.Imports)
import ${import};
#end
/**
#if ($intf.classJavaDoc != "")
$intf.classJavaDoc
*
#end
* This class was generated by $fullversion
* $currentdate
* Generated source version: $version
*
*/
#foreach ($annotation in $intf.Annotations)
$annotation
#end
#if ($mark-generated == "true")
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "$currentdate", comments = "$fullversion")
#end
public interface $intf.Name ${sei-superinterface-string}{
#foreach ($method in $intf.Methods)
#if ($method.JavaDoc != "")
/**
${method.JavaDoc}
*/
#end
#foreach ($annotation in $method.Annotations)
$annotation
#end
@Valid
#if ($mark-generated == "true")
@Generated(value = "org.apache.cxf.tools.wsdlto.WSDLToJava", date = "$currentdate")
#end
public $method.returnValue ${method.Name}(#if($method.ParameterList.size() == 0))#end
#if($method.ParameterList.size() != 0)
#foreach ($param in ${method.ParameterList})
$param
#end
)#end#if($method.Exceptions.size() > 0) throws#foreach($exception in $method.Exceptions) $exception.ClassName#if($method.Exceptions.size() != $velocityCount),#end#end#end;
#end
}
最后让我们创建 CustomSEIGenerator
,它将使用我们的速度模板。
package org.example.test;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.generators.SEIGenerator;
import java.io.Writer;
/**
* Just a sample custom generator which use custom velocity template to generate SEI
*
*/
public class CustomSEIGenerator extends SEIGenerator {
@Override
protected void doWrite(String templateName, Writer outputs) throws ToolException {
if (templateName.endsWith("/sei.vm")) {
templateName = "valid-sei.vm";
}
super.doWrite(templateName, outputs);
}
}
假设你已经构建了插件并覆盖并发布到你的本地maven repo,接下来你只需要向你的项目添加一个依赖信息pom.xml
。
因此,问题中的 pom 将保持不变,只有 cxf-codegen-plugin
会获得新的依赖项。
在你的pom.xml
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${cxf-codegen-plugin.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<sourceRoot>${project.build.directory}/generated/</sourceRoot>
<wsdlOptions>
<wsdlOption>
<wsdl>${project.basedir}/src/main/resources/wsdl/test.wsdl</wsdl>
<wsdlLocation>classpath:wsdl/test.wsdl</wsdlLocation>
</wsdlOption>
<extraargs>
<extraarg>-fe</extraarg>
<extraarg>sample</extraarg>
</extraargs>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.example.test</groupId>
<artifactId>valid-cxf</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
就是这样,现在您可以完全管理从 wsdl 生成的内容和生成方式。
可以在此 repo 中找到一个完整的示例 https://github.com/babltiga/cxf-valid-sample
郑重声明,以下是我认为最适合 CustomSEIGenerator 实施的内容:
package org.example.test;
import java.util.List;
import java.util.Map;
import javax.validation.Valid;
import javax.xml.namespace.QName;
import org.apache.cxf.helpers.CastUtils;
import org.apache.cxf.tools.common.ToolContext;
import org.apache.cxf.tools.common.ToolException;
import org.apache.cxf.tools.common.model.JAnnotation;
import org.apache.cxf.tools.common.model.JavaInterface;
import org.apache.cxf.tools.common.model.JavaMethod;
import org.apache.cxf.tools.common.model.JavaModel;
import org.apache.cxf.tools.common.model.JavaParameter;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.generators.SEIGenerator;
import org.apache.cxf.tools.wsdlto.frontend.jaxws.processor.WSDLToJavaProcessor;
public class CustomSEIGenerator extends SEIGenerator {
private static final String VALID_PARAM = "VALID_PARAM";
private static final String VALID_RETURN = "VALID_RETURN";
@Override
public void generate(ToolContext penv) throws ToolException {
JAnnotation validAnno = new JAnnotation(Valid.class);
Map<QName, JavaModel> map = CastUtils.cast((Map<?, ?>) penv.get(WSDLToJavaProcessor.MODEL_MAP));
for (JavaModel javaModel : map.values()) {
Map<String, JavaInterface> interfaces = javaModel.getInterfaces();
for (JavaInterface intf : interfaces.values()) {
intf.addImport(Valid.class.getCanonicalName());
List<JavaMethod> methods = intf.getMethods();
for (JavaMethod method : methods) {
List<JavaParameter> parameters = method.getParameters();
method.addAnnotation(VALID_RETURN, validAnno);
for (JavaParameter param : parameters) {
param.addAnnotation(VALID_PARAM, validAnno);
}
}
}
}
super.generate(penv);
}
}