`
whb_923
  • 浏览: 5406 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论
收藏列表
标题 标签 来源
Spring工具类详解
Spring 不但提供了一个功能全面的应用开发框架,本身还拥有众多可以在程序编写时直接使用的工具类,您不但可以在 Spring 应用中使用这些工具类,也可以在其它的应用中使用,这些工具类中的大部分是可以在脱离 Spring 框架时使用的。了解 Spring 中有哪些好用的工具类并在程序编写时适当使用,将有助于提高开发效率、增强代码质量。  
文件资源操作  
文件资源的操作是应用程序中常见的功能,如当上传一个文件后将其保存在特定目录下,从指定地址加载一个配置文件等等。我们一般使用 JDK 的 I/O 处理类完成这些操作,但对于一般的应用程序来说,JDK 的这些操作类所提供的方法过于底层,直接使用它们进行文件操作不但程序编写复杂而且容易产生错误。相比于 JDK 的 File,Spring 的 Resource 接口(资源概念的描述接口)抽象层面更高且涵盖面更广,Spring 提供了许多方便易用的资源操作工具类,它们大大降低资源操作的复杂度,同时具有更强的普适性。这些工具类不依赖于 Spring 容器,这意味着您可以在程序中象一般普通类一样使用它们。  
加载文件资源  
Spring 定义了一个 org.springframework.core.io.Resource 接口,Resource 接口是为了统一各种类型不同的资源而定义的,Spring 提供了若干 Resource 接口的实现类,这些实现类可以轻松地加载不同类型的底层资源,并提供了获取文件名、URL 地址以及资源内容的操作方法。  
访问文件资源  
假设有一个文件地位于 Web 应用的类路径下,您可以通过以下方式对这个文件资源进行访问:  
通过 FileSystemResource 以文件系统绝对路径的方式进行访问;  
通过 ClassPathResource 以类路径的方式进行访问;  
通过 ServletContextResource 以相对于 Web 应用根目录的方式进行访问。  
相比于通过 JDK 的 File 类访问文件资源的方式,Spring 的 Resource 实现类无疑提供了更加灵活的操作方式,您可以根据情况选择适合的 Resource 实现类访问资源。下面,我们分别通过 FileSystemResource 和 ClassPathResource 访问同一个文件资源:  
  
例 1. FileSourceExample  
 package com.baobaotao.io;   
 import java.io.IOException;   
 import java.io.InputStream;   
 import org.springframework.core.io.ClassPathResource;   
 import org.springframework.core.io.FileSystemResource;   
 import org.springframework.core.io.Resource;   
 public class FileSourceExample {   
    public static void main(String[] args) {   
        try {   
            String filePath =   
            "D:/masterSpring/chapter23/webapp/WEB-INF/classes/conf/file1.txt";   
            // ① 使用系统文件路径方式加载文件  
            Resource res1 = new FileSystemResource(filePath);   
            // ② 使用类路径方式加载文件  
            Resource res2 = new ClassPathResource("conf/file1.txt");   
            InputStream ins1 = res1.getInputStream();   
            InputStream ins2 = res2.getInputStream();   
            System.out.println("res1:"+res1.getFilename());   
            System.out.println("res2:"+res2.getFilename());   
        } catch (IOException e) {   
            e.printStackTrace();   
        }   
    }   
 }   
  
在获取资源后,您就可以通过 Resource 接口定义的多个方法访问文件的数据和其它的信息:如您可以通过 getFileName() 获取文件名,通过 getFile() 获取资源对应的 File 对象,通过 getInputStream() 直接获取文件的输入流。此外,您还可以通过 createRelative(String relativePath) 在资源相对地址上创建新的资源。  
在 Web 应用中,您还可以通过 ServletContextResource 以相对于 Web 应用根目录的方式访问文件资源,如下所示:  
 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>   
 <jsp:directive.page import="  
    org.springframework.web.context.support.ServletContextResource"/>   
 <jsp:directive.page import="org.springframework.core.io.Resource"/>   
 <%   
    // ① 注意文件资源地址以相对于 Web 应用根路径的方式表示  
    Resource res3 = new ServletContextResource(application,   
        "/WEB-INF/classes/conf/file1.txt");   
    out.print(res3.getFilename());   
 %>   
  
对于位于远程服务器(Web 服务器或 FTP 服务器)的文件资源,您则可以方便地通过 UrlResource 进行访问。  
为了方便访问不同类型的资源,您必须使用相应的 Resource 实现类,是否可以在不显式使用 Resource 实现类的情况下,仅根据带特殊前缀的资源地址直接加载文件资源呢? Spring 提供了一个 ResourceUtils 工具类,它支持“classpath:”和“file:”的地址前缀,它能够从指定的地址加载文件资源,请看下面的例子:  
  
例 2. ResourceUtilsExample  
 package com.baobaotao.io;   
 import java.io.File;   
 import org.springframework.util.ResourceUtils;   
 public class ResourceUtilsExample {   
    public static void main(String[] args) throws Throwable{   
        File clsFile = ResourceUtils.getFile("classpath:conf/file1.txt");   
        System.out.println(clsFile.isFile());   
  
        String httpFilePath = "file:D:/masterSpring/chapter23/src/conf/file1.txt";   
        File httpFile = ResourceUtils.getFile(httpFilePath);   
        System.out.println(httpFile.isFile());          
    }   
 }   
  
ResourceUtils 的 getFile(String resourceLocation) 方法支持带特殊前缀的资源地址,这样,我们就可以在不和 Resource 实现类打交道的情况下使用 Spring 文件资源加载的功能了。  
本地化文件资源  
本地化文件资源是一组通过本地化标识名进行特殊命名的文件,Spring 提供的 LocalizedResourceHelper 允许通过文件资源基名和本地化实体获取匹配的本地化文件资源并以 Resource 对象返回。假设在类路径的 i18n 目录下,拥有一组基名为 message 的本地化文件资源,我们通过以下实例演示获取对应中国大陆和美国的本地化文件资源:  
  
例 3. LocaleResourceTest  
 package com.baobaotao.io;   
 import java.util.Locale;   
 import org.springframework.core.io.Resource;   
 import org.springframework.core.io.support.LocalizedResourceHelper;   
 public class LocaleResourceTest {   
    public static void main(String[] args) {   
        LocalizedResourceHelper lrHalper = new LocalizedResourceHelper();   
        // ① 获取对应美国的本地化文件资源  
        Resource msg_us = lrHalper.findLocalizedResource("i18n/message", ".properties",   
        Locale.US);   
        // ② 获取对应中国大陆的本地化文件资源  
        Resource msg_cn = lrHalper.findLocalizedResource("i18n/message", ".properties",   
        Locale.CHINA);   
        System.out.println("fileName(us):"+msg_us.getFilename());   
        System.out.println("fileName(cn):"+msg_cn.getFilename());   
    }   
 }   
  
虽然 JDK 的 java.util.ResourceBundle 类也可以通过相似的方式获取本地化文件资源,但是其返回的是 ResourceBundle 类型的对象。如果您决定统一使用 Spring 的 Resource 接表征文件资源,那么 LocalizedResourceHelper 就是获取文件资源的非常适合的帮助类了。  
文件操作  
在使用各种 Resource 接口的实现类加载文件资源后,经常需要对文件资源进行读取、拷贝、转存等不同类型的操作。您可以通过 Resource 接口所提供了方法完成这些功能,不过在大多数情况下,通过 Spring 为 Resource 所配备的工具类完成文件资源的操作将更加方便。  
文件内容拷贝  
第一个我们要认识的是 FileCopyUtils,它提供了许多一步式的静态操作方法,能够将文件内容拷贝到一个目标 byte[]、String 甚至一个输出流或输出文件中。下面的实例展示了 FileCopyUtils 具体使用方法:  
  
例 4. FileCopyUtilsExample  
 package com.baobaotao.io;   
 import java.io.ByteArrayOutputStream;   
 import java.io.File;   
 import java.io.FileReader;   
 import java.io.OutputStream;   
 import org.springframework.core.io.ClassPathResource;   
 import org.springframework.core.io.Resource;   
 import org.springframework.util.FileCopyUtils;   
 public class FileCopyUtilsExample {   
    public static void main(String[] args) throws Throwable {   
        Resource res = new ClassPathResource("conf/file1.txt");   
        // ① 将文件内容拷贝到一个 byte[] 中  
        byte[] fileData = FileCopyUtils.copyToByteArray(res.getFile());   
        // ② 将文件内容拷贝到一个 String 中  
        String fileStr = FileCopyUtils.copyToString(new FileReader(res.getFile()));   
        // ③ 将文件内容拷贝到另一个目标文件  
        FileCopyUtils.copy(res.getFile(),   
        new File(res.getFile().getParent()+ "/file2.txt"));   
  
        // ④ 将文件内容拷贝到一个输出流中  
        OutputStream os = new ByteArrayOutputStream();   
        FileCopyUtils.copy(res.getInputStream(), os);   
    }   
 }   
  
往往我们都通过直接操作 InputStream 读取文件的内容,但是流操作的代码是比较底层的,代码的面向对象性并不强。通过 FileCopyUtils 读取和拷贝文件内容易于操作且相当直观。如在 ① 处,我们通过 FileCopyUtils 的 copyToByteArray(File in) 方法就可以直接将文件内容读到一个 byte[] 中;另一个可用的方法是 copyToByteArray(InputStream in),它将输入流读取到一个 byte[] 中。  
如果是文本文件,您可能希望将文件内容读取到 String 中,此时您可以使用 copyToString(Reader in) 方法,如 ② 所示。使用 FileReader 对 File 进行封装,或使用 InputStreamReader 对 InputStream 进行封装就可以了。  
FileCopyUtils 还提供了多个将文件内容拷贝到各种目标对象中的方法,这些方法包括:  
方法  说明  
static void copy(byte[] in, File out)   将 byte[] 拷贝到一个文件中  
static void copy(byte[] in, OutputStream out)   将 byte[] 拷贝到一个输出流中  
static int copy(File in, File out)  将文件拷贝到另一个文件中  
static int copy(InputStream in, OutputStream out)   将输入流拷贝到输出流中  
static int copy(Reader in, Writer out)  将 Reader 读取的内容拷贝到 Writer 指向目标输出中  
static void copy(String in, Writer out) 将字符串拷贝到一个 Writer 指向的目标中  
在实例中,我们虽然使用 Resource 加载文件资源,但 FileCopyUtils 本身和 Resource 没有任何关系,您完全可以在基于 JDK I/O API 的程序中使用这个工具类。  
属性文件操作  
我们知道可以通过 java.util.Properties 的 load(InputStream inStream) 方法从一个输入流中加载属性资源。Spring 提供的 PropertiesLoaderUtils 允许您直接通过基于类路径的文件地址加载属性资源,请看下面的例子:  
 package com.baobaotao.io;   
 import java.util.Properties;   
 import org.springframework.core.io.support.PropertiesLoaderUtils;   
 public class PropertiesLoaderUtilsExample {   
    public static void main(String[] args) throws Throwable {      
        // ① jdbc.properties 是位于类路径下的文件  
        Properties props = PropertiesLoaderUtils.loadAllProperties("jdbc.properties");   
        System.out.println(props.getProperty("jdbc.driverClassName"));   
    }   
 }   
  
一般情况下,应用程序的属性文件都放置在类路径下,所以 PropertiesLoaderUtils 比之于 Properties#load(InputStream inStream) 方法显然具有更强的实用性。此外,PropertiesLoaderUtils 还可以直接从 Resource 对象中加载属性资源:  
方法  说明  
static Properties loadProperties(Resource resource) 从 Resource 中加载属性  
static void fillProperties(Properties props, Resource resource) 将 Resource 中的属性数据添加到一个已经存在的 Properties 对象中  
特殊编码的资源  
当您使用 Resource 实现类加载文件资源时,它默认采用操作系统的编码格式。如果文件资源采用了特殊的编码格式(如 UTF-8),则在读取资源内容时必须事先通过 EncodedResource 指定编码格式,否则将会产生中文乱码的问题。  
  
例 5. EncodedResourceExample  
 package com.baobaotao.io;   
 import org.springframework.core.io.ClassPathResource;   
 import org.springframework.core.io.Resource;   
 import org.springframework.core.io.support.EncodedResource;   
 import org.springframework.util.FileCopyUtils;   
 public class EncodedResourceExample {   
        public static void main(String[] args) throws Throwable  {   
            Resource res = new ClassPathResource("conf/file1.txt");   
            // ① 指定文件资源对应的编码格式(UTF-8)  
            EncodedResource encRes = new EncodedResource(res,"UTF-8");   
            // ② 这样才能正确读取文件的内容,而不会出现乱码  
            String content  = FileCopyUtils.copyToString(encRes.getReader());   
            System.out.println(content);    
    }   
 }   
  
EncodedResource 拥有一个 getResource() 方法获取 Resource,但该方法返回的是通过构造函数传入的原 Resource 对象,所以必须通过 EncodedResource#getReader() 获取应用编码后的 Reader 对象,然后再通过该 Reader 读取文件的内容。  
回页首  
Web 相关工具类  
您几乎总是使用 Spring 框架开发 Web 的应用,Spring 为 Web 应用提供了很多有用的工具类,这些工具类可以给您的程序开发带来很多便利。在这节里,我们将逐一介绍这些工具类的使用方法。  
操作 Servlet API 的工具类  
当您在控制器、JSP 页面中想直接访问 Spring 容器时,您必须事先获取 WebApplicationContext 对象。Spring 容器在启动时将 WebApplicationContext 保存在 ServletContext 的属性列表中,通过 WebApplicationContextUtils 工具类可以方便地获取 WebApplicationContext 对象。  
WebApplicationContextUtils  
当 Web 应用集成 Spring 容器后,代表 Spring 容器的 WebApplicationContext 对象将以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为键存放在 ServletContext 属性列表中。您当然可以直接通过以下语句获取 WebApplicationContext:  
 WebApplicationContext wac = (WebApplicationContext)servletContext.   
 getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);   
  
但通过位于 org.springframework.web.context.support 包中的 WebApplicationContextUtils 工具类获取 WebApplicationContext 更方便:  
 WebApplicationContext wac =WebApplicationContextUtils.   
 getWebApplicationContext(servletContext);   
  
当 ServletContext 属性列表中不存在 WebApplicationContext 时,getWebApplicationContext() 方法不会抛出异常,它简单地返回 null。如果后续代码直接访问返回的结果将引发一个 NullPointerException 异常,而 WebApplicationContextUtils 另一个 getRequiredWebApplicationContext(ServletContext sc) 方法要求 ServletContext 属性列表中一定要包含一个有效的 WebApplicationContext 对象,否则马上抛出一个 IllegalStateException 异常。我们推荐使用后者,因为它能提前发现错误的时间,强制开发者搭建好必备的基础设施。  
WebUtils  
位于 org.springframework.web.util 包中的 WebUtils 是一个非常好用的工具类,它对很多 Servlet API 提供了易用的代理方法,降低了访问 Servlet API 的复杂度,可以将其看成是常用 Servlet API 方法的门面类。  
下面这些方法为访问 HttpServletRequest 和 HttpSession 中的对象和属性带来了方便:  
方法  说明  
Cookie getCookie(HttpServletRequest request, String name)   获取 HttpServletRequest 中特定名字的 Cookie 对象。如果您需要创建 Cookie, Spring 也提供了一个方便的 CookieGenerator 工具类;  
Object getSessionAttribute(HttpServletRequest request, String name) 获取 HttpSession 特定属性名的对象,否则您必须通过 request.getHttpSession.getAttribute(name) 完成相同的操作;  
Object getRequiredSessionAttribute(HttpServletRequest request, String name) 和上一个方法类似,只不过强制要求 HttpSession 中拥有指定的属性,否则抛出异常;  
String getSessionId(HttpServletRequest request) 获取 Session ID 的值;  
void exposeRequestAttributes(ServletRequest request, Map attributes)    将 Map 元素添加到 ServletRequest 的属性列表中,当请求被导向(forward)到下一个处理程序时,这些请求属性就可以被访问到了;  
此外,WebUtils 还提供了一些和 ServletContext 相关的方便方法:  
方法  说明  
String getRealPath(ServletContext servletContext, String path)  获取相对路径对应文件系统的真实文件路径;  
File getTempDir(ServletContext servletContext)  获取 ServletContex 对应的临时文件地址,它以 File 对象的形式返回。  
下面的片断演示了使用 WebUtils 从 HttpSession 中获取属性对象的操作:  
 protected Object formBackingObject(HttpServletRequest request) throws Exception {   
    UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request,   
        "userSession");   
    if (userSession != null) {   
        return new AccountForm(this.petStore.getAccount(   
        userSession.getAccount().getUsername()));   
    } else {   
        return new AccountForm();   
    }   
 }   
  
Spring 所提供的过滤器和监听器  
Spring 为 Web 应用提供了几个过滤器和监听器,在适合的时间使用它们,可以解决一些常见的 Web 应用问题。  
延迟加载过滤器  
Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常。  
Spring 为此专门提供了一个 OpenSessionInViewFilter 过滤器,它的主要功能是使每个请求过程绑定一个 Hibernate Session,即使最初的事务已经完成了,也可以在 Web 层进行延迟加载的操作。  
OpenSessionInViewFilter 过滤器将 Hibernate Session 绑定到请求线程中,它将自动被 Spring 的事务管理器探测到。所以 OpenSessionInViewFilter 适用于 Service 层使用 HibernateTransactionManager 或 JtaTransactionManager 进行事务管理的环境,也可以用于非事务只读的数据操作中。  
要启用这个过滤器,必须在 web.xml 中对此进行配置:  
…  
 <filter>   
    <filter-name>hibernateFilter</filter-name>   
    <filter-class>   
    org.springframework.orm.hibernate3.support.OpenSessionInViewFilter   
    </filter-class>   
 </filter>   
 <filter-mapping>   
    <filter-name>hibernateFilter</filter-name>   
    <url-pattern>*.html</url-pattern>   
 </filter-mapping>   
…  
  
上面的配置,我们假设使用 .html 的后缀作为 Web 框架的 URL 匹配模式,如果您使用 Struts 等 Web 框架,可以将其改为对应的“*.do”模型。  
中文乱码过滤器  
在您通过表单向服务器提交数据时,一个经典的问题就是中文乱码问题。虽然我们所有的 JSP 文件和页面编码格式都采用 UTF-8,但这个问题还是会出现。解决的办法很简单,我们只需要在 web.xml 中配置一个 Spring 的编码转换过滤器就可以了:  
 <web-app>   
 <!---listener 的配置 -->   
 <filter>   
    <filter-name>encodingFilter</filter-name>   
    <filter-class>   
        org.springframework.web.filter.CharacterEncodingFilter ① Spring 编辑过滤器  
    </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> ② 过滤器的匹配 URL   
        <filter-name>encodingFilter</filter-name>   
        <url-pattern>*.html</url-pattern>   
    </filter-mapping>   
  
 <!---servlet 的配置 -->   
 </web-app>   
  
这样所有以 .html 为后缀的 URL 请求的数据都会被转码为 UTF-8 编码格式,表单中文乱码的问题就可以解决了。  
请求跟踪日志过滤器  
除了以上两个常用的过滤器外,还有两个在程序调试时可能会用到的请求日志跟踪过滤器,它们会将请求的一些重要信息记录到日志中,方便程序的调试。这两个日志过滤器只有在日志级别为 DEBUG 时才会起作用:  
方法  说明  
org.springframework.web.filter.ServletContextRequestLoggingFilter   该过滤器将请求的 URI 记录到 Common 日志中(如通过 Log4J 指定的日志文件);  
org.springframework.web.filter.ServletContextRequestLoggingFilter   该过滤器将请求的 URI 记录到 ServletContext 日志中。  
以下是日志过滤器记录的请求跟踪日志的片断:  
(JspServlet.java:224) - JspEngine --> /htmlTest.jsp   
(JspServlet.java:225) - ServletPath: /htmlTest.jsp   
(JspServlet.java:226) - PathInfo: null   
(JspServlet.java:227) - RealPath: D:\masterSpring\chapter23\webapp\htmlTest.jsp   
(JspServlet.java:228) - RequestURI: /baobaotao/htmlTest.jsp   
…  
  
通过这个请求跟踪日志,程度调试者可以详细地查看到有哪些请求被调用,请求的参数是什么,请求是否正确返回等信息。虽然这两个请求跟踪日志过滤器一般在程序调试时使用,但是即使程序部署不将其从 web.xml 中移除也不会有大碍,因为只要将日志级别设置为 DEBUG 以上级别,它们就不会输出请求跟踪日志信息了。  
转存 Web 应用根目录监听器和 Log4J 监听器  
Spring 在 org.springframework.web.util 包中提供了几个特殊用途的 Servlet 监听器,正确地使用它们可以完成一些特定需求的功能。比如某些第三方工具支持通过 ${key} 的方式引用系统参数(即可以通过 System.getProperty() 获取的属性),WebAppRootListener 可以将 Web 应用根目录添加到系统参数中,对应的属性名可以通过名为“webAppRootKey”的 Servlet 上下文参数指定,默认为“webapp.root”。下面是该监听器的具体的配置:  
  
例 6. WebAppRootListener 监听器配置  
…  
 <context-param>   
    <param-name>webAppRootKey</param-name>   
    <param-value>baobaotao.root</param-value> ① Web 应用根目录以该属性名添加到系统参数中  
 </context-param>   
…  
② 负责将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中  
 <listener>   
    <listener-class>   
    org.springframework.web.util.WebAppRootListener   
    </listener-class>   
 </listener>   
…  
  
这样,您就可以在程序中通过 System.getProperty("baobaotao.root") 获取 Web 应用的根目录了。不过更常见的使用场景是在第三方工具的配置文件中通过 ${baobaotao.root} 引用 Web 应用的根目录。比如以下的 log4j.properties 配置文件就通过 ${baobaotao.root} 设置了日志文件的地址:  
 log4j.rootLogger=INFO,R   
 log4j.appender.R=org.apache.log4j.RollingFileAppender   
 log4j.appender.R.File=${baobaotao.root}/WEB-INF/logs/log4j.log ① 指定日志文件的地址  
 log4j.appender.R.MaxFileSize=100KB   
 log4j.appender.R.MaxBackupIndex=1   
 log4j.appender.R.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n   
  
另一个专门用于 Log4J 的监听器是 Log4jConfigListener。一般情况下,您必须将 Log4J 日志配置文件以 log4j.properties 为文件名并保存在类路径下。Log4jConfigListener 允许您通过 log4jConfigLocation Servlet 上下文参数显式指定 Log4J 配置文件的地址,如下所示:  
① 指定 Log4J 配置文件的地址  
 <context-param>   
    <param-name>log4jConfigLocation</param-name>   
    <param-value>/WEB-INF/log4j.properties</param-value>   
 </context-param>   
…  
② 使用该监听器初始化 Log4J 日志引擎  
 <listener>   
    <listener-class>   
    org.springframework.web.util.Log4jConfigListener   
    </listener-class>   
 </listener>   
…  
  
提示  
一些 Web 应用服务器(如 Tomcat)不会为不同的 Web 应用使用独立的系统参数,也就是说,应用服务器上所有的 Web 应用都共享同一个系统参数对象。这时,您必须通过 webAppRootKey 上下文参数为不同 Web 应用指定不同的属性名:如第一个 Web 应用使用 webapp1.root 而第二个 Web 应用使用 webapp2.root 等,这样才不会发生后者覆盖前者的问题。此外,WebAppRootListener 和 Log4jConfigListener 都只能应用在 Web 应用部署后 WAR 文件会解包的 Web 应用服务器上。一些 Web 应用服务器不会将 Web 应用的 WAR 文件解包,整个 Web 应用以一个 WAR 包的方式存在(如 Weblogic),此时因为无法指定对应文件系统的 Web 应用根目录,使用这两个监听器将会发生问题。  
Log4jConfigListener 监听器包括了 WebAppRootListener 的功能,也就是说,Log4jConfigListener 会自动完成将 Web 应用根目录以 webAppRootKey 上下文参数指定的属性名添加到系统参数中,所以当您使用 Log4jConfigListener 后,就没有必须再使用 WebAppRootListener 了。  
Introspector 缓存清除监听器  
Spring 还提供了一个名为 org.springframework.web.util.IntrospectorCleanupListener 的监听器。它主要负责处理由 JavaBean Introspector 功能而引起的缓存泄露。IntrospectorCleanupListener 监听器在 Web 应用关闭的时会负责清除 JavaBean Introspector 的缓存,在 web.xml 中注册这个监听器可以保证在 Web 应用关闭的时候释放与其相关的 ClassLoader 的缓存和类引用。如果您使用了 JavaBean Introspector 分析应用中的类,Introspector 缓存会保留这些类的引用,结果在应用关闭的时候,这些类以及 Web 应用相关的 ClassLoader 不能被垃圾回收。不幸的是,清除 Introspector 的唯一方式是刷新整个缓存,这是因为没法准确判断哪些是属于本 Web 应用的引用对象,哪些是属于其它 Web 应用的引用对象。所以删除被缓存的 Introspection 会导致将整个 JVM 所有应用的 Introspection 都删掉。需要注意的是,Spring 托管的 Bean 不需要使用这个监听器,因为 Spring 的 Introspection 所使用的缓存在分析完一个类之后会马上从 javaBean Introspector 缓存中清除掉,并将缓存保存在应用程序特定的 ClassLoader 中,所以它们一般不会导致内存资源泄露。但是一些类库和框架往往会产生这个问题。例如 Struts 和 Quartz 的 Introspector 的内存泄漏会导致整个的 Web 应用的 ClassLoader 不能进行垃圾回收。在 Web 应用关闭之后,您还会看到此应用的所有静态类引用,这个错误当然不是由这个类自身引起的。解决这个问题的方法很简单,您仅需在 web.xml 中配置 IntrospectorCleanupListener 监听器就可以了:  
 <listener>   
    <listener-class>   
    org.springframework.web.util.IntrospectorCleanupListener   
    </listener-class>   
 </listener>   
  
回页首  
小结  
本文介绍了一些常用的 Spring 工具类,其中大部分 Spring 工具类不但可以在基于 Spring 的应用中使用,还可以在其它的应用中使用。使用 JDK 的文件操作类在访问类路径相关、Web 上下文相关的文件资源时,往往显得拖泥带水、拐弯抹角,Spring 的 Resource 实现类使这些工作变得轻松了许多。  
在 Web 应用中,有时你希望直接访问 Spring 容器,获取容器中的 Bean,这时使用 WebApplicationContextUtils 工具类从 ServletContext 中获取 WebApplicationContext 是非常方便的。WebUtils 为访问 Servlet API 提供了一套便捷的代理方法,您可以通过 WebUtils 更好的访问 HttpSession 或 ServletContext 的信息。  
Spring 提供了几个 Servlet 过滤器和监听器,其中 ServletContextRequestLoggingFilter 和 ServletContextRequestLoggingFilter 可以记录请求访问的跟踪日志,你可以在程序调试时使用它们获取请求调用的详细信息。WebAppRootListener 可以将 Web 应用的根目录以特定属性名添加到系统参数中,以便第三方工具类通过 ${key} 的方式进行访问。Log4jConfigListener 允许你指定 Log4J 日志配置文件的地址,且可以在配置文件中通过 ${key} 的方式引用 Web 应用根目录,如果你需要在 Web 应用相关的目录创建日志文件,使用 Log4jConfigListener 可以很容易地达到这一目标。  
Web 应用的内存泄漏是最让开发者头疼的问题,虽然不正确的程序编写可能是这一问题的根源,也有可能是一些第三方框架的 JavaBean Introspector 缓存得不到清除而导致的,Spring 专门为解决这一问题配备了 IntrospectorCleanupListener 监听器,它只要简单在 web.xml 中声明该监听器就可以了。 
spring之BeanFactoryAware接口
要直接在自己的代码中读取spring的bean,我们除了根据常用的set外,也可以通过spring的BeanFactoryAware接口实现,只要实现setBeanFactory方法就可以,

private BeanFactory               beanFactory;

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

这样我们就可以直接拿东西用了,如
Object  object = beanFactory.getBean(beanName);

我们既然可以通过set来拿到我们要的对象,为什么还要用这个beanFactory呢,道理很简单,因为有些情况是需要动态的去获取对象的,比如说我有10个银行的处理对象,他们都继承了我的BankService对象,但是具体处理的时候要哪家银行的对象呢?这个依赖于用户的选择。你可以注入10个BankService实例,然后用if --else来搞,不过那样太坨了。每增加一家银行你都需要改代码。

通过beanFactory的话,那就一行代码搞定,只要给beanName就OK了,动点脑筋吧beanName配置的有规律点,然后根据用户的银行选择,凑出个beanName,大功告成了!

简单一句话的理解是:beanFactory让你可以不依赖注入方式,随意的读取IOC容器里面的对象,不过beanFactory本身还是要注入的,呵呵
Hibernate4之二级缓存配置
缓存:缓存是什么,解决什么问题? 
位于速度相差较大的两种硬件/软件之间的,用于协调两者数据传输速度差异的结构,均可称之为缓存Cache。缓存目的:让数据更接近于应用程序,协调速度不匹配,使访问速度更快。 

缓存的范围分为3类: 
1.事务范围(单Session即一级缓存) 
   事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围. 
2.应用范围(单SessionFactory即二级缓存) 
   应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围. 
3.集群范围(多SessionFactory) 
   在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.

二级缓存如何工作的 
Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存。所以说,二级缓存的作用范围是针对根据ID获得对象的查询。 
● 在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象根据ID放入到二级缓存中。 
● 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。 
● 删除、更新、增加数据的时候,同时更新缓存。 

与Hibernate一级缓存Session范围相对的是SessionFactory范围的二级缓存,SessionFactory也提供了相应的缓存机制。SessionFactory缓存可以依据功能和目的的不同而划分为内置缓存和外置缓存。 

SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的副本,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。 

SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的副本,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。 

Hibernate的二级缓存的实现原理与一级缓存是一样的,也是通过以ID为key的Map来实现对对象的缓存。 

二级缓存是缓存实体对象的,由于Hibernate的二级缓存是作用在SessionFactory范围内的,因而它比一级缓存的范围更广,可以被所有的Session对象所共享。 

在通常情况下会将具有以下特征的数据放入到二级缓存中: 
● 很少被修改的数据。 
● 不是很重要的数据,允许出现偶尔并发的数据。 
● 不会被并发访问的数据。 
● 常量数据。 
● 不会被第三方修改的数据 

而对于具有以下特征的数据则不适合放在二级缓存中: 
● 经常被修改的数据。 
● 财务数据,绝对不允许出现并发。 
● 与其他应用共享的数据。 

在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC),因为那样Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性。 

常见的缓存组件 
在默认情况下,Hibernate会使用EHCache作为二级缓存组件。但是,可以通过设置hibernate.cache.provider_class属性,指定其他的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。 
通过实现org.hibernate.cache.CacheProvider接口可以提供对不同二级缓存组件的支持,此接口充当缓存插件与Hibernate之间的适配器。 
组件	Provider类	类型	集群	查询缓存
Hashtable	org.hibernate.cache.HashtableCacheProvider	内存	不支持	支持
EHCache	org.hibernate.cache.EhCacheProvider	内存,硬盘	不支持	支持
OSCache	org.hibernate.cache.OSCacheProvider	内存,硬盘	支持	支持
SwarmCache	org.hibernate.cache.SwarmCacheProvider	集群	支持	不支持
JBoss TreeCache	org.hibernate.cache.TreeCacheProvider	集群	支持	支持
Hibernate已经不再提供对JCS(Java Caching System)组件的支持了。 
集群缓存的概念: 
当一台服务器上的执行了update方法修改了一条数据,那么只有这一台服务器上的二级缓存会同步于数据库,其他服务器上的二级缓存里面这条数据就没意义了。这个时候用OSCache缓存机制,只要有一台服务器上有数据修改了,马上会从配置文件中找到配置好的其他服务器IP地址,进行广播,告诉他们我这条数据修改了,你们也更新同步一下。(是不是有点像手机上微博的推送功能) 

如何在程序里使用二级缓存: 
首先在hibernate.cfg.xml开启二级缓存 
Xml代码  收藏代码
<hibernate-configuration>  
   <session-factory>  
  
      ......  
  
      <!-- 开启二级缓存 -->  
      <property name="hibernate.cache.use_second_level_cache">true</property>  
      <!-- 启动"查询缓存"如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集,必须配置此项-->  
      <property name="hibernate.cache.use_query_cache">true</property>  
      <!-- 设置二级缓存插件EHCache的Provider类-->  
      <!-- <property name="hibernate.cache.provider_class">  
         org.hibernate.cache.EhCacheProvider  
      </property> -->  
      <!-- 二级缓存区域名的前缀 -->  
      <!--<property name="hibernate.cache.region_prefix">test</property>-->  
      <!-- 高速缓存提供程序 -->  
      <property name="hibernate.cache.region.factory_class">  
         net.sf.ehcache.hibernate.EhCacheRegionFactory  
      </property>  
      <!-- Hibernate4以后都封装到org.hibernate.cache.ehcache.EhCacheRegionFactory -->  
      <!-- 指定缓存配置文件位置 -->  
      <!-- <property name="hibernate.cache.provider_configuration_file_resource_path">  
         ehcache.xml  
      </property> -->  
      <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->  
      <property name="hibernate.cache.use_structured_entries">true</property>  
  
      <!-- Hibernate将收集有助于性能调节的统计数据 -->  
      <property name="hibernate.generate_statistics">true</property>  
  
      ......  
  
   </session-factory>  
</hibernate-configuration>  

然后是ehcache配置(ehcache.xml) 
cache参数详解: 
● name:指定区域名 
● maxElementsInMemory :缓存在内存中的最大数目 
● maxElementsOnDisk:缓存在磁盘上的最大数目 
● eternal :设置是否永远不过期 
● overflowToDisk : 硬盘溢出数目 
● timeToIdleSeconds :对象处于空闲状态的最多秒数后销毁 
● timeToLiveSeconds :对象处于缓存状态的最多秒数后销毁 
● memoryStoreEvictionPolicy:缓存算法,有LRU(默认)、LFU、LFU 

关于缓存算法,常见有三种: 
● LRU:(Least Rencently Used)新来的对象替换掉使用时间算最近很少使用的对象 
● LFU:(Least Frequently Used)替换掉按命中率高低算比较低的对象 
● LFU:(First In First Out)把最早进入二级缓存的对象替换掉 


Xml代码  收藏代码
<?xml version="1.0" encoding="UTF-8"?>  
<ehcache>  
  <!--如果缓存中的对象存储超过指定的缓存数量的对象存储的磁盘地址-->  
  <diskStore path="D:/ehcache"/>  
  
  <!-- 默认cache:如果没有对应的特定区域的缓存,就使用默认缓存 -->  
  <defaultCache maxElementsInMemory="10000"  
                eternal="false"  
                timeToIdleSeconds="300"   
                timeToLiveSeconds="600"  
                overflowToDisk="false"/>  
  <!-- 指定区域cache:通过name指定,name对应到Hibernate中的区域名即可-->  
  <cache name="cn.javass.h3test.model.UserModel"  
                eternal="false"  
                maxElementsInMemory="100"  
                timeToIdleSeconds="1200"  
                timeToLiveSeconds="1200"  
                overflowToDisk="false">  
  </cache>  
  
</ehcache>  

在每个实体的hbm文件中配置cache元素,usage可以是read-only或者是read-write等4种。 
Xml代码  收藏代码
<?xml version="1.0" encoding='UTF-8'?>  
<!DOCTYPE hibernate-mapping PUBLIC  
                            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
                            "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >  
<hibernate-mapping>      
   <class>  
       <!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional-->  
       <class name="cn.java.test.model.User" table="TBL_USER">  
              <cache usage="read-write"/>  
       ......    
   </class>  
</hibernate-mapping>  

也可以用Hibernate注解配置缓存实体类 
Java代码  收藏代码
@Entity    
@Table    
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)  
public class User implements Serializable {    
    private static final long serialVersionUID = -5121812640999313420L;  
    private Integer id;  
    private String name;  
  
    ......  
}  

Query或Criteria接口查询时设置其setCacheable(true): 
默认的如果不在程序中显示的执行查询缓存声明操作,Hibernate是不会对查询的list进行缓存的。 
Java代码  收藏代码
Session s1= HibernateSessionFactory.getCurrentSession();  
s1.beginTransaction();  
System.out.println("第一次查询User");  
Query q = s1.createQuery("from User");  
q.setCacheable(true);  
q.list();  
System.out.println("放进二级缓存");  
s1.getTransaction().commit();  
  
Session s2= HibernateSessionFactory.getCurrentSession();  
s2.beginTransaction();  
System.out.println("第二次查询User,将不会发出sql");  
Query q = s2.createQuery("from User");  
q.setCacheable(true);  
q.list();  
s2.getTransaction().commit();  
  
//如果配置文件打开了generate_statistics性能调解,可以得到二级缓存命中次数等数据  
Statistics s = HibernateSessionFactoryUtil.getSessionFactory().getStatistics();  
System.out.println(s);  
System.out.println("put:"+s.getSecondLevelCachePutCount());  
System.out.println("hit:"+s.getSecondLevelCacheHitCount());  
System.out.println("miss:"+s.getSecondLevelCacheMissCount());  

如果开启了二级缓存,由于session是共享二级缓存的,只要缓存里面有要查询的对象,就不会向数据库发出sql,如果在二级缓存里没有找到需要的数据就会发出sql语句去数据库拿。 

一级缓存的管理: 
● evit(Object obj)将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象. 
● clear()将一级缓存中的所有持久化对象清除,释放其占用的内存资源 
● contains(Object obj)判断指定的对象是否存在于一级缓存中. 
● flush()刷新一级缓存区的内容,使之与数据库数据保持同步. 

二级缓存的管理: 
● evict(Class arg0, Serializable arg1)将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源. 
Java代码  收藏代码
sessionFactory.evict(Customer.class, new Integer(1));  

evict(Class arg0)将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源 
Java代码  收藏代码
sessionFactory.evict(Customer.class);  

evictCollection(String arg0)将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源. 
Java代码  收藏代码
sessionFactory.evictCollection("Customer.orders");  


设置一级缓存和二级缓存的交互权限 
Java代码  收藏代码
session = HibernateUtils.getSession();  
session.beginTransaction();  
              
//仅向二级缓存读数据,而不向二级缓存写数据,这里load的数据就不会放入二级缓存,下次再查还是会去数据库拿  
session.setCacheMode(CacheMode.GET);  
//只向二级缓存写数据,而不从二级缓存读数据  
//session.setCacheMode(CacheMode.PUT);  
//不与二级缓存交互  
//session.setCacheMode(CacheMode.IGNORE);  
//可以与二级缓存交互  
//session.setCacheMode(CacheMode.NORMAL);  
  
Student student = (Student)session.load(Student.class, 1);  
  
session.getTransaction().commit();  

设置二级缓存策略 
● READ_ONLY:实体只读缓存 
   只读缓存不允许更新,将报错Can't write to a readonly object。 
   允许新增,(从2.0以后新增直接添加到二级缓存) 
Java代码  收藏代码
//确保数据库中有标识符为1的FarmModel    
FarmModel farm = (FarmModel) session.get(FarmModel.class, 1);    
//如果修改将报错,只读缓存不允许修改    
//farm.setName("aaa");  

● NONSTRICT_READ_WRITE:实体非严格读/写缓存 
   允许更新,更新后缓存失效,需再查询一次。 
   允许新增,新增记录自动加到二级缓存中。 
   整个过程不加锁。 
● READ_WRITE:实体读/写缓存 
   允许更新,更新后自动同步到缓存。 
   允许新增,新增记录后自动同步到缓存。 
   保证read committed隔离级别及可重复读隔离级别(通过时间戳实现) 
   整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的 
   事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别。 
   读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁 
● TRANSACTIONAL:实体事务缓存 
   缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境 
● Collection集合缓存 
Java代码  收藏代码
<hibernate-mapping>  
    <class name="cn.java.test.model.UserModel" table="TBL_USER">  
        <cache usage="read-write" />  
        <set name="farms" cascade="all" inverse="true" lazy="false">  
            <cache usage="read-write"/>  
            <key column="fk_user_id"/>  
            <one-to-many class="cn.java.test.model.FarmModel"/>  
        </set>  
    </class>  
</hibernate-mapping>  


和实体并发策略有相同含义; 
但集合缓存只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符去二级缓存查找相应的实体最后组合为集合返回 
Collection的缓存和前面查询缓存的list一样,也是只保持一串id,但它不会因为这个表更新过就失效,一个collection缓存仅在这个collection里面的元素有增删时才失效。 
这样有一个问题,如果你的collection是根据某个字段排序的,当其中一个元素更新了该字段时,导致顺序改变时,collection缓存里面的顺序没有做更新 

高速缓存区域 
Hibernate在不同的高速缓存区域保存不同的类(实体)/集合,如果不配置区域默认都保存到“默认缓存”(defaultCache)中。 
●每一个区域可以设置过期策略、缓存条目大小等等。 
●对于类缓存,默认区域名是全限定类名,如cn.javass.h3test.model.UserModel。 
●对于集合而言,默认区域名是全限定类名+属性名,如cn.javass.….UserModel.farms。 
●可通过hibernate.cache.region_prefix指定特定SessionFactory的区域前缀,如前缀是h3test,则如类缓存的区域名就是h3test.cn.javass.h3test.model.UserModel。如果应用程序使用多个SessionFactory这可能是必须的。 
可通过<cache usage="read-write" region="区域名"/>自定义区域名,不过默认其实就可以了。 

一些对二级缓存的理解 
当hibernate更新数据库的时候,它怎么知道更新哪些查询缓存呢? 
hibernate在一个地方维护每个表的最后更新时间,其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。 
当通过hibernate更新的时候,hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓存所查询的表,当hibernate查询一个缓存是否存在的时候,如果缓存存在,它还要取出缓存的生成时间和这个缓存所查询的表,然后去查找这些表的最后更新时间,如果有一个表在生成时间后更新过了,那么这个缓存是无效的。 
如果找到的时间戳晚于高速缓存查询结果的时间戳,那么缓存结果将被丢弃,重新执行一次查询。 
可以看出,只要更新过一个表,那么凡是涉及到这个表的查询缓存就失效了,因此查询缓存的命中率可能会比较低。 

使用二级缓存的前置条件 
对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。 
hibernate程序对数据库有独占的写访问权,其他的进程更新了数据库,hibernate是不可能知道的。 
你操作数据库必需直接通过hibernate,如果你调用存储过程,或者自己使用jdbc更新数据库,hibernate也是不知道的。 
这个限制相当的棘手,有时候hibernate做批量更新、删除很慢,但是你却不能自己写jdbc来优化。 
当然可以用SessionFactory提供的移除缓存的方法(上面的二级缓存的管理里面有介绍) 

总结 
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。 

如果受不了Hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧! 
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧!
REDIS与MEMCACHED的区别(转)
如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:
1 Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
2 Redis支持数据的备份,即master-slave模式的数据备份。
3 Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

在Redis中,并不是所有的数据都一直存储在内存中的。这是和Memcached相比一个最大的区别(我个人是这么认为的)。

Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。当然,机器本身的内存必须要能够保持所有的key,毕竟这些数据是不会进行swap操作的。

同时由于Redis将内存中的数据swap到磁盘中的时候,提供服务的主线程和进行swap操作的子线程会共享这部分内存,所以如果更新需要swap的数据,Redis将阻塞这个操作,直到子线程完成swap操作后才可以进行修改。

可以参考使用Redis特有内存模型前后的情况对比:

VM off: 300k keys, 4096 bytes values: 1.3G used
VM on: 300k keys, 4096 bytes values: 73M used
VM off: 1 million keys, 256 bytes values: 430.12M used
VM on: 1 million keys, 256 bytes values: 160.09M used
VM on: 1 million keys, values as large as you want, still: 160.09M used 

当从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方。这里就存在一个I/O线程池的问题。在默认的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应。这种策略在客户端的数量较小,进行批量操作的时候比较合适。但是如果将Redis应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的。所以Redis运行我们设置I/O线程池的大小,对需要从swap文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。

redis、memcache、mongoDB 对比
从以下几个维度,对redis、memcache、mongoDB 做了对比,欢迎拍砖

1、性能
都比较高,性能对我们来说应该都不是瓶颈
总体来讲,TPS方面redis和memcache差不多,要大于mongodb


2、操作的便利性
memcache数据结构单一
redis丰富一些,数据操作方面,redis更好一些,较少的网络IO次数
mongodb支持丰富的数据表达,索引,最类似关系型数据库,支持的查询语言非常丰富


3、内存空间的大小和数据量的大小
redis在2.0版本后增加了自己的VM特性,突破物理内存的限制;可以对key value设置过期时间(类似memcache)
memcache可以修改最大可用内存,采用LRU算法
mongoDB适合大数据量的存储,依赖操作系统VM做内存管理,吃内存也比较厉害,服务不要和别的服务在一起

4、可用性(单点问题)

对于单点问题,
redis,依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制,因性能和效率问题,
所以单点问题比较复杂;不支持自动sharding,需要依赖程序设定一致hash 机制。
一种替代方案是,不用redis本身的复制机制,采用自己做主动复制(多份存储),或者改成增量复制的方式(需要自己实现),一致性问题和性能的权衡

Memcache本身没有数据冗余机制,也没必要;对于故障预防,采用依赖成熟的hash或者环状的算法,解决单点故障引起的抖动问题。

mongoDB支持master-slave,replicaset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切分机制。


5、可靠性(持久化)

对于数据持久化和数据恢复,

redis支持(快照、AOF):依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响

memcache不支持,通常用在做缓存,提升性能;

MongoDB从1.8版本开始采用binlog方式支持持久化的可靠性


6、数据一致性(事务支持)

Memcache 在并发场景下,用cas保证一致性

redis事务支持比较弱,只能保证事务中的每个操作连续执行

mongoDB不支持事务


7、数据分析

mongoDB内置了数据分析的功能(mapreduce),其他不支持


8、应用场景
redis:数据量较小的更性能操作和运算上

memcache:用于在动态系统中减少数据库负载,提升性能;做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding)

MongoDB:主要解决海量数据的访问效率问题
用apache的dbcp数据库缓冲池
JDBC连接池参数:
    jdbc.initialSize=0       //初始化连接
    jdbc.maxActive=30     //连接池的最大数据库连接数,设为0表示无限制
    jdbc.maxIdle=20        //没有人用连接的时候,最大闲置的连接个数,设置为0时,表示没有限制。
    jdbc.maxWait=1000    //超时等待时间以毫秒为单位
    jdbc.removeAbandoned=true //是否自动回收超时连接
    jdbc.removeAbandonedTimeout=60 //设置被遗弃的连接的超时的时间(以秒数为单位),即当一个连接被遗弃的时间超过设置的时间,则它会自动转换成可利用的连接。默认的超时时间是300秒。
    jdbc.logAbandoned = true //是否在自动回收超时连接的时候打印连接的超时错误
    jdbc.validationQuery=select 1 from dual //给出一条简单的sql语句进行验证
    jdbc.testOnBorrow=true //在取出连接时进行有效验证
二分法查找 二分法查找
import java.util.Arrays;

/**
 * 
* @项目名称:Test   
* @类名称:BinarySearch   
* @类描述:   二分法查找
* @创建人:Ansj   
* @创建时间:2011-9-13 下午02:53:47  
* @修改备注:   
* @version    
*
 */
public class BinarySearch {
	public static void main(String[] args) {
		int[] ints = {12,123,21,123,1,432,23,42,3,123,124,3,5435,66,456554,435,423,42,1} ;
		
		int find = 66 ;
		
		//先对数组排序
		Arrays.sort(ints) ;
		
		for (int i = 0; i < ints.length; i++) {
			new BinarySearch().search(ints, ints[i]) ;
		}
		
		
	}
	
	public void search(int[] ints , int find){
		search(ints,0,ints.length,find) ;
	}
	
	
	public void search(int[] ints , int start , int end , int find){
		if(ints[(start+end)/2]==find){
			System.out.println((start+end)/2);
		}else if(ints[(start+end)/2] > find){
			search(ints , start , (start+end)/2 , find) ;
		}else{
			search(ints , (start+end)/2 , end , find) ;
		}
	}
	
}
Global site tag (gtag.js) - Google Analytics