有了前一篇 应用 Axis 1.4 开发 WebService 的对 Axis 1 较为深刻的理解后,现在正式给古老的 Axis 1.4 拉个伴,那就是 SpringBoot2。SpringBoot2 + Axis 1 的主要工作就是把 Axis 的 web.xml 用 SpringBoot2 的方式进行转述。
在 SpringBoot 中用 Axis 1 后,有两个特性不再支持
- 不再支持 jws 即时发布 Web Service,不能直接搬用 url-pattern *.jws,没继续深究,实际中希望这么部署的方式用得较少
- 不再支持 SOAPMonitorService,它是一个 Java Applet, Java Applet 在新版的 JDK 中已被移除,早就不推荐使用了
在 SpringBoot 中配置 Servlet 或 ServletListener 有两种方式
- ServletRegistrationBean/ServletListenerRegistrationBean
- @WebServlet/@WebListener
spring-boot-starter 引入了 log4j-to-slf4j, jul-to-slf4j, 所以不需要配置 log4j.properties, 需要的话可用 logback.xml 配置日志输出。
下面来看整个 SpringBoot2 + Axis 1 项目的目录结构(Maven 项目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
springboot2-axis1.4 ├── pom.xml └── src ├── main │ ├── java │ │ └── blog │ │ └── yanbin │ │ └── ws │ │ ├── HelloService.java │ │ ├── Main.java │ │ └── config │ │ ├── AppConfig.java │ │ └── AppConfig1.java │ ├── resources │ │ ├── application.properties │ │ └── logback.xml │ └── webapp │ └── WEB-INF │ └── server-config.wsdd └── test ├── java │ └── blog │ └── yanbin │ └── ws │ ├── RunAdminClient.java │ └── ServiceTest.java └── resources ├── deploy.wsdd └── undeploy.wsdd |
该项目已上传至 GitHub springboot2-axis1.4
项目及配置解析
下面是对该项目的关键部分的说明
依赖包
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>blog.yanbin.ws</groupId> <artifactId>springboot2-axis1</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot2-axis1</name> <description>Demo project for SpringBoot2 with Axis1</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>commons-discovery</groupId> <artifactId>commons-discovery</artifactId> <version>0.2</version> </dependency> <!-- Axis 1.4 --> <dependency> <groupId>org.apache.axis</groupId> <artifactId>axis</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>org.apache.axis</groupId> <artifactId>axis-jaxrpc</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>axis</groupId> <artifactId>axis-wsdl4j</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>org.apache.axis</groupId> <artifactId>axis-saaj</artifactId> <version>1.4</version> </dependency> <!-- Axis 1.4 --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
Axis 1.4 中间的依赖是根据下载的 axis-bin-1_4.tar.gz 解压后 webapps/axis/WEB-INFO/lib 目录中 jar 包依赖改成用 Maven 来管理,外加了一个 javax.mail 依赖,省得启动服务时总提示没有 mail 组件的警告信息。其他为 SpringBoot2 的依赖,由于 Axis 是一个 Web 项目,所以引入了 spring-boot-starter-web。
日志方面,由于 spring-boot-starter-logging 的介入,log4j, jul 的日志全被 slf4j 接管,最终由 LogBack 输出,所以只需要 logback.xml,或者完全在 application.properties 中配置日志也行。
src/main/webapps/WEB-INF/server-config.wsdd 文件是拷贝自 axis 的 server-config.wsdd,用 WSDD 的方式发布 Web Service 要用到它
Servlet/Listener 配置
接着就是转换 axis 的 web.xml 为 SpringBoot 的表达方式,有两种实现
ServletRegistrationBean/ServletListenerRegistrationBean
AppConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package blog.yanbin.ws.config; import org.apache.axis.transport.http.AdminServlet; import org.apache.axis.transport.http.AxisHTTPSessionListener; import org.apache.axis.transport.http.AxisServlet; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public ServletRegistrationBean<AxisServlet> axisServlet() { AxisServlet servlet = new AxisServlet(); return new ServletRegistrationBean<>(servlet, "/servlet/AxisServlet", "/services/*", "/axis/services/*"); } @Bean public ServletRegistrationBean<AdminServlet> axisAdminServlet() { AdminServlet servlet = new AdminServlet(); ServletRegistrationBean<AdminServlet> adminServletBean = new ServletRegistrationBean<>(servlet); adminServletBean.setLoadOnStartup(100); return adminServletBean; } @Bean public ServletListenerRegistrationBean<AxisHTTPSessionListener> axisSessionListener() { AxisHTTPSessionListener sessionListener = new AxisHTTPSessionListener(); return new ServletListenerRegistrationBean<>(sessionListener); } } |
@WebServlet/@WebListener 的方式
AppConfig1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package blog.yanbin.ws.config; import org.apache.axis.transport.http.AdminServlet; import org.apache.axis.transport.http.AxisHTTPSessionListener; import org.apache.axis.transport.http.AxisServlet; import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.Configuration; import javax.servlet.annotation.WebListener; import javax.servlet.annotation.WebServlet; @Configuration @ServletComponentScan public class AppConfig1 { @WebServlet({"/servlet/AxisServlet", "/services/*", "/axis/services/*"}) public static class MyAxisServlet extends AxisServlet { } @WebServlet(loadOnStartup = 100) public static class MyAxisAdminService extends AdminServlet { } @WebListener public static class MyAxisSessionListener extends AxisHTTPSessionListener { } } |
从中二选一,比如想要用 AppConfig, 则要把 AppConfig1 中的 @Configuration 注解注释掉,反之亦然。
注:用 @WebServlet/@WebListener 的方式时,Servlet 和 Listener 不能为实例的内部类,因此声明类时要用 public static class
加上 /axis/services/*
的 url-pattern
是因为 AdminClient 默认会调用 http://localhost:8080/axis/services/AdminService,如果总是用 -l
参数指定 url 的话,该 url-pattern
可省去。
应用程序入口
Main 启动类没什么特别的,就是一个最简单的 SpringBootApplication 启动类,后面说的重启应用就是重新运行下面的 Main 类
1 2 3 4 5 6 |
@SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } } |
到目前为止,我们可以启动本应用,这是一个 Web 项目(因为引入了 spring-boot-starter-web 依赖),它会起动一个内嵌的 Tomcat, 启动后打开 http://localhost:8080/servlet/,看到
这是一个熟悉的 Apache Axis 1 的 Web Service 列表界面,此时测试 Version.getVersion 服务是可用的, 访问 http://localhost:8080/services/Version?method=getVersion
开发一个简单的 Web Service
现在来开发一个自己简单的 Web Service - HelloService,要添加的 Java 类只要 HelloService, 代码为
1 2 3 4 5 6 7 8 |
package blog.yanbin.ws; public class HelloService { public String sayHello(String name) { return "Hello " + name; } } |
发布 HelloService,只要在 server-config.wsdd 中加上几行
1 2 3 4 5 |
<service name="HelloService" provider="java:RPC"> <parameter name="className" value="blog.yanbin.ws.HelloService"/> <parameter name="allowedMethods" value="*"/> <parameter name="scope" value="application"/> </service> |
暴露 blog.yanbin.ws.HelloService 中的所有 public 方法为 Web Service, 并且服务对象的生命周期是 application。要卸载 HelloService 服务,只要把这几行去掉,并重启应用
完后,只要重启应用程序,再次查看服务列表就能看到
用 http://localhost:8080/services/HelloService?wsdl 查看服务描述。
测试新加的 HelloService
Web 方式,访问 http://localhost:8080/services/HelloService?method=sayHello&name=Yanbin
HelloService 有一个接口是 sayHello, 来测试一下该接口
在 ServiceTest 中有测试该服务的代码,如下
1 2 3 4 5 6 7 8 9 |
@Test void sayHello() throws Exception { String servicesURL = "http://localhost:8080/services/HelloService"; Call call = new Call(servicesURL); String result = (String)call.invoke("", "sayHello", new Object[]{"Yanbin"}); assertEquals("Hello Yanbin", result); System.out.println(result); } |
测试通过,并且输出如下:
Hello Yanbin
同时我们在日志配置 logback.xml 设置了 org.apache.axis.transport.http.HTTPSender 的输出级别为 DEBUG,所以能完整看到 HTTP 交互的请求响应的详情
可利用该日志输出信息,用 cURL 或 PostMan 进行一样的 HTTP 请求,这方面的详情请参考上一篇 应用 Axis 1.4 开发 WebService。为了不来回跳跃,还是重复一下吧
$ curl -X POST -H "SOAPAction;" http://localhost:8080/services/HelloService \
--data '<?xml version="1.0"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<sayHello soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<arg0 xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:string">Yanbin</arg0>
</sayHello>
</soapenv:Body>
</soapenv:Envelope>'
<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><sayHelloResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><sayHelloReturn xsi:type="xsd:string">Hello Yanbin</sayHelloReturn></sayHelloResponse></soapenv:Body></soapenv:Envelope>
用 deploy.wsdd 文件部署服务
前面是直接修改 server-config.wsdd
的方来部署新服务的,我们也可以通过 AdminClient 来部署,写好了 HelloService 类后,把想要加入到 server-config.wsdd
文件中的内容放在一个单独的文件里,如这里的 deploy.wsdd,内容为
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="UTF-8"?> <deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service name="HelloService" provider="java:RPC"> <parameter name="className" value="blog.yanbin.ws.HelloService" /> <parameter name="allowedMethods" value="*" /> <parameter name="scope" value="request" /> </service> </deployment> |
然后可执行类似如下的命令
$ java -Djava.ext.dirs=lib org.apache.axis.client.AdminClient -lhttp://localhost:8080/services/AdminService test/resources/deloy.wsdd
-Djava.ext.dirs=lib 指定能运行 AdminClient 的含有足够依赖 jar 包的目录,如 axis-bin-1_4.tar.gz 解压后 webapps/axis/WEB-INFO/lib 目录
为了省事,当前项目包含必须的 jar 包,我们直接用 Java 代码来调用 AdminClient 更简单且可重用。本例中写在 RunAdminClient 类中,以单元测试用例的方式呈现
1 2 3 4 5 6 7 |
@Test void deployHelloService() { AdminClient.main(new String[]{ "-lhttp://localhost:8080/services/AdminService", "src/test/resources/deploy.wsdd" }); } |
如果要卸载该 HelloService, 把有关它的那几行从 server-config.wsdd
中移除掉,再重启服务就行。或者创建一个 undeploy.wsdd 文件,内容为
1 2 3 4 5 |
<?xml version="1.0" encoding="UTF-8"?> <undeployment xmlns="http://xml.apache.org/axis/wsdd/"> <service name="HelloService"> </service> </undeployment> |
再对它执行 AdminClient,如
$ java -Djava.ext.dirs=lib org.apache.axis.client.AdminClient -lhttp://localhost:8080/services/AdminService test/resources/deloy.wsdd
或是用 RunAdminClient 类中的
1 2 3 4 5 6 7 |
@Test void undeployHelloService() { AdminClient.main(new String[]{ "-lhttp://localhost:8080/services/AdminService", "src/test/resources/undeploy.wsdd" }); } |
用 AdminClient 的好处是不需要每次重启,因为它除了对 server-config.wsdd
文件修改外,还调用 AdminService 在内存里也作了手脚。
问题
如果在 IntelliJ IDEA 中,你的 Axis 应用是一个子模块,那么在 IDE 中启动时须配置
Working directory: $MODULE_DIR$
否则运行时报类似如下的错误
16 Nov 2021 19:25:11 [main] ERROR org.apache.axis.configuration.EngineConfigurationFactoryServlet.getServerEngineConfig(162) - Unable to find config file. Creating new servlet engine config file: /WEB-INF/server-config.wsdd
16 Nov 2021 19:25:11 [main] ERROR org.apache.axis.configuration.EngineConfigurationFactoryServlet.getServerEngineConfig(176) - Problem with servlet engine /WEB-INF directory
org.apache.axis.ConfigurationException: Configuration file directory '/private/var/folders/hm/t_nnzqw55g17fqwhf2rgtx3r0000gp/T/tomcat-docbase.8080.3352273356626804333/WEB-INF' does not exist or is not a directory or is not readable.
at org.apache.axis.configuration.FileProvider.<init>(FileProvider.java:99)
at org.apache.axis.configuration.EngineConfigurationFactoryServlet.getServerEngineConfig(EngineConfigurationFactoryServlet.java:174)
因为从当前目录开始找不到 src/main/webapp/WEB-INF,而且放在该目录中的 index.html 也不会被注册为 welcome file。
总结
最后好像也没有太多要说,升级到 Apache Axis2, 因为它最近都有更新,Axis 1 早就没人管了。或者还可以选择 Apache CXF 去,CXF 是两个项目的联合体,Celtix 和 XFire(XFire 还真有耳闻),曾经叫过 CeltiXfire,后来缩写成了 CXF(Celtix 含 CX, XFire 含 XF, 大家都公平)。还有一个选择是 Spring WS。这里有一篇三者对比的文章 Apache CXF vs. Apache AXIS vs. Spring WS。
链接: