Chris's Blog

Keep Walking......

基于Spring Mobile开发移动Web应用

最近在开发FX Trading的Dashboard的时候,发现用到的一些Bootstrap的样式在手机上的显示效果很不好,而且每笔trade包含的信息也比较多,用手机看实在是不方便,于是就萌生了做一版针对mobile的想法。

Spring Mobile还在1.0的beta版的时候就有了解过,但当时的版本还比较简陋,有些功能都不够完整,其实现在也都比较简单,总共也没多少代码,不过也足以解决mobile相关的一些问题了。Spring Mobile是基于Spring MVC的,如果你的项目本身使用的就是Spring MVC,那基本没多少额外的工作量。我们的项目就是基于Spring MVC的,现在也只是想增加针对mobile的展示,所以controller都可以重用,只需做一些针对mobile的页面就可以了。

Spring Mobile实现的功能主要包括以下四块,也都是配置一下就可以使用了,所以你只需要专注于页面的展示。

Device Resolution

访问设备的识别,默认采用LiteDeviceResolver作为实现,通过分析HTTP Header来进行识别,如User-Agent,Accept等。

若是一些特殊的User-Agent被识别成了mobile,可以将这些特殊的User-Agent的关键字定义为一个List,作为LiteDeviceResolver的构造函数的参数,这些User-Agent将会被作为PC来处理。

每个request被处理之前,将会先识别设备类型Device,然后保存在request的currentDevice参数中。

定义DeviceResolverHandlerInterceptor在Spring MVC的配置中,即可实现设备的自动识别。

<interceptors>
      <!-- resolve the device that originated the web request -->
      <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />
</interceptors> 

也可以通过在web.xml中定义DeviceResolverRequestFilter,来实现设备的自动识别。

<filter>
     <filter-name>deviceResolverRequestFilter</filter-name>
     <filter-class>org.springframework.mobile.device.DeviceResolverRequestFilter</filter-class>
</filter> 

若要在代码中获取当前设备类型,可以通过以下三种方式:

1、直接从HttpRequest中获取currentDevice参数。

2、使用DeviceUtils,但需要一个ServletRequest或Spring WebRequest作为参数。

 Device currentDevice = DeviceUtils.getCurrentDevice(servletRequest); 

3、将Device作为Controller中方法的参数,需要添加以下配置。

 <annotation-driven>
      <argument-resolvers>
           <bean class="org.springframework.mobile.device.DeviceWebArgumentResolver" />
      </argument-resolvers>
 </annotation-driven> 

Site Preference

用于管理web应用采用哪种模式展现给用户。

默认采用StandardSitePreferenceHandler作为实现,首先从request中读取site_preference参数,并将读取到的设备类型保存在cookie中;若没读取到,则尝试从cookie中获取CookieSitePreferenceRepository.SITE_PREFERENCE,若仍然没有获取到,则采用DeviceResolver得到的设备类型;最终采用的设备类型将会保存在request的currentSitePreference参数中。

site_preference支持:normal,mobile和tablet。不区分大小写。 如:

<a href="${currentUrl}?site_preference=mobile">Mobile</a>

定义SitePreferenceHandlerInterceptor在Spring MVC的配置中,即可实现Site Preference管理。

<interceptors>
     <!-- manage the user's site preference (declare after DeviceResolverHandlerInterceptor) -->
     <bean class="org.springframework.mobile.device.site.SitePreferenceHandlerInterceptor" />
</interceptors>

若要在代码中获取当前的Site Preference,可以通过一下三种方式:

1、直接从HttpRequest中获取currentSitePreference参数。

2、使用SitePreferenceUtils,但需要一个ServletRequest或Spring WebRequest作为参数。

  SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(servletRequest);

3、将SitePreference作为Controller中方法的参数,需要添加以下配置。

  <annotation-driven>
       <argument-resolvers>
             <bean class="org.springframework.mobile.device.site.SitePreferenceWebArgumentResolver" />
       </argument-resolvers>
  </annotation-driven>

Site Switch

Site Switch用于移动版和桌面版采用不同domain的应用,如:m.google.comgoogle.com

Site Switch默认采用StandardSitePreferenceHandler作为Site Preference的实现,因此不需要再配置SitePreferenceHandlerInterceptor。

mDot

用于重定向mobile用户到m.${serverName}

