Java Web 學習筆記之十五:Spring整合RestEasy

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:

ApplicationContext appContext = Main.getApplicationContext();//獲取Main啓動類中的spring容器上下文

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

這樣獲取spring容器中的bean是可行的

2.Service對象與Spring容器隔離,我們就不能通過 @Autowired 註解將Service對象中的屬性進行依賴注入了

private UserDao userDao;

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

這樣是不可行的

3.Service對象與Spring容器隔離,我們就無法使用Spring的AOP對Service進行切面編程,完成諸如事務日誌的記錄等功能了。

解決方案分析

reastEasy提供幾種方式配置Service應用對象。

  • (1)通過web.xml配置文件的標籤配置resteasy.resources:
    web.xml配置:
<?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>
  • (2)通過編碼添加Service應用對象,ResteasyDeployment會在接收到請求後將其添加到restEasy中:

web.xml配置:

<?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:

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服務工程:

工程目錄結構:

| 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
<?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:
<?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:
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配置:

<?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:
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類
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方法啓動項目,這時候我們可以看到控制檯輸出信息爲:
五月 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服務器啓動成功;

瀏覽器響應:

service1-test2

控制檯輸出:

五月 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對象,最後把請求轉發給了**testService1**bean進行處理。

工程實例下載地址:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章