(1)在开发应用程序的时候,经常会遇到支持多语言的需求,这种支持多语言的功能称之为国际化,英文是internationalization。还有针对特定地区的本地化功能,应为是localization,缩写为L10n,本地化是根据地区调整类似姓名、日期的显示等。也有把上面两者合称为全球化,应为是glabalization,缩写为g11n。在Java中,支持多语言和本地化是通过MessageFormat配合Locale实现的。对于Web应用程序,要实现国际化功能,主要是渲染View的时候,要把各种语言的资源文件提出来,这样,不同的用户访问同一个页面的时候,显示的语言就是不同的。
(2)实现国际化的第一步是获取到用户的Locale。在Web应用程序中,HTTP规范规定了浏览器会在请求中携带Accept-Language头,用来指示用户浏览器设定的语言顺序,如:
上述HTTP请求头表示优先选择简体中文,其次选择中文,最后选择英文。q表示权重,解析后我们可获得一个根据优先级排序的语言列表,把它转化为Java的Locale,即获得了用户的Locale(场所)。大多数框架只返回权重最高的Locale。Spring MVC通过LocaleResolver来自动从HttpServletRequest中获取Locale。有多种LocaleResolver的实现类,其中最常用的是CookieLocaleResolver。
CookieLocaleResolver从HttpServletRequest中获取Locale时,首先根据一个特定的Cookie判断是否制定了Locale,如果没有,就从HTTP请求头获取,如果还没有,就返回默认的Locale。当用户第一次访问网站时,CookieLocaleResolver只能从HTTP头获取Locale,即使用浏览器的默认语言。通常网站也允许用户自己选择语言,此时,CookieLocaleResolver就会把用户选择的语言放入Cookie中,下一次访问时,就会返回用户上次选择的语言而不是浏览器默认语言。
(3)第二步是吧写死在模板里面的字符串以资源文件的方式存储在外部。对于多语言,主文件名如果命名为messages,那么资源文件必须按如下方式明明并放入classpath中:
默认语言,文件名必须为messages.properties;
简体中文,Locale是zh_CN,文件名必须为messages_zh_CN.properties
日文,Locale是ja_JP,文件名必须为messages_ja_JP.properties
每个资源文件都有相同的key,例如,默认语言是英文,文件messages.properties内容如下:
(4)第三步是创建一个Spring提供的MessageSource实例,它自动读取所有的.peoperties文件,并提供一个统一接口来实现翻译:
String text = messageSource.getMessage(“signin”, null, locale)
其中,signin是我们在.properties文件中定义的key,第二个参数是Object[]数组作为格式化时传入的参数,最后一个参数就是获取的用户Locale实例。
创建MessageSource如下:
注意到ResourceBundleMessageSource会自动根据主文件名自动把所有相关语言的资源文件都读进来。
再注意到Spring容器会创建不只一个MessageSource实例,我们自己创建的这个MessageSource是专门给页面国际化使用的,因此命名为i18n,不会与其它MessageSource实例冲突。
(1)在Servlet模型中,每个请求都是由某个线程处理,然后,将响应写入IO流,发送给客户端。从开始处理请求,到写入响应完成,都是在同一个线程中处理的。实现Servlet容器的时候,只要每处理一个请求,就创建一个新线程处理它,就能保证正确实现了Servlet线程模型。在实际产品中,例如Tomcat,总是通过线程池来处理请求,它仍然符合一个请求从头到尾都由一个线程处理。
(2)这种线程模型非常重要,因为Spring的JDBC事务是基于ThreadLocal实现的,如果在处理过程中,一会由线程A处理,一会又由线程B处理,那事务就乱套了。此外,很多安全认证,也是基于ThreadLocal实现的,可以保证在处理请求的过程中,各个线程互不影响。但是,如果一个请求处理的时间较长,例如几秒钟甚至更长,那么,这种基于线程池的同步模型很快就会把所有线程耗尽,导致服务器无法响应新的请求。如果把长时间处理的请求改为异步处理,那么线程池的利用率就会大大提高。Servlet从3.0规范开始添加了异步支持,允许对一个请求进行异步处理。
(3)使用Filter,当我么您使用async模式处理请求时,原有的Filter也可以工作,但我们必须在web.xml中添加
(4)使用async异步处理响应时,要时刻牢记,在另一个异步线程中的事务和Controller方法中执行的事务不是同一个事务,在Controller中绑定的ThreadLocal信息也无法在异步线程中获取。此外,Servlet3.0规范添加的异步支持是针对同步模型打了一个补丁,虽然可以异步处理请求,但高并发异步请求时,它的处理效率并不高,因为这种异步模型并没有用到真正的原生异步。Java标准库提供了封装操作系统的异步IO包java.nio,是真正的多路复用IO模型,可以用少量线程支持大量并发。使用NIO编程复杂度比同步IO高很多,因为我们很少直接使用NIO。相反,大部分需要高性能异步IO的应用程序会选择Netty这样的框架,它基于NIO提供了更易于使用的API,方便开发异步应用程序。
(1)WebSocket是一种基于HTTP的长链接技术。传统的HTTP协议是一种请求-响应模型,如果浏览器不发送请求,那么服务器无法主动给浏览器推送数据。如果需要定期给浏览器推送数据,例如股票行情,或者不定期给浏览器推送数据,例如在线聊天,基于HTTP协议实现这类需求,只能依靠浏览器的JavaScrept定时轮询,效率很低且实时性不高。
(2)因为HTTP本身是基于TCP连接的,所以,WebSocket在HTTP协议的基础上做了一个简单的升级,即建立TCP连接后,浏览器发送请求时,附带几个头:
这就表示客户端希望升级连接,变成长连接的WebSocket,服务器返回升级成功的响应。收到成功响应后表示WebSocket握手成功,这样,代表WebSocket的这个TCP连接将不会被服务器关闭,而是一直保持,服务器可能随时向浏览器推送信息,浏览器也可以随时向服务器推送信息。双方推送的消息既可以是文本消息,也可以是二进制消息,一般来说,绝大部分应用程序会推送基于JSON的文本信息。
(3)现代浏览器都已经支持WebSocket协议,服务器则需要底层框架支持。Java的Servlet规范从3.1开始支持WebSocket,所以必须选择支持Servlet3.1或者更高规范的Servlet容器,才能支持WebSocket。
(4)我们以实际代码演示如何在Spring MVC中实现对WebSocket的支持,首先,我们需要在pom.xml中加入以下依赖:
第一项是嵌入式Timcat支持WebSocket的组件,第二项是Spring封装的支持WebSocket的接口。接下来,我们需要在AppConfig中加入Spring Web对WebSocket的配置,此处我们需要创建一个WebSocketConfigurer实例:
此实例在内部通过WebSocketHandlerRegistry注册能处理WebSocket的WebSocketHandler,以及可选的WebSocket拦截器HandshakeInterceptor。
(1)和处理普通HTTP请求不同,没法用一个方法处理URL。Spring提供了TextWebSocketHandler和BinaryWebSocketHandler
分别处理文本消息和二进制消息,这里我们选择文本信息作为聊天室的协议,因此,ChatHandler需要继承自TextWebSocketHandler:
当浏览器请求一个WebSocket连接后,如果成功建立连接,Spring会自动调用afterConnectionEstablished()方法,任何原因导致WebSocker连接中断时,Spring会自动调用afterConnectionClosed方法,因此,覆写这两个方法即可以处理连接成功和结束后的业务逻辑:
(2)每个WebSocket会话以WebSocketSession表示,且已分配唯一ID。和WebSocket相关的数据,例如用户名称等,均可放入关联的GetAttrbutes()中。用实例变量clients持有当前所有的WebSocketSession是为了广播,即向所有用户同时推送同一消息时,可以这么写:
我们发送的消息是序列化后的JSON,可以用ChatMessage表示:
每收到一个用户的消息后,我们就需要广播给所有用户:
如果需要推送给指定的给定的几个用户,那就需要在clients中根据条件查找出某些WebSocketSession,然后发送消息。
在完成了服务器端的开发后,我么你还需要在页面编写一点JavaScript逻辑:
用户可以再连接成功后任何时候给服务器发送消息:
最后,联调浏览器和服务器端,如果一切无误,可以开多个不同的浏览器测试WebSocket的推送和广播:
Servlet的线程模型并不适合大规模的长链接。