jersey2+freemarker+spring3的集成

因爲即將開始的新項目,是一個對外網開放訪問權限的web應用。因此,公司技術管理層不容許使用struts以及spring mvc這一套。因此,咱們開始轉戰曾經用做REST API的框架jersey及其周邊工具,實現MVC。css

 

業務邏輯的bean依然採用spring進行管理。html

 

spring mvc是咱們團隊成員都很熟悉的MVC框架,jersey系列,有的知道有的不知道,其實jersey也比較的簡單。只是要有個熟悉的過程。前端

 

下面,我就撿起早期的工具,重溫一下jersey,新的jersey2進行簡單的web應用, 此處的demo採用tomcat8+jersey2+freemarker+spring的架構。java

在此,有必要說明一下,對於初學者,可能會很疑惑或者詫異,jersey的jar包,有的是com.sun的,有的是org.glassfish的,對的,這兩個是同時存在的,只是com.sun的jersey是jersey 1.x版本,而org.glassfish的版本,俗稱jersey2.x版本。nginx

 

在eclipse裏面,建立一個Dynamic web project。固然你也能夠採用maven構建項目,我所在的公司環境,maven環境是折磨人的,因此,我選擇的是dynamic web project。項目建立好後,就須要準備jersey2以及與spring結合相關的jar文件。個人項目中涉及到的全部的jar文件都在這裏,點擊可下載。個人jersey2的版本是當前最新的版本2.25.1,spring3的版本是3.2.17.RELEASE。web

 

這個demo很簡單,首先看看web.xml文件的內容:redis

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name>TGECS</display-name>    
    <welcome-file-list>  
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    
    <listener>  
          <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  
      </listener>
    <context-param>   
           <param-name>log4jConfigLocation</param-name>   
           <param-value>classpath:conf/log4j.properties</param-value>          
      </context-param>
    
    <!-- Spring configuration -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener> 
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/applicationContext.xml</param-value>
    </context-param>
    
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>  
            <param-name>encoding</param-name>  
            <param-value>utf-8</param-value>  
        </init-param> 
        <init-param>  
            <param-name>forceEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>
     
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
  
      
     <filter>
        <filter-name>JerseyServlet</filter-name>
        <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class>
        <init-param>
            <!--    這個類是資源註冊類   -->
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.tg.ecs.core.RestConfigService</param-value>
        </init-param>
        <init-param>
            <!--    這個類是用來註冊靜態資源 (css,js,images,fonts)  -->
            <param-name>jersey.config.servlet.filter.staticContentRegex</param-name>
            <param-value>(/(css|js|images|fonts)/.*)|(/favicon.ico)</param-value>
        </init-param>
        <init-param>  
            <!-- 禁止出現404的時候繼續將http請求向下一級filter發送,即一旦出錯就退出 -->
            <param-name>jersey.config.servlet.filter.forwardOn404</param-name>  
            <param-value>false</param-value>  
        </init-param>        
        <init-param> 
            <!-- 
            解決warning信息:The root of the app was not properly defined. Either use a Servlet 3.x 
            container or add an init-param jersey.config.servlet.filter.contextPath to the filter configuration. 
            Due to Servlet 2.x API, Jersey cannot determine the request base URI solely from the ServletContext. 
            The application will most likely not work. 
            --> 
            <param-name>jersey.config.servlet.filter.contextPath</param-name>  
            <param-value></param-value>  
        </init-param>
        <!-- 
        <load-on-startup>2</load-on-startup>
         -->
    </filter>

    <filter-mapping>
        <filter-name>JerseyServlet</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>  
</web-app>

 

上述的配置文件中,有兩個地方須要說明:spring

1. JerseyServlet這個filter-name,若改爲了servlet,對應的filter-mapping的地方都改爲servlet的配置,那麼,你將會遇到靜態資源文件找不到的問題。以下面圖所示,全部的html頁面中的css,js,images等靜態資源都報404.express

這個,官方的解釋,能夠參考下https://jersey.java.net/apidocs/latest/jersey/org/glassfish/jersey/servlet/ServletProperties.html#FILTER_STATIC_CONTENT_REGEXapi

public static final String FILTER_STATIC_CONTENT_REGEX
If set the regular expression is used to match an incoming servlet path URI to some web page content such as static resources or JSPs to be handled by the underlying servlet engine.

 