定义SiteSwitcherHandlerInterceptor在Spring MVC的配置中:

  <interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "m.myapp.com" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="mDot">
              <constructor-arg index="0" type="java.lang.String" value="myapp.com"/>
              <constructor-arg index="1" type="java.lang.Boolean" value="true"/>
        </bean>
 </interceptors>

第一个参数用于指定serverName,必须的参数;第二个为可选参数,用于指定是否将tablet作为mobile来处理。

dotMobi

用于重定向mobile用户到${serverName - lastDomain}.mobi

定义SiteSwitcherHandlerInterceptor在Spring MVC的配置中:

  <interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "myapp.mobi" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="dotMobi">
              <constructor-arg index="0" type="java.lang.String" value="myapp.com"/>
              <constructor-arg index="1" type="java.lang.Boolean" value="true"/>
        </bean>
 </interceptors>

第一个参数用于指定serverName,必须的参数;第二个为可选参数,用于指定是否将tablet作为mobile来处理。

Standard

用于重定向mobile和tablet用户到指定的domain。

定义SiteSwitcherHandlerInterceptor在Spring MVC的配置中:

  <interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "mobile.app.com" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="standard">
              <constructor-arg value="app.com"/>
              <constructor-arg value="mobile.app.com"/>
              <constructor-arg value="tablet.app.com"/>
              <constructor-arg value=".app.com"/>
        </bean>
 </interceptors>

第一个参数用于指定serverName;第二个参数用于指定mobile的domain,第三个参数用于指定tablet的domain,第四个参数用于指定cookie的domain。

urlPath

用于重定向mobile用户到相同domain下的不同的路径 ${serverName}/${mobilePath}

定义SiteSwitcherHandlerInterceptor在Spring MVC的配置中:

  <interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "myapp.com/m" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="urlPath">
              <constructor-arg index="0" type="java.lang.String" value="/m" />
        </bean>
 </interceptors>

参数用于指定mobile的路径。

若应用不在domain的根路径下,可指定root path。

<interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "myapp.com/showcase/m" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="urlPath">
              <constructor-arg index="0" type="java.lang.String" value="/m" />
              <constructor-arg index="1" type="java.lang.String" value="/showcase" />
        </bean>
 </interceptors>

第一个参数用于指定mobile的路径,第二个参数用于指定应用的root path。

以上两种配置都会将tablet作为PC来处理,如需指定tablet的路径,可通过如下的配置。

 <interceptors>
        <!-- resolve the device that originated the web request -->
        <bean class="org.springframework.mobile.device.DeviceResolverHandlerInterceptor" />


        <!-- redirects mobile users to "myapp.com/showcase/m" (declare after DeviceResolverHandlerInterceptor) -->
        <bean class=“org.springframework.mobile.device.switcher.SiteSwitcherHandlerInterceptor" factory-method="urlPath">
              <constructor-arg index="0" type="java.lang.String" value="/m" />
              <constructor-arg index="1" type="java.lang.String" value="/t" />
              <constructor-arg index="2" type="java.lang.String" value="/showcase" />
        </bean>
 </interceptors>

为了让mobile和tablet的路径能够正常工作,还需要在web.xml中配置相应的url给DispatcherServlet。

  <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
        <url-pattern>/m/*</url-pattern>
        <url-pattern>/t/*</url-pattern>
  </servlet-mapping>

Device Aware View Management

根据设备类型来决定使用的view,避免了在Controller中判断设备类型来返回特定view的繁琐。LiteDeviceDelegatingViewResolver可以在不同的路径下查找相同名字的view,也可以在相同路径下查找不同后缀的view。

在Spring MVC的配置中添加如下定义:

   <bean class="org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver">
         <constructor-arg>
               <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                     <property name="prefix" value="/WEB-INF/views/" />
                     <property name="suffix" value=".jsp" />
              </bean>
         </constructor-arg>
         <property name="mobilePrefix" value="mobile/" />
         <property name="tabletPrefix" value="tablet/" />
         <property name="enableFallback" value="true" />
   </bean>

enableFallback用于若找不到调整路径后的view,则使用原始的view。这是一个很好的特性,但是在Spring MVC并没有真正实现它,所以这个参数是不起作用的。

可参见spring的issue list:

Spring Mobile / MOBILE-76

Spring Framework / SPR-7727

Comments