我一直致力于使用 JAX-RS 2 在无状态的 RESTful 接口中包装有状态的 CGI/Perl Web 应用程序。除了与 CGI/Perl Web 应用程序交互的机制(考虑明智地使用 HTTP 客户端和 HTML 抓取),一个具有挑战性的方面是确保对 REST 服务的同时请求不会最终共享 CGI/Perl 应用程序会话。
每个请求都必须为特定的 webapp 用户建立和结束会话;在此期间的任何传入请求都不应与同一用户建立会话。
我最初的想法是自定义 Tomcat 的线程池执行器,以便每个线程都绑定到一个特定的用户。我在这方面取得了进展,但在与记录不足的
tomcat7-maven-plugin
斗争之后放弃了这种方法。此外,考虑到我的自定义
org.apache.catalina.Executor
如何从
标准实现中
大量“借用”,我对在如此低的级别耦合到 Tomcat 持怀疑态度。
我最终确定的方法由以下部分组成:
-
具有两个子模块的基于 Maven 的 Spring Boot + Jersey 2 项目
- CGI/Perl 网络应用交互库
- JAX-RS 网络服务提供围绕该库的 RESTful 接口
-
java.util.concurrent.BlockingQueue
用于管理线程对有限用户 ID 池的访问,以及用于管理队列的自定义 Servlet 过滤器。 -
自定义注入提供程序
,负责通过
@Context
或@Inject
注释将会话注入 JAX-RS 资源。
带有 Jersey 平台的 Maven、Spring Boot 和 JAX-RS 2
这是 JAX-RS 组件的 POM 配置(交互库的父 POM 和 POM 并不令人兴奋或与手头的事情无关)。
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
需要强调的重要领域:
- 因为我有自己的父 POM,所以我使用 dependencyManagement POM 导入 方法来引入 Spring Boot 依赖项。
-
我将 maven-war-plugin 自定义为不
failOnMissingWebXml
因为我使用注释来配置我的 Servlet 应用程序并且不想创建一个空的web.xml
我在
src/main/resources/application.properties
文件中定义了我的 CGI/Perl 应用程序用户 ID 池
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
并创建了一个自定义配置类,用于将这些用户检索为
java.util.Set
以避免多次指定相同用户 ID 时出现问题:
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
通过将此类标记为
@Component
,我可以依靠 Spring 来管理此配置并将其注入其他 bean。
最后,我使用自定义的 Jersey
ResourceConfig
扩展引导了我的 Web 应用程序,并用 Spring Boot 的优点进行了装饰。
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
在构造函数中,我将我希望注入的
AppSession
实例绑定到我将用来注入它们的工厂类,即创造性地命名的
AppSessionInjectionFactory
。
AppSession
类是 CGI/Perl 应用程序交互库的入口点。除了以下事实之外,它的代码与主题无关
-
每个
AppSession
实例都绑定到一个特定的、珍贵的用户 ID 字符串。 -
AppSession
实现了java.lang.AutoCloseable
接口。这允许它在 try-with-resources 块中使用,并通知 API 用户这是必须关闭的资源。
Servlet过滤器
我想专门管理
AppSessionInjectionFactory
中的 BlockingQueue,但遇到了范围问题。
也就是说,Jersey 的默认行为是为每次注入创建一个新的工厂实例。我尝试更改绑定的范围并将我的整体绑定策略更改为
bindFactory(new AppSessionInjectionFactory()).to(AppSession.class)
(请注意我如何创建单个实例而不是使用类文字提供 bindFactory 方法)。在这两种情况下,我都无法让 Spring 将
AppConfig
bean 注入
AppSessionInjectionFactory
中。因此,我决定使用 Servlet Filter,我知道它只会被实例化一次。这是过滤器的代码,在我看来,这是应用程序中最有趣的部分。
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
doFilter
方法尝试从 BlockingQueue 中获取用户 ID。如果没有可用的用户 ID,它将阻塞,直到将用户 ID 放回池中。一旦获得用户 ID,它将实例化一个
AppSession
并将其作为属性存储在 HttpServletRequest 中,以供链中的其他过滤器将来使用。过滤器链返回后,它会替换 BlockingQueue 中的用户 ID。
定制注塑厂
最后一部分是工厂本身,它使请求属性绑定的
AppSession
可用于任何需要它的 JAX-RS 资源。
这个类的代码非常简单。唯一棘手的部分是将类注释为请求范围,以便它可以在实例化时用当前的
HttpServletRequest
注入。
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
回报
现在我们已经解决了所有这些麻烦以确保没有两个请求线程共享同一个 AppSession 实例,让我们实际看一下 JAX-RS 资源示例。
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example.bdkosher/groupId>
<artifactId>cgi-app-wrapper</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cgi-app-rest-api</artifactId>
<packaging>war</packaging>
<properties>
<spring-boot.version>1.2.4.RELEASE</spring-boot.version>
<java.version>1.7</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>cgi-app-interaction-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
</dependencies>
<build>
<finalName>cgiwrapp</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
</project>
在实际应用程序中,我正在与
AppSession
对象的更有用的方法进行交互。但这个简单的示例说明了主要目标:我已将我有限的资源注入到我的 JAX-RS 资源中,使我的服务代码从管理它和保护它免受共享访问的责任中解放出来。