The property is only applicable when  Jersey servlet container is configured to run as a  Filter, otherwise this property will be ignored. If a servlet path matches this regular expression then the filter forwards the request to the next filter in the filter chain so that the underlying servlet engine can process the request otherwise Jersey will process the request. For example if you set the value to  /(image|css)/.* then you can serve up images and CSS files for your Implicit or Explicit Views while still processing your JAX-RS resources.

 

The type of this property must be a String and the value must be a valid regular expression.

 

A default value is not set.

 

The name of the configuration property is  "jersey.config.servlet.filter.staticContentRegex".
See Also:
Constant Field Values

 

2.jersey2的配置,不一樣於jersey的配置,資源的配置信息,這裏主要是RestConfigService類。具體內容看java代碼:

/**
 * @author "shihuc"
 * @date   2017年4月25日
 */
package com.tg.ecs.core;

import java.util.HashMap;
import java.util.Map;

import javax.ws.rs.container.ContainerRequestFilter;

import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.mvc.freemarker.FreemarkerMvcFeature;
import org.glassfish.jersey.server.spring.SpringComponentProvider;
import org.glassfish.jersey.server.spring.scope.RequestContextFilter;

/**
 * @author chengsh05
 *
 */
public class RestConfigService extends ResourceConfig{
    
    public RestConfigService(){
        
        //告知jersey掃描controller的路徑
        packages("com.tg.ecs");
        
        /*
         *配置view端的模板信息,本應用前端採用freemarker對頁面進行渲染
         */
        Map<String, Object> pro = new HashMap<String, Object>(1);
        //模板編碼
        pro.put(FreemarkerMvcFeature.ENCODING, "UTF-8");
        //禁止freemarker的cache,每次都從新編譯
        pro.put(FreemarkerMvcFeature.CACHE_TEMPLATES, false);
        //指定模板基礎路徑
        pro.put(FreemarkerMvcFeature.TEMPLATE_BASE_PATH, "WEB-INF/ftl");
        addProperties(pro).register(FreemarkerMvcFeature.class);
        
        // register filters
        register(RequestContextFilter.class);
        register(ContainerRequestFilter.class);         
        register(SpringComponentProvider.class);
        
        // register features
        register(JacksonFeature.class);
        register(MultiPartFeature.class);
    }
}

這個的相關jersey2的配置,看上去是否是很像spring-boot推崇的去xml化,即主要採用JavaConfig的思路。 很方便實現各類功能的配置。

 

另外, 關於jersey2的一些註解,實現servlet的攔截相關的註解,能夠本身複習一下,很少解釋。下面這兩個是比較重要的,對比spring mvc中,@Produces有點相似@ResponseBody,可是又比它強大。與spring mvc的關係,能夠慢慢體會。

@Produces
@Produces註釋用來指定將要返回給client端的數據標識類型(MIME)。@Produces能夠做爲class註釋,也能夠做爲方法註釋,方法的@Produces註釋將會覆蓋class的註釋

@Consumes
@Consumes與@Produces相反,用來指定能夠接受client發送過來的MIME類型,一樣能夠用於class或者method,也能夠指定多個MIME類型,通常用於@PUT,@POST

還有,@GET,@POST,@Path,@FormParam,@FormDataParam,@QueryParam,@Context等等。

 

下面,針對@Context,說一下,這個與Spring mvc不一樣的地方,主要是參數匹配上,在spring mvc的controller的方法上,參數前能夠不用明確指定參數類型,可是在jersey裏面,須要指定,不然會遇到錯誤。

四月 25, 2017 10:16:26 上午 org.glassfish.jersey.internal.Errors logErrors
警告: The following warnings have been detected: WARNING: A HTTP GET method, public org.glassfish.jersey.server.mvc.Viewable com.tg.ecs.test.DemoController.home(javax.servlet.http.HttpServletRequest), should not consume any entity.
WARNING: A HTTP GET method, public org.glassfish.jersey.server.mvc.Viewable com.tg.ecs.test.DemoController.login(javax.servlet.http.HttpServletRequest), should not consume any entity.

這個錯誤,能夠經過下面的代碼方式進行修改:

@GET
@Path("/home")
@Produces(MediaType.TEXT_HTML)
public Viewable home(@Context HttpServletRequest req) {
    Map<String,Object> map = new HashMap<String, Object>();  
    map.put("basePath", infoService.basePath(req));        
    return new Viewable("/home", map);
}

