Java Web 学习笔记之十五:Spring整合RestEasy

restEasy 使用

通过restEasy + Jetty 搭建简单的 rest web service

问题描述

简述

  • Servlet容器配置restEasy使其能够对http请求进行转发至我们编写的Service对象中。
  • Servlet具有自己的生命周期,并不在Spring容器中进行管理,即Servlet与Spring隔离。
  • restEasy请求处理分发Servlet对请求进行处理,实例化的Service对象与Spring容器隔离。因此我们无法通过熟悉的Spring各项功能对Service进行扩展(如自动注入依赖对象、AOP等功能)。
  • 实际业务中往往有需要使用Spring支持的功能,如利用AOP切面进行事务日志的记录等。

描述

1.Service对象与Spring容器隔离,其实也是可以使用Spring容器中的bean对象的,需要通过硬编码方式调用 applicationContext 获取bean:

1
2
3
ApplicationContext appContext = Main.getApplicationContext();//获取Main启动类中的spring容器上下文

UserDao userDao = (UserDao)appContext.getBean("userDao");//通过Spring容器上下文获取bean

这样获取spring容器中的bean是可行的

2.Service对象与Spring容器隔离,我们就不能通过 @Autowired 注解将Service对象中的属性进行依赖注入了

1
2
3
4
5
6
private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}

这样是不可行的

3.Service对象与Spring容器隔离,我们就无法使用Spring的AOP对Service进行切面编程,完成诸如事务日志的记录等功能了。

解决方案分析

reastEasy提供几种方式配置Service应用对象。

通过web.xml配置文件的标签配置resteasy.resources:

web.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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>SpringRestEasy</display-name>

<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest/v1</param-value>
</context-param>


<listener>
<listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>
<!-- 这里配置Service应用 -->
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>
com.github.johnsonmoon.resteasyspring.service.TestService1,
com.github.johnsonmoon.resteasyspring.service.TestService2,
com.github.johnsonmoon.resteasyspring.service.TestService3,
</param-value>
</context-param>


<!-- RestEasy请求分发Servlet -->
<servlet>
<servlet-name>rest-easy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>rest-easy</servlet-name>
<url-pattern>/rest/v1/*</url-pattern>
</servlet-mapping>
</web-app>

通过编码添加Service应用对象,ResteasyDeployment会在接收到请求后将其添加到restEasy中:

web.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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">

<display-name>JettyRestEasy</display-name>

<!-- 这里配置resteasy -->
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest/v1</param-value>
</context-param>

<!-- RestEasy请求分发Servlet -->
<servlet>
<servlet-name>rest-easy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>

<!-- 通过程序代码手动配置Service应用 -->
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit</param-value> <!-- 需要编写该类型 -->
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>rest-easy</servlet-name>
<url-pattern>/rest/v1/*</url-pattern>
</servlet-mapping>
</web-app>

RestEasyApplicationInit.java:

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
package com.github.johnsonmoon.resteasyspring.common;

import com.github.johnsonmoon.resteasyspring.Main;
import org.springframework.stereotype.Service;
import com.github.johnsonmoon.resteasyspring.service.TestService1;
import com.github.johnsonmoon.resteasyspring.service.TestService2;
import com.github.johnsonmoon.resteasyspring.service.TestService3;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

/**
* Created by johnsonmoon at 2018/5/10 15:33.
*/
public class RestEasyApplicationInit extends Application {
private Set<Object> set;

public RestEasyApplicationInit() {
Set<Object> set = new HashSet<>();
set.add(new TestService1());
set.add(new TestService2());
set.add(new TestService3());
this.set = set;
}

@Override
public Set<Object> getSingletons() {
return set;
}
}

两种方式比较

  • 通过web.xml的标签进行配置,得到的Service对象是restEasy自己进行实例化的,与Spring隔离。
  • 通过编码添加Service应用对象的方式,可以将Spring容器中的bean对象添加到restEasy中。

结论:

我们可以通过编码添加Service应用对象的方式,将Spring容器中管理的Service对象示例添加到RestEasy中,这样Service实例就跟普通的bean没有区别,能够实现Spring的其他功能。

