Spring最近换域名了,去转转,发现了一个有意思的项目:spring mobile。

http://projects.spring.io/spring-mobile/

这个项目有很多实用的功能,如识别访问我们网站的设备是什么类型的(手机,平板,PC),据域名或者url来切换不同访问内容,据不同的访问设备转到不同的view中。


识别访问设备类型的核心代码在org.springframework.mobile.device.LiteDeviceResolver 类中,这个类实际上根据http请求头部的User-Agent的内容来判断设备到底是哪种类型的。

可以看到,这个类实际上是很简单的。不过字符串的匹配算法可能可以优化下。




[java] ​​view plain ​​​​copy​




  1. public class LiteDeviceResolver implements DeviceResolver {  

  2.     private final List<String> mobileUserAgentPrefixes = new ArrayList<String>();  

  3.     private final List<String> mobileUserAgentKeywords = new ArrayList<String>();  

  4.     private final List<String> tabletUserAgentKeywords = new ArrayList<String>();  

  5.     private final List<String> normalUserAgentKeywords = new ArrayList<String>();  

  6.     public LiteDeviceResolver() {  
  7.         init();  
  8.     }  

  9.     public LiteDeviceResolver(List<String> normalUserAgentKeywords) {  
  10.         init();  
  11.         this.normalUserAgentKeywords.addAll(normalUserAgentKeywords);  
  12.     }  

  13.     public Device resolveDevice(HttpServletRequest request) {  
  14.         String userAgent = request.getHeader("User-Agent");  
  15.         // UserAgent keyword detection of Normal devices  
  16.         if (userAgent != null) {  
  17.             userAgent = userAgent.toLowerCase();  
  18.             for (String keyword : normalUserAgentKeywords) {  
  19.                 if (userAgent.contains(keyword)) {  
  20.                     return resolveFallback(request);  
  21.                 }  
  22.             }  
  23.         }  
  24.         // UAProf detection  
  25.         if (request.getHeader("x-wap-profile") != null || request.getHeader("Profile") != null) {  
  26.             return LiteDevice.MOBILE_INSTANCE;  
  27.         }  
  28.         // User-Agent prefix detection  
  29.         if (userAgent != null && userAgent.length() >= 4) {  
  30.             String prefix = userAgent.substring(04).toLowerCase();  
  31.             if (mobileUserAgentPrefixes.contains(prefix)) {  
  32.                 return LiteDevice.MOBILE_INSTANCE;  
  33.             }  
  34.         }  
  35.         // Accept-header based detection  
  36.         String accept = request.getHeader("Accept");  
  37.         if (accept != null && accept.contains("wap")) {  
  38.             return LiteDevice.MOBILE_INSTANCE;  
  39.         }  
  40.         // UserAgent keyword detection for Mobile and Tablet devices  
  41.         if (userAgent != null) {  
  42.             userAgent = userAgent.toLowerCase();  
  43.             // Android special case   
  44.             if (userAgent.contains("android") && !userAgent.contains("mobile")) {  
  45.                 return LiteDevice.TABLET_INSTANCE;  
  46.             }  
  47.             // Kindle Fire special case   
  48.             if (userAgent.contains("silk") && !userAgent.contains("mobile")) {  
  49.                 return LiteDevice.TABLET_INSTANCE;  
  50.             }  
  51.             for (String keyword : tabletUserAgentKeywords) {  
  52.                 if (userAgent.contains(keyword)) {  
  53.                     return LiteDevice.TABLET_INSTANCE;  
  54.                 }  
  55.             }  
  56.             for (String keyword : mobileUserAgentKeywords) {  
  57.                 if (userAgent.contains(keyword)) {  
  58.                     return LiteDevice.MOBILE_INSTANCE;  
  59.                 }  
  60.             }  
  61.         }  
  62.         // OperaMini special case  
  63.         @SuppressWarnings("rawtypes")  
  64.         Enumeration headers = request.getHeaderNames();  
  65.         while (headers.hasMoreElements()) {  
  66.             String header = (String) headers.nextElement();  
  67.             if (header.contains("OperaMini")) {  
  68.                 return LiteDevice.MOBILE_INSTANCE;  
  69.             }  
  70.         }  
  71.         return resolveFallback(request);  
  72.     }  

  73.     // subclassing hooks  

  74.     /** 
  75.      * List of user agent prefixes that identify mobile devices. 
  76.      * Used primarily to match by operator or handset manufacturer. 
  77.      */  
  78.     protected List<String> getMobileUserAgentPrefixes() {  
  79.         return mobileUserAgentPrefixes;  
  80.     }  

  81.     /** 
  82.      * List of user agent keywords that identify mobile devices. 
  83.      * Used primarily to match by mobile platform or operating system. 
  84.      */  
  85.     protected List<String> getMobileUserAgentKeywords() {  
  86.         return mobileUserAgentKeywords;  
  87.     }  

  88.     /** 
  89.      * List of user agent keywords that identify tablet devices. 
  90.      * Used primarily to match by tablet platform or operating system. 
  91.      */  
  92.     protected List<String> getTabletUserAgentKeywords() {  
  93.         return tabletUserAgentKeywords;  
  94.     }  

  95.     /** 
  96.      * List of user agent keywords that identify normal devices. 
  97.      * Any items in this list take precedence over the mobile and 
  98.      * tablet user agent keywords, effectively overriding those.  
  99.      */  
  100.     protected List<String> getNormalUserAgentKeywords() {  
  101.         return normalUserAgentKeywords;  
  102.     }  

  103.     /** 
  104.      * Initialize this device resolver implementation. 
  105.      * Registers the known set of device signature strings. 
  106.      * Subclasses may override to register additional strings. 
  107.      */  
  108.     protected void init() {  
  109.         getMobileUserAgentPrefixes().addAll(Arrays.asList(KNOWN_MOBILE_USER_AGENT_PREFIXES));  
  110.         getMobileUserAgentKeywords().addAll(Arrays.asList(KNOWN_MOBILE_USER_AGENT_KEYWORDS));  
  111.         getTabletUserAgentKeywords().addAll(Arrays.asList(KNOWN_TABLET_USER_AGENT_KEYWORDS));  
  112.     }  

  113.     /** 
  114.      * Fallback called if no mobile device is matched by this resolver. 
  115.      * The default implementation of this method returns a "normal" {@link Device} that is neither mobile or a tablet. 
  116.      * Subclasses may override to try additional mobile or tablet device matching before falling back to a "normal" device. 
  117.      */  
  118.     protected Device resolveFallback(HttpServletRequest request) {  
  119.         return LiteDevice.NORMAL_INSTANCE;  
  120.     }  

  121.     // internal helpers  

  122.     private static final String[] KNOWN_MOBILE_USER_AGENT_PREFIXES = new String[] { "w3c ""w3c-""acs-""alav",  
  123.             "alca""amoi""audi""avan""benq""bird""blac""blaz""brew""cell""cldc""cmd-""dang",  
  124.             "doco""eric""hipt""htc_""inno""ipaq""ipod""jigs""kddi""keji""leno""lg-c""lg-d",  
  125.             "lg-g""lge-""lg/u""maui""maxo""midp""mits""mmef""mobi""mot-""moto""mwbp""nec-",  
  126.             "newt""noki""palm""pana""pant""phil""play""port""prox""qwap""sage""sams""sany",  
  127.             "sch-""sec-""send""seri""sgh-""shar""sie-""siem""smal""smar""sony""sph-""symb",  
  128.             "t-mo""teli""tim-""tosh""tsm-""upg1""upsi""vk-v""voda""wap-""wapa""wapi""wapp",  
  129.             "wapr""webc""winw""winw""xda ""xda-" };  

  130.     private static final String[] KNOWN_MOBILE_USER_AGENT_KEYWORDS = new String[] { "blackberry""webos""ipod",  
  131.             "lge vx""midp""maemo""mmp""mobile""netfront""hiptop""nintendo DS""novarra""openweb",  
  132.             "opera mobi""opera mini""palm""psp""phone""smartphone""symbian""up.browser""up.link",  
  133.             "wap""windows ce" };  

  134.     private static final String[] KNOWN_TABLET_USER_AGENT_KEYWORDS = new String[] { "ipad""playbook""hp-tablet",  
  135.             "kindle" };  

  136. }