@GET
@Path("/login")
@Produces(MediaType.TEXT_HTML)
public Viewable login(@Context HttpServletRequest req) {
    Map<String,Object> map = new HashMap<String, Object>();  
    map.put("basePath", infoService.basePath(req));
    System.out.println(demoService.say());
    return new Viewable("/login", map);
}

即,添加上紅色的@Context註解後,上面的錯誤提示信息就解決了。

 

@Context對應的使用場合,能夠參照官方user-guide進行學習。

3.6. Use of @Context

Previous sections have introduced the use of @Context. Chapter 5 of the JAX-RS specification presents all the standard JAX-RS Java types that may be used with @Context.

When deploying a JAX-RS application using servlet then ServletConfigServletContextHttpServletRequest and HttpServletResponse are available using @Context.

 

 

再說說,jersey2究竟是如何和spring進行集成的呢?咱們的demo裏面,主要採用的是基於註解。也就是說,在jersey2裏面,只要經過@Resource或者@Autowired等方式,將spring的bean注入到jersey2的servlet中,便可。先來看看個人spring的配置文件吧:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:cache="http://www.springframework.org/schema/cache" 
       xsi:schemaLocation="
   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
   http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="configRealm" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
         <property name="locations">
            <list>
                <value>classpath:conf/internal.properties</value>
                <value>classpath:conf/jdbc.properties</value>
                <value>classpath:conf/redis.properties</value>
                <value>classpath:conf/session.properties</value>                
            </list>
        </property>
    </bean>
     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
         <property name="properties" ref="configRealm"/>
    </bean>
    
    
    <bean id="springCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehcacheManager"/>
    </bean>

    <bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:conf/ehcache.xml"/>
    </bean>

    <cache:annotation-driven cache-manager="springCacheManager"/>
     
     <context:annotation-config/>

    <context:component-scan base-package="com.tg.ecs">        
    </context:component-scan>
    
    <import resource="spring-cache.xml"/>     
    <import resource="spring-dao.xml"/>    
    <import resource="spring-redis.xml"/>    
</beans>

 

另外,看看,咱們的測試用controller的所有代碼:

/**
 * @author "shihuc"
 * @date   2017年4月24日
 */
package com.tg.ecs.test;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.server.mvc.Viewable;
import org.springframework.stereotype.Controller;

import com.tg.ecs.core.InfoService;

/**
 * @author chengsh05
 *
 */ @Controller
@Path("/demo")
public class DemoController {

    @Resource(name="dsi")    
    private DemoService demoService;

    @Resource
    private InfoService infoService;
    
    @GET
    @Path("/home")
    @Produces(MediaType.TEXT_HTML)
    public Viewable home(@Context HttpServletRequest req) {
        Map<String,Object> map = new HashMap<String, Object>();  
        map.put("basePath", infoService.basePath(req));        
        return new Viewable("/home", map); #不能寫成home
    }
    
    @GET
    @Path("/login")
    @Produces(MediaType.TEXT_HTML)
    public Viewable login(@Context HttpServletRequest req) {
        Map<String,Object> map = new HashMap<String, Object>();  
        map.put("basePath", infoService.basePath(req));
        System.out.println(demoService.say());
        return new Viewable("/login", map); #不能寫成login
    }

}

這個DemoController裏面,一個很重要的地方,就是Viewable裏面的第一個參數,指定的是ftl文件的「絕對路徑」,這個絕對路徑是相對於在RestConfigService裏面配置的Freemarker的模板路徑而言的。前面配置的freemarker的模板路徑在WEB-INF/ftl目錄下面,這裏,在servlet裏面,指定的view的文件路徑,必須是相對於模板路徑的絕對路徑。這個邏輯,和nginx的SSI中virtual的路徑指定是一個思路。

另外,上面的@Controller,是spring-context的註解,加這個註解,是爲了讓spring將bean注入到這個servlet(控制器)裏面,這樣,當/demo/home或者/demo/login的請求到來時,infoService這個bean就已經初始化了,整個邏輯就跑通了。

 

 

到此,基本的demo就算介紹完畢了,有些許spring mvc和jersey的基礎的,就很容易理解這裏的集成過程。歡迎討論和轉帖。

相關文章
相關標籤/搜索