解决方案示例讲解

  • 搭建一个简单的Spring + Jetty + RestEasy服务工程:

工程目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
| src 
| main
| java
| com.github.johnsonmoon.resteasyspring
| common
| RestEasyApplicationInit.java
| service
| TestService1.java
| TestService2.java
| TestService3.java
| Main.java
| resources
| app-context.xml
| pom.xml

编码

  • maven项目配置 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
    <?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>

    <groupId>com.github.johnsonmoon</groupId>
    <artifactId>resteasy-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <spring.version>4.3.6.RELEASE</spring.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    </properties>

    <dependencies>
    <!-- For jetty -->
    <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
    <version>9.4.2.v20170220</version>
    </dependency>
    <dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-webapp</artifactId>
    <version>9.4.2.v20170220</version>
    </dependency>

    <!-- For resteasy -->
    <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jaxrs</artifactId>
    <version>3.1.0.Final</version>
    </dependency>

    <!-- For Spring -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
    </dependency>
    </dependencies>
    </project>
  • Spring配置文件 app-context.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- base configurations and component scan -->
    <context:annotation-config/>
    <!-- 组件注解自动扫描 -->
    <context:component-scan base-package="com.github.johnsonmoon.resteasyspring"/>
    </beans>
  • 工程启动类 Main.java:

    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
    package com.github.johnsonmoon.resteasyspring;

    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    import java.net.InetSocketAddress;

    /**
    * Created by johnsonmoon at 2018/5/10 15:29.
    */
    public class Main {
    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
    return applicationContext;
    }

    public static void main(String... args) throws Exception {
    applicationContext = springStartup();
    jettyStartup();
    }

    private static ApplicationContext springStartup() {
    String contextFilePath = "classpath*:app-context.xml";
    ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(contextFilePath);
    classPathXmlApplicationContext.registerShutdownHook();
    return classPathXmlApplicationContext;
    }

    /**
    * 访问URL: http://127.0.0.1:9685/
    */
    private static void jettyStartup() throws Exception {
    String host = "127.0.0.1";
    String port = "9685";
    InetSocketAddress address = new InetSocketAddress(host, Integer.parseInt(port));
    Server server = new Server(address);

    ServletContextHandler servletContextHandler = new ServletContextHandler();
    servletContextHandler.setContextPath("/");
    //<!-- RestEasy请求分发Servlet -->
    ServletHolder servletHolder = new ServletHolder();
    servletHolder.setName("rest-easy");
    servletHolder.setClassName("org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher");
    //<!-- 通过程序代码配置请求转发 -->
    servletHolder.setInitParameter("javax.ws.rs.Application", "com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit");
    servletContextHandler.addServlet(servletHolder, "/*");

    server.setHandler(servletContextHandler);
    server.start();
    }
    }

其中,Jetty启动的配置通过代码实现,效果等同于下面的web.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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>RestEasySpring</display-name>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/</param-value>
</context-param>
<!-- RestEasy请求分发Servlet -->
<servlet>
<servlet-name>rest-easy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
<!-- 通过程序代码配置请求转发 -->
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>rest-easy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

  • RestEasy初始化配置Service应用类 RestEasyApplicationInit.java:
    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
    package com.github.johnsonmoon.resteasyspring.common;

    import com.github.johnsonmoon.resteasyspring.Main;
    import org.springframework.stereotype.Service;

    import javax.ws.rs.core.Application;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;

    /**
    * Created by johnsonmoon at 2018/5/10 15:33.
    */
    public class RestEasyApplicationInit extends Application {
    private Set<Object> set;

    public RestEasyApplicationInit() {
    Set<Object> set = new HashSet<>();
    //编写代码整合restEasy与spring
    //获取spring容器中的含有@service注解的bean对象,作为restEasy配置请求转发的服务类,将其添加至singletons中
    //这样的Service对象就能够被spring管理,能够支持spring的各项功能(IOC, AOP等)
    Map<String, Object> map = Main.getApplicationContext().getBeansWithAnnotation(Service.class);

    String info = "Found service bean: \n---------------------\n";
    for (Map.Entry<String, Object> entry : map.entrySet()) {
    set.add(entry.getValue());
    info += (entry.getKey() + "\n");
    }
    info += "---------------------\n";
    System.out.println(info);

    this.set = set;
    }

    @Override
    public Set<Object> getSingletons() {
    return set;
    }
    }

其中通过调用Main的静态方法getApplicationContext()获取到了Spring容器上下文,通过Spring上下文可以获取容器中的bean对象。

其中调用getBeansWithAnnotation(Service.class)方法说明,凡是用 @Service (org.springframework.stereotype.Service) 注解修饰的bean,都作为Service对象提供给restEasy。因此在编写Service类的时候,只要添加@Service注解即可。当然用户也可以通过其他方式来判断Service对象,只要是从Spring上下文中获取的对象,都可以获得Spring容器的支持。

  • Service类
    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
    package com.github.johnsonmoon.resteasyspring.service;

    import org.springframework.stereotype.Service;

    import javax.ws.rs.GET;
    import javax.ws.rs.Path;

    /**
    * Created by johnsonmoon at 2018/5/10 15:34.
    */
    @Service
    @Path("/service1")
    public class TestService1 {
    @GET
    @Path("/test1")
    public String test1() {
    return "service1-test1";
    }

    @GET
    @Path("/test2")
    public String test2() {
    return "service1-test2";
    }
    }

这里的Service类需要通过@Service注解进行修饰,其他的编写方式同javax.ws规范。

启动项目并测试

  • 直接通过Main#main方法启动项目,这时候我们可以看到控制台输出信息为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    五月 10, 2018 7:20:29 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@32e6e9c3: startup date [Thu May 10 19:20:29 CST 2018]; root of context hierarchy
    五月 10, 2018 7:20:29 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from URL [file:/C:/Users/johnson/johnson_workspace/tests/resteasy-spring-test/target/classes/app-context.xml]
    2018-05-10 19:20:30.224:INFO::main: Logging initialized @903ms to org.eclipse.jetty.util.log.StdErrLog
    2018-05-10 19:20:30.323:INFO:oejs.Server:main: jetty-9.4.2.v20170220
    2018-05-10 19:20:30.408:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@651aed93{/,null,AVAILABLE}
    2018-05-10 19:20:30.526:INFO:oejs.AbstractConnector:main: Started ServerConnector@528c868{HTTP/1.1,[http/1.1]}{127.0.0.1:9685}
    2018-05-10 19:20:30.527:INFO:oejs.Server:main: Started @1191ms

说明Spring容器初始化成功;Jetty服务器启动成功;

浏览器响应:

1
service1-test2

控制台输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
五月 10, 2018 7:21:45 下午 org.jboss.resteasy.spi.ResteasyDeployment processApplication
INFO: RESTEASY002225: Deploying javax.ws.rs.core.Application: class com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit
Found service bean:
---------------------
testService1
testService2
testService3
---------------------

五月 10, 2018 7:21:45 下午 org.jboss.resteasy.spi.ResteasyDeployment processApplication
INFO: RESTEASY002220: Adding singleton resource com.github.johnsonmoon.resteasyspring.service.TestService3 from Application class com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit
五月 10, 2018 7:21:45 下午 org.jboss.resteasy.spi.ResteasyDeployment processApplication
INFO: RESTEASY002220: Adding singleton resource com.github.johnsonmoon.resteasyspring.service.TestService1 from Application class com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit
五月 10, 2018 7:21:45 下午 org.jboss.resteasy.spi.ResteasyDeployment processApplication
INFO: RESTEASY002220: Adding singleton resource com.github.johnsonmoon.resteasyspring.service.TestService2 from Application class com.github.johnsonmoon.resteasyspring.common.RestEasyApplicationInit

说明在进行第一次请求的时候,restEasy实例化了RestEasyApplicationInit对象,并调用RestEasyApplicationInit#getSingletons方法,获取到项目中的Service对象,最后把请求转发给了testService1bean进行处理。

工程实例下载地址:

https://download.csdn.net/download/johnson_moon/10406483