昊天

Spring提供了对quartz的集成,这样在j2ee的应用中,可以很方便的实现我们的任务计划,比如:你可以设定每天半夜的时候,来实现备份数据库,记录日志,因为这个时候web的压力相对比较小。也可以用来定时的发EMAIL等。

1、Spring中集成quartz 首先需要在web.xml 中配置个quartz的监听器。这样,随着WEB程序的启动,会自动启动quartz的调度配置。

Web.xml 中加入 监听器:

<listener> 
<listener-class>com.yangxinyong.quartz.listener.QuartzServiceLoader</listener-class>  
 </listener>

2、QuartzServiceLoader 实现了 javax.servlet.ServletContextListener  ,里面有两个方法。

QuartzServiceLoader类:

Java代码  
  1. public class QuartzServiceLoader implements ServletContextListener {     
  2.     public void contextDestroyed(ServletContextEvent arg0) {             
  3.         try {     
  4.                 //监听器关闭时关闭Sheduler     
  5.          JobListener.stop();     
  6.         } catch (Exception ex) {     
  7.             System.out.println(ex.getMessage());     
  8.         }     
  9.          
  10.     }     
  11.     
  12.     public void contextInitialized(ServletContextEvent arg0) {     
  13.         try {    //监听器启动时启动Sheduler      
  14.          JobListener.run();     
  15.         } catch (Exception ex) {     
  16.             System.out.println(ex.getMessage());     
  17.         }     
  18.     }     
  19.     
  20. }    

 

3、此时,我们要新建个 JobListener 类 ,来读取配置文件applicationContext-quartz.xml,启动quartz的计划任务配置,以及关闭监听时的关闭quartz的操作。

Java代码  
  1. package com.yangxinyong.quartz.listener;   
  2.   
  3. import org.quartz.Scheduler;   
  4. import org.quartz.impl.StdSchedulerFactory;   
  5. import org.springframework.beans.factory.xml.XmlBeanFactory;   
  6. import org.springframework.core.io.ClassPathResource;   
  7. import org.springframework.scheduling.quartz.CronTriggerBean;   
  8. import org.springframework.scheduling.quartz.JobDetailBean;   
  9.   
  10.   
  11. /**  
  12.  * @author Seyo816@gmail.com  
  13.  */  
  14. public class JobListener   
  15. {   
  16.     
  17.  public static void run() throws Exception {     
  18.   ClassPathResource res = new ClassPathResource( "applicationContext-quartz.xml" );   
  19.    XmlBeanFactory factory = new XmlBeanFactory( res );   
  20.    JobDetailBean job = ( JobDetailBean ) factory   
  21.      .getBean( "Job" );   
  22.    CronTriggerBean trigger = ( CronTriggerBean ) factory   
  23.      .getBean( "cronTrigger" );   
  24.    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler( );   
  25.    scheduler.start( );   
  26.    scheduler.scheduleJob( job, trigger );   
  27.  }     
  28.   
  29.   
  30.  public static void stop() throws Exception {     
  31.        
  32.  }     
  33. }  

 

其中applicationContext-quartz.xml 放在classpath路径下,应用程序才可以找到该配置文件。配置文件定义了一个job bean 以及 触发器cronTrigger bean ,最后 通过 SchedulerFactoryBean 添加 添加触发器 。

applicationContext-quartz.xml  代码:

 

Xml代码  
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"    
  3. "http://www.springframework.org/dtd/spring-beans.dtd">  
  4. <beans>  
  5.   
  6.  <bean name="Job" class="org.springframework.scheduling.quartz.JobDetailBean">  
  7.     
  8.   <property name="jobClass">  
  9.    <value>com.yangxinyong.quartz.listener.Job</value>  
  10.   </property>  
  11.     
  12.   <property name="jobDataAsMap">  
  13.    <map>  
  14.     <entry key="email"><value>seyo816@gmail.com</value></entry>  
  15.    </map>  
  16.   </property>  
  17.     
  18.     
  19.  </bean>  
  20.     
  21.  <bean id="cronTrigger" class="com.yangxinyong.quartz.listener.InitializingCronTrigger">  
  22.   <property name="jobDetail">  
  23.    <ref bean="Job"/>  
  24.   </property>  
  25.   <property name="cronExpression">  
  26.    <value>0 50 16 * * ?</value>  
  27.   </property>  
  28.  </bean>  
  29.     
  30.     
  31.  <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  
  32.   
  33.   <!-- 添加触发器 -->  
  34.   <property name="triggers">  
  35.    <list>  
  36.     <ref local="cronTrigger"/>  
  37.    </list>  
  38.   </property>  
  39.  </bean>  
  40.     
  41. </beans>  

 

3、job bean  对应的类:com.yangxinyong.quartz.listener.Job 是spring的 QuartzJobBean接口的实现,用来定义 执行的任务。

Java代码  
  1. package com.yangxinyong.quartz.listener;   
  2.   
  3. import java.util.Date;   
  4.   
  5. import org.quartz.JobDetail;   
  6. import org.quartz.JobExecutionContext;   
  7. import org.quartz.JobExecutionException;   
  8. import org.springframework.scheduling.quartz.QuartzJobBean;   
  9.   
  10. /**  
  11.  * @author Seyo816@gmail.com  
  12.  */  
  13. public class Job extends QuartzJobBean {   
  14.  private String email;   
  15.   
  16.  protected void executeInternal(JobExecutionContext context)   
  17.    throws JobExecutionException {   
  18.   JobDetail job= context.getJobDetail();   
  19.   System.out.println(new Date()+":"+job.getName()+"["+email+"]");   
  20.  }   
  21.   
  22.   
  23.  public String getEmail() {   
  24.   return email;   
  25.  }   
  26.   
  27.  public void setEmail(String email) {   
  28.   this.email = email;   
  29.  }   
  30.   
  31. }  

 

该job只是简单的 在控制台中输出 一串字符串。

最后你可以 部署该 web应用,根据触发器中的时间配置,将实现 每天的16:50分的时候 执行操作。我们可以看到下面的输出字符串。

Tue Nov 18 16:50:00 CST 2008:Job[seyo816@gmail.com]

posted @ 2011-05-24 11:50 昊天 阅读(3708) | 评论 (1)编辑 收藏

Lucence是一个很容易上手,java语言的全文索引检索工具包。

    Lucene的作者是资深的全文索引/检索专家,最开始发布在他本人的主页上,200110月贡献给APACHE,成为APACHE基金jakarta的一个子项目。

    目前,lucene广泛用于全文索引/检索的项目中。 

    Lucene 原理

lucene的检索算法属于索引检索,即用空间来换取时间,对需要检索的文件、字符流进行全文索引,在检索的时候对索引进行快速的检索,得到检索位置,这个位置记录检索词出现的文件路径或者某个关键词。

    在使用数据库的项目中,不使用数据库进行检索的原因主要是:数据库在非精确查询的时候使用查询语言“like %keyword%”,对数据库进行查询是对所有记录遍历,并对字段进行“%keyword%”匹配,在数据库的数据庞大以及某个字段存储的数据量庞大的时候,这种遍历是致命的,它需要对所有的记录进行匹配查询。因此,lucene主要适用于文档集的全文检索,以及海量数据库的模糊检索,特别是对数据库的 xml或者大数据的字符类型。 

    全文检索的实现机制

LuceneAPI接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。

 

比较一下Lucene和数据库:

Lucene

数据库

索引数据源:doc(field1,field2...) doc(field1,field2...)

\ indexer /

| Lucene Index|

/ searcher \

结果输出:Hits(doc(field1,field2) doc(field1...))

索引数据源:record(field1,field2...) record(field1..)

\ SQL: insert/

| DB Index |

/ SQL: select \

结果输出:results(record(field1,field2..) record(field1...))

Document:一个需要进行索引的“单元”

一个Document由多个字段组成

Record:记录,包含多个字段

Field:字段

Field:字段

Hits:查询结果集,由匹配的Document组成

RecordSet:查询结果集,由多个Record组成

 

全文检索 like "%keyword%"

-------------------------------------------------------------------------

Lucene作为一个全文检索引擎,其具有如下突出的优点:

 

1)索引文件格式独立于应用平台。Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。

2)在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。

3)优秀的面向对象的系统架构,使得对于Lucene扩展的学习难度降低,方便扩充新功能。

4)设计了独立于语言和文件格式的文本分析接口,索引器通过接受Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需要实现文本分析的接口

5)已经默认实现了一套强大的查询引擎,用户无需自己编写代码即使系统可获得强大的查询能力,Lucene的查询实现中默认实现了布尔操作、模糊查询(Fuzzy Search[11])、分组查询等等。

---------------------------------------------------------------------------------- 

lucence例子: 

Windows

    C:\file\1.txt; 内容:中华人民共和国,全国人民 2006 

    C:\file\2.txt;

    C:\file\3.txt;

    C:\index

step1.建立索引;step2.搜索文档 

建立索引 

为了对文档进行索引,Lucene 提供了五个基础的类,他们分别是 Document, Field, IndexWriter, Analyzer, Directory。下面我们分别介绍一下这五个类的用途: 

Document

Document 是用来描述文档的,这里的文档可以指一个 HTML 页面,一封电子邮件,或者是一个文本文件。一个 Document 对象由多个 Field 对象组成的。可以把一个 Document 对象想象成数据库中的一个记录,而每个 Field 对象就是记录的一个字段。 

Field

Field 对象是用来描述一个文档的某个属性的,比如一封电子邮件的标题和内容可以用两个 Field 对象分别描述。 

Analyzer

在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 AnalyzerAnalyzer 把分词后的内容交给 IndexWriter 来建立索引 

IndexWriter

IndexWriter Lucene 用来创建索引的一个核心的类,他的作用是把一个个的 Document 对象加到索引中来。 

Directory

这个类代表了 Lucene 的索引的存储的位置,这是一个抽象类,它目前有两个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引的位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引的位置。 

搜索文档 

利用Lucene进行搜索就像建立索引一样也是非常方便的。在上面一部分中,我们已经为一个目录下的文本文档建立好了索引,现在我们就要在这个索引上进行搜索以找到包含某个关键词或短语的文档。Lucene提供了几个基础的类来完成这个过程,它们分别是呢IndexSearcher, Term, Query, TermQuery, Hits. 下面我们分别介绍这几个类的功能。 

Query

这是一个抽象类,他有多个实现,比如TermQuery, BooleanQuery, PrefixQuery. 这个类的目的是把用户输入的查询字符串封装成Lucene能够识别的Query
Term

Term是搜索的基本单位,一个Term对象有两个String类型的域组成。生成一个Term对象可以有如下一条语句来完成:Term term = new Term(fieldName,queryWord); 其中第一个参数代表了要在文档的哪一个Field上进行查找,第二个参数代表了要查询的关键词。 

TermQuery

TermQuery是抽象类Query的一个子类,它同时也是Lucene支持的最为基本的一个查询类。生成一个TermQuery对象由如下语句完成: TermQuery termQuery = new TermQuery(new Term(fieldName,queryWord)); 它的构造函数只接受一个参数,那就是一个Term对象。 

IndexSearcher

IndexSearcher是用来在建立好的索引上进行搜索的。它只能以只读的方式打开一个索引,所以可以有多个IndexSearcher的实例在一个索引上进行操作。 

Hits

Hits是用来保存搜索的结果的。

————————————————————————

总结:

Lucene 其实很简单的,它最主要就是做两件事:建立索引和进行搜索
来看一些在lucene中使用的术语,这里并不打算作详细的介绍,只是点一下而已----因为这一个世界有一种好东西,叫搜索。

IndexWriter:lucene中最重要的的类之一,它主要是用来 将文档加入索引,同时控制索引过程中的一些参数使用。

Analyzer:分析器,主要用于分析搜索引擎遇到的各种文本。常用的 StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。

Directory:索引存放的位置;lucene提供了两种索引存放的 位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectoryRAMDirectory两个类。

Document:文档;Document相当于一个要进行索引的单元, 任何可以想要被索引的文件都必须转化为Document对象才能进行索引。

Field:字段。

IndexSearcher:lucene中最基本的检索工具,所有的 检索都会用到IndexSearcher工具;

Query:查询,lucene中支持模糊查询,语义查询,短语查询,组 合查询等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。

QueryParser: 是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。

Hits:在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才 算是完成搜索的目的。在lucene中,搜索的结果的集合是用Hits类的实例来表示的。

posted @ 2011-05-24 10:58 昊天 阅读(3705) | 评论 (0)编辑 收藏

概述

了解Quartz体系结构

Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述:

●Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在 JobDataMap实例中;

●JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。

通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;

●Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和 CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则 可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;

●Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历 特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合—— java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个 Trigger可以和多个Calendar关联,以便排除或包含某些时间点。

假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触 发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干 个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周 进行定义;

●Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到 Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组 及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多 个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可 以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于 ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。 SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个 put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;

●ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。

Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz 知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而 有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执 行任务后都会对后面的执行发生影响。

正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的 StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的 复杂度,因此除非必要,应该尽量使用无状态的Job。

如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。

Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过 JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管 是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。

Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。

图1描述了Scheduler的内部组件结构,SchedulerContext提供Scheduler全局可见的上下文信息,每一个任务都对应一个JobDataMap,虚线表达的JobDataMap表示对应有状态的任务:

Scheduler结构图

一个Scheduler可以拥有多个Triger组和多个JobDetail组,注册Trigger和JobDetail 时,如果不显式指定所属的组,Scheduler将放入到默认组中,默认组的组名为Scheduler.DEFAULT_GROUP。组名和名称组成了对 象的全名,同一类型对象的全名不能相同。

Scheduler本身就是一个容器,它维护着Quartz的各种组件并实施调度的规则。Scheduler还拥有一个线 程池,线程池为任务提供执行线程——这比执行任务时简单地创建一个新线程要拥有更高的效率,同时通过共享节约资源的占用。通过线程池组件的支持,对于繁忙 度高、压力大的任务调度,Quartz将可以提供良好的伸缩性。

提示: Quartz完整下载包examples目录下拥有10多个实例,它们是快速掌握Quartz应用很好的实例。

使用SimpleTrigger

SimpleTrigger拥有多个重载的构造函数,用以在不同场合下构造出对应的实例:

●SimpleTrigger(String name, String group):通过该构造函数指定Trigger所属组和名称;

●SimpleTrigger(String name, String group, Date startTime):除指定Trigger所属组和名称外,还可以指定触发的开发时间;

●SimpleTrigger(String name, String group, Date startTime, Date endTime, int repeatCount, long repeatInterval):除指定以上信息外,还可以指定结束时间、重复执行次数、时间间隔等参数;

●SimpleTrigger(String name, String group, String jobName, String jobGroup, Date startTime, Date endTime, int repeatCount, long repeatInterval):这是最复杂的一个构造函数,在指定触发参数的同时,还通过jobGroup和jobName,让该Trigger和 Scheduler中的某个任务关联起来。

通过实现 org.quartz..Job 接口,可以使 Java 类化身为可调度的任务。代码清单1提供了 Quartz 任务的一个示例:

SimpleJob:简单的Job实现类

package com.baobaotao.basic.quartz;

import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class SimpleJob implements Job {

①实例Job接口方法

public void execute(JobExecutionContext jobCtx)throws JobExecutionException {
System.out.println(jobCtx.getTrigger().getName()+ " triggered. time is:" + (new Date()));
}
}

这个类用一条非常简单的输出语句实现了Job接口的execute(JobExecutionContext context) 方法,这个方法可以包含想要执行的任何代码。下面,我们通过SimpleTrigger对SimpleJob进行调度:

SimpleTriggerRunner:使用SimpleTrigger进行调度

package com.baobaotao.basic.quartz;

import java.util.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;

public class SimpleTriggerRunner {

public static void main(String args[]) {
try {
①创建一个JobDetail实例,指定SimpleJob
JobDetail jobDetail = new JobDetail("job1_1","jGroup1", SimpleJob.class);

②通过SimpleTrigger定义调度规则:马上启动,每2秒运行一次,共运行100次
SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1","tgroup1");
simpleTrigger.setStartTime(new Date());
simpleTrigger.setRepeatInterval(2000);
simpleTrigger.setRepeatCount(100);

③通过SchedulerFactory获取一个调度器实例
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, simpleTrigger);④ 注册并进行调度
scheduler.start();⑤调度启动
} catch (Exception e) {
e.printStackTrace();
}
}
}

首先在①处通过JobDetail封装SimpleJob,同时指定Job在Scheduler中所属组及名称,这里,组名为jGroup1,而名称为job1_1。

在②处创建一个SimpleTrigger实例,指定该Trigger在Scheduler中所属组及名称。接着设置调度的时间规则。

最后,需要创建Scheduler实例,并将JobDetail和Trigger实例注册到Scheduler中。这里, 我们通过StdSchedulerFactory获取一个Scheduler实例,并通过scheduleJob(JobDetail jobDetail, Trigger trigger)完成两件事:

1)将JobDetail和Trigger注册到Scheduler中;

2)将Trigger指派给JobDetail,将两者关联起来。

当Scheduler启动后,Trigger将定期触发并执行SimpleJob的execute(JobExecutionContext jobCtx)方法,然后每 10 秒重复一次,直到任务被执行 100 次后停止。

还可以通过SimpleTrigger的setStartTime(java.util.Date startTime)和setEndTime(java.util.Date endTime)指定运行的时间范围,当运行次数和时间范围冲突时,超过时间范围的任务运行不被执行。如可以通过 simpleTrigger.setStartTime(new Date(System.currentTimeMillis() + 60000L))指定60秒钟以后开始。

除了通过scheduleJob(jobDetail, simpleTrigger)建立Trigger和JobDetail的关联,还有另外一种关联Trigger和JobDetail的方式:

JobDetail jobDetail = new JobDetail("job1_1","jGroup1", SimpleJob.class);
SimpleTrigger simpleTrigger = new SimpleTrigger("trigger1_1","tgroup1");

simpleTrigger.setJobGroup("jGroup1");①-1:指定关联的Job组名
simpleTrigger.setJobName("job1_1");①-2:指定关联的Job名称
scheduler.addJob(jobDetail, true);② 注册JobDetail
scheduler.scheduleJob(simpleTrigger);③ 注册指定了关联JobDetail的Trigger

 

在这种方式中,Trigger通过指定Job所属组及Job名称,然后使用Scheduler的scheduleJob(Trigger trigger)方法注册Trigger。有两个值得注意的地方:

通过这种方式注册的Trigger实例必须已经指定Job组和Job名称,否则调用注册Trigger的方法将抛出异常;

引用的JobDetail对象必须已经存在于Scheduler中。也即,代码中①、②和③的先后顺序不能互换。

在构造Trigger实例时,可以考虑使用org.quartz.TriggerUtils工具类,该工具类不但提供了众 多获取特定时间的方法,还拥有众多获取常见Trigger的方法,如makeSecondlyTrigger(String trigName)方法将创建一个每秒执行一次的Trigger,而makeWeeklyTrigger(String trigName, int dayOfWeek, int hour, int minute)将创建一个每星期某一特定时间点执行一次的Trigger。而getEvenMinuteDate(Date date)方法将返回某一时间点一分钟以后的时间。

使用CronTrigger

CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。因此,相对于SimpleTrigger而 言,CronTrigger在使用上也要复杂一些。

Cron表达式

Quartz使用类似于Linux下的Cron表达式定义时间规则,Cron表达式由6或7个由空格分隔的时间字段组成,如表1所示:

Cron表达式时间字段

位置

时间域名

允许值

允许的特殊字符

1

0-59

, - * /

2

分钟

0-59

, - * /

3

小时

0-23

, - * /

4

日期

1-31

, - * ? / L W C

5

月份

1-12

, - * /

6

星期

1-7

, - * ? / L C #

7

年(可选)

空值1970-2099

, - * /

Cron表达式的时间字段除允许设置数值外,还可使用一些特殊的字符,提供列表、范围、通配符等功能,细说如下:

●星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,*在分钟字段时,表示“每分钟”;

●问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;

●减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12;

●逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;

●斜杠(/):x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;

●L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L在日期字段中,表示这个月 份的最后一天,如一月的31号,非闰年二月的28号;如果L用在星期中,则表示星期六,等同于7。但是,如果L出现在星期字段里,而且在前面有一个数值 X,则表示“这个月的最后X天”,例如,6L表示该月的最后星期五;

●W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如15W表示离该月15号最近的工 作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二。但必须注意 关联的匹配日期不能够跨月,如你指定1W,如果1号是星期六,结果匹配的是3号星期一,而非上个月最后的那天。W字符串只能指定单一日期,而不能指定日期 范围;

●LW组合:在日期字段可以组合使用LW,它的意思是当月的最后一个工作日;

●井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如6#3表示当月的第三个星期五(6表示星期五,#3表示当前的第三个),而4#5表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;

● C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有被关联,则相当于日历中所有日期。例如5C在日期字段中就相当于日历5日以后的第一天。1C在星期字段中相当于星期日后的第一天。

Cron表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。

表2下面给出一些完整的Cron表示式的实例:

Cron表示式示例

表示式

说明

"0 0 12 * * ? "

每天12点运行

"0 15 10 ? * *"

每天10:15运行

"0 15 10 * * ?"

每天10:15运行

"0 15 10 * * ? *"

每天10:15运行

"0 15 10 * * ? 2008"

在2008年的每天10:15运行

"0 * 14 * * ?"

每天14点到15点之间每分钟运行一次,开始于14:00,结束于14:59。

"0 0/5 14 * * ?"

每天14点到15点每5分钟运行一次,开始于14:00,结束于14:55。

"0 0/5 14,18 * * ?"

每天14点到15点每5分钟运行一次,此外每天18点到19点每5钟也运行一次。

"0 0-5 14 * * ?"

每天14:00点到14:05,每分钟运行一次。

"0 10,44 14 ? 3 WED"

3月每周三的14:10分到14:44,每分钟运行一次。

"0 15 10 ? * MON-FRI"

每周一,二,三,四,五的10:15分运行。

"0 15 10 15 * ?"

每月15日10:15分运行。

"0 15 10 L * ?"

每月最后一天10:15分运行。

"0 15 10 ? * 6L"

每月最后一个星期五10:15分运行。

"0 15 10 ? * 6L 2007-2009"

在2007,2008,2009年每个月的最后一个星期五的10:15分运行。

"0 15 10 ? * 6#3"

每月第三个星期五的10:15分运行。

CronTrigger实例

下面,我们使用CronTrigger对SimpleJob进行调度,通过Cron表达式制定调度规则,让它每5秒钟运行一次:

CronTriggerRunner:使用CronTrigger进行调度

package com.baobaotao.basic.quartz;

import org.quartz.CronExpression;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

public class CronTriggerRunner {

public static void main(String args[]) {
try {
JobDetail jobDetail = new JobDetail("job1_2", "jGroup1",SimpleJob.class);
①-1:创建CronTrigger,指定组及名称
CronTrigger cronTrigger = new CronTrigger("trigger1_2", "tgroup1");
CronExpression cexp = new CronExpression("0/5 * * * * ?");①-2:定义Cron表达式
cronTrigger.setCronExpression(cexp);①-3:设置Cron表达式
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
//②
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行CronTriggerRunner,每5秒钟将触发运行SimpleJob一次。默认情况下Cron表达式对应当前的时区,可以通过 CronTriggerRunner的setTimeZone(java.util.TimeZone timeZone)方法显式指定时区。你还也可以通过setStartTime(java.util.Date startTime)和setEndTime(java.util.Date endTime)指定开始和结束的时间。

在代码清单3的②处需要通过Thread.currentThread.sleep()的方式让主线程睡眠,以便调度器可 以继续工作执行任务调度。否则在调度器启动后,因为主线程马上退出,也将同时引起调度器关闭,调度器中的任务都将相应销毁,这将导致看不到实际的运行效 果。在单元测试的时候,让主线程睡眠经常使用的办法。对于某些长周期任务调度的测试,你可以简单地调整操作系统时间进行模拟。

使用Calendar

在实际任务调度中,我们不可能一成不变地按照某个周期性的调度规则运行任务,必须考虑到实现生活中日历上特定日期,就象习惯了大男人作风的人在2月14号也会有不同表现一样。

下面,我们安排一个任务,每小时运行一次,并将五一节和国际节排除在外,其代码如代码清单4所示:

CalendarExample:使用Calendar

package com.baobaotao.basic.quartz;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.quartz.impl.calendar.AnnualCalendar;
import org.quartz.TriggerUtils;

public class CalendarExample {

public static void main(String[] args) throws Exception {

SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
①法定节日是以每年为周期的,所以使用AnnualCalendar
AnnualCalendar holidays = new AnnualCalendar();

②五一劳动节
Calendar laborDay = new GregorianCalendar();
laborDay.add(Calendar.MONTH,5);
laborDay.add(Calendar.DATE,1);
holidays.setDayExcluded(laborDay, true); ②-1:排除的日期,如果设置为false则为包含
③国庆节
Calendar nationalDay = new GregorianCalendar();
nationalDay.add(Calendar.MONTH,10);
nationalDay.add(Calendar.DATE,1);
holidays.setDayExcluded(nationalDay, true);③-1:排除该日期
scheduler.addCalendar("holidays", holidays, false, false);④向Scheduler注册日历
Date runDate = TriggerUtils.getDateOf(0,0, 10, 1, 4);⑤4月1号 上午10点
JobDetail job = new JobDetail("job1", "group1", SimpleJob.class);
SimpleTrigger trigger = new SimpleTrigger("trigger1", "group1",
runDate,
null,
SimpleTrigger.REPEAT_INDEFINITELY,
60L * 60L * 1000L);
trigger.setCalendarName("holidays");⑥让Trigger应用指定的日历规则
scheduler.scheduleJob(job, trigger);
scheduler.start();
//实际应用中主线程不能停止,否则Scheduler得不到执行,此处从略
}
}

由于节日是每年重复的,所以使用org.quartz.Calendar的AnnualCalendar实现类,通过②、③的代码,指定五一和国庆 两个节日并通过AnnualCalendar#setDayExcluded(Calendar day, boolean exclude)方法添加这两个日期。exclude为true时表示排除指定的日期,如果为false时表示包含指定的日期。

在定制好org.quartz.Calendar后,还需要通过 Scheduler#addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)进行注册,如果updateTriggers为true,Scheduler中已引用Calendar的Trigger将 得到更新,如④所示。

在⑥处,我们让一个Trigger指定使用Scheduler中代表节日的Calendar,这样Trigger就会避开五一和国庆这两个特殊日子了。

任务调度信息存储

在默认情况下Quartz将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能,因为内存中数据访问最快。不足之处是缺乏数据的持久性,当程序路途停止或系统崩溃时,所有运行的信息都会丢失。

比如我们希望安排一个执行100次的任务,如果执行到50次时系统崩溃了,系统重启时任务的执行计数器将从0开始。在大多数实际的应用中,我们往往并不需要保存任务调度的现场数据,因为很少需要规划一个指定执行次数的任务。

对于仅执行一次的任务来说,其执行条件信息本身应该是已经持久化的业务数据(如锁定到期解锁任务,解锁的时间应该是业务数据),当执行完成后,条件信息也会相应改变。当然调度现场信息不仅仅是记录运行次数,还包括调度规则、JobDataMap中的数据等等。

如果确实需要持久化任务调度信息,Quartz允许你通过调整其属性文件,将这些信息保存到数据库中。使用数据库保存任务 调度信息后,即使系统崩溃后重新启动,任务的调度信息将得到恢复。如前面所说的例子,执行50次崩溃后重新运行,计数器将从51开始计数。使用了数据库保 存信息的任务称为持久化任务。

通过配置文件调整任务调度信息的保存策略

其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,可以在类路 径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。

先来了解一下Quartz的默认属性配置文件:

代码清单5 quartz.properties:默认配置

①集群的配置,这里不使用集群

org.quartz.scheduler.instanceName = DefaultQuartzScheduler

org.quartz.scheduler.rmi.export = false

org.quartz.scheduler.rmi.proxy = false

org.quartz.scheduler.wrapJobExecutionInUserTransaction = false

②配置调度器的线程池

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

org.quartz.threadPool.threadCount = 10

org.quartz.threadPool.threadPriority = 5

org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

③配置任务调度现场数据保存机制

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

Quartz的属性配置文件主要包括三方面的信息:

1)集群信息;

2)调度器线程池;

3)任务调度现场数据的保存。

如果任务数目很大时,可以通过增大线程池的大小得到更好的性能。默认情况下,Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,顾名思义,信息保存在RAM内存中,我们可以通过以下设置将任务调度现场数据保存到数据库中:

quartz.properties:使用数据库保存任务调度现场数据

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

org.quartz.jobStore.tablePrefix = QRTZ_①数据表前缀

org.quartz.jobStore.dataSource = qzDS②数据源名称

③定义数据源的具体属性

org.quartz.dataSource.qzDS.driver = oracle.jdbc.driver.OracleDriver

org.quartz.dataSource.qzDS.URL = jdbc:oracle:thin:@localhost:1521:ora9i

org.quartz.dataSource.qzDS.user = stamen

org.quartz.dataSource.qzDS.password = abc

org.quartz.dataSource.qzDS.maxConnections = 10

要将任务调度数据保存到数据库中,就必须使用 org.quartz.impl.jdbcjobstore.JobStoreTX代替原来的org.quartz.simpl.RAMJobStore 并提供相应的数据库配置信息。首先①处指定了Quartz数据库表的前缀,在②处定义了一个数据源,在③处具体定义这个数据源的连接信息。

你必须事先在相应的数据库中创建Quartz的数据表(共8张),在Quartz的完整发布包的docs/dbTables目录下拥有对应不同数据库的SQL脚本。

查询数据库中的运行信息

任务的现场保存对于上层的Quartz程序来说是完全透明的,我们在src目录下编写一个如代码清单6所示的 quartz.properties文件后,重新运行代码清单2或代码清单3的程序,在数据库表中将可以看到对应的持久化信息。当调度程序运行过程中途停 止后,任务调度的现场数据将记录在数据表中,在系统重启时就可以在此基础上继续进行任务的调度。

JDBCJobStoreRunner:从数据库中恢复任务的调度

package com.baobaotao.basic.quartz;

import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class JDBCJobStoreRunner {

public static void main(String args[]) {
try {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
①获取调度器中所有的触发器组
String[] triggerGroups = scheduler.getTriggerGroupNames();
②重新恢复在tgroup1组中,名为trigger1_1触发器的运行
for (int i = 0; i < triggerGroups.length; i++) {
String[] triggers = scheduler.getTriggerNames(triggerGroups[i]);
for (int j = 0; j < triggers.length; j++) {
Trigger tg = scheduler.getTrigger(triggers[j],triggerGroups[i]);
if (tg instanceof SimpleTrigger
&& tg.getFullName().equals("tgroup1.trigger1_1")) {②-1:根据名称判断
②-1:恢复运行
scheduler.rescheduleJob(triggers[j], triggerGroups[i],tg);
}
}
}
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

当代码清单2中的SimpleTriggerRunner执行到一段时间后非正常退出,我们就可以通过这个 JDBCJobStoreRunner根 据记录在数据库中的现场数据恢复任务的调度。Scheduler中的所有Trigger以及JobDetail的运行信息都会保存在数据库中,这里我们仅 恢复tgroup1组中名称为trigger1_1的触发器,这可以通过如②-1所示的代码进行过滤,触发器的采用GROUP.TRIGGER_NAME 的全名格式。通过Scheduler#rescheduleJob(String triggerName,String groupName,Trigger newTrigger)即可重新调度关联某个Trigger的任务。

下面我们来观察一下不同时期qrtz_simple_triggers表的数据:

1.运行代码清单2的SimpleTriggerRunner一小段时间后退出:

REPEAT_COUNT表示需要运行的总次数,而TIMES_TRIGGER表示已经运行的次数。

2.运行代码清单7的JDBCJobStoreRunner恢复trigger1_1的触发器,运行一段时间后退出,这时qrtz_simple_triggers中的数据如下:

首先Quartz会将原REPEAT_COUNT-TIMES_TRIGGER得到新的REPEAT_COUNT值,并记录已经运行的次数(重新从0开始计算)。

3.重新启动JDBCJobStoreRunner运行后,数据又将发生相应的变化:

4.继续运行直至完成所有剩余的次数,再次查询qrtz_simple_triggers表:

这时,该表中的记录已经变空。

值得注意的是,如果你使用JDBC保存任务调度数据时,当你运行代码清单2的SimpleTriggerRunner然后退出,当再次希望运行SimpleTriggerRunner时,系统将抛出JobDetail重名的异常:

Unable to store Job with name: 'job1_1' and group: 'jGroup1', because one already exists with this identification.

因为每次调用Scheduler#scheduleJob()时,Quartz都会将JobDetail和Trigger的信息保存到数据库中,如果数据表中已经同名的JobDetail或Trigger,异常就产生了。

本 文使用quartz 1.6版本,我们发现当后台数据库使用MySql时,数据保存不成功,该错误是Quartz的一个Bug,相信会在高版本中得到修复。因为HSQLDB不 支持SELECT * FROM TABLE_NAME FOR UPDATE的语法,所以不能使用HSQLDB数据库。

小结

Quartz 提供了最为丰富的任务调度功能,不但可以制定周期性运行的任务调度方案,还可以让你按照日历相关的方式进行任 务调度。Quartz框架的重要组件包括Job、JobDetail、Trigger、Scheduler以及辅助性的JobDataMap和 SchedulerContext。

Quartz拥有一个线程池,通过线程池为任务提供执行线程,你可以通过配置文件对线程池进行参数定 制。Quartz的另 一个重要功能是可将任务调度信息持久化到数据库中,以便系统重启时能够恢复已经安排的任务。此外,Quartz还拥有完善的事件体系,允许你注册各种事件 的监听器。

posted @ 2011-05-24 10:55 昊天 阅读(5815) | 评论 (0)编辑 收藏

如果直接drop掉,与这个sequence相关的function trigger什么的在下次运行的时候都会重新编译,而且编译有可能会失败,所以采用了下边的方法:

假设sequence seq属性如下:
increment 1
minvalue 1

则用下边方法:
alter sequence seq minvalue 0;
select seq.nextval from dual; --假设值是30
alter sequence seq increment by -30;
select seq.nextval from dual; --会得到0
alter sequence seq minvalue by 1;
alter sequence seq increment by 1;

这样之后再取的时候就会从1开始了。

posted @ 2011-03-14 09:59 昊天 阅读(955) | 评论 (0)编辑 收藏
select t.task_id,
       max(decode(t.ps_org_no, '35406', t.value)) a,
       max(decode(t.ps_org_no, '3540601', t.value)) b,
       max(decode(t.ps_org_no, '3540602', t.value)) c,
       max(decode(t.ps_org_no, '3540603', t.value)) d
from s_csqe_eva_rslt t
where t.task_id = '28884'
   and t.content_id = '24730'
group by t.task_id;

 
删除重复值

方法二: delete from demo where rowid in
(select rid from
(select rowid rid,row_number() over(partition by object_id order by rowid) rn
from demo)
where rn <> 1 );
耗时:30秒

方法三: create table demo2 as
select object_id,owner... from
(select demo.*,row_number() over(partition by object_id order by rowid) rn from demo)
where rn = 1;
truncate table demo; insert into demo select * from demo2; drop table demo2;
共耗时: 10秒,适合大数据量的情况,产生更少回滚量;

 

posted @ 2011-03-14 09:54 昊天 阅读(564) | 评论 (0)编辑 收藏

SELECT 0 id, '' org_no, '' org_name
FROM dual
UNION
SELECT 1 id,
       a.org_no || ',' || a.org_type org_no,
       lpad(' ', 4 * (LEVEL - 1)) || a.org_name org_name
FROM o_org a
WHERE LEVEL <= 4
START WITH a.org_no = '35406'
CONNECT BY a.p_org_no = PRIOR a.org_no

posted @ 2011-03-14 09:53 昊天 阅读(1996) | 评论 (0)编辑 收藏

在java.lang包里有个ClassLoader类,ClassLoader 的基本目标是对类的请求提供服务,按需动态装载类和资
源,只有当一个类要使用(使用new 关键字来实例化一个类)的时候,类加载器才会加载这个类并初始化。
一个Java应用程序可以使用不同类型的类加载器。例如Web Application Server中,Servlet的加载使用开发
商自定义的类加载器, java.lang.String在使用JVM系统加载器,Bootstrap Class Loader,开发商定义的其他类
则由AppClassLoader加载。在JVM里由类名和类加载器区别不同的Java类型。因此,JVM允许我们使用不同
的加载器加载相同namespace的java类,而实际上这些相同namespace的java类可以是完全不同的类。这种
机制可以保证JDK自带的java.lang.String是唯一的。
2. 加载类的两种方式:
(1)  隐式方式
使用new关键字让类加载器按需求载入所需的类
(2)  显式方式
由 java.lang.Class的forName()方法加载
public static Class forName(String className)
public static Class forName(String className, boolean initialize,ClassLoader loader)
参数说明:
        className - 所需类的完全限定名
        initialize - 是否必须初始化类(静态代码块的初始化)
        loader - 用于加载类的类加载器

调用只有一个参数的forName()方法等效于 Class.forName(className, true, loader)。
这两个方法,最后都要连接到原生方法forName0(),其定义如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader)
 throws ClassNotFoundException;
只有一个参数的forName()方法,最后调用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而三个参数的forName(),最后调用的是:
forName0(name, initialize, loader);
所以,不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含
了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数
为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块
才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。
直接使用类加载器
获得对象所属的类 : getClass()方法
获得该类的类加载器 : getClassLoader()方法
3.执行java XXX.class的过程
找到JRE——》找到jvm.dll——》启动JVM并进行初始化——》产生Bootstrap Loader——》
载入ExtClassLoader——》载入AppClassLoader——》执行java XXX.class

posted @ 2011-03-14 09:49 昊天 阅读(2121) | 评论 (0)编辑 收藏
利用 Spring 和 EHCache 缓存结果(翻译)

导言

   从 Spring 1.1.1 开始,EHCache 就作为一种通用缓存解决方案集成进 Spring。
我将示范拦截器的例子,它能把方法返回的结果缓存起来。
  利用 Spring IoC 配置 EHCache
在 Spring 里配置 EHCache 很简单。你只需一个 ehcache.xml 文件,该文件用于配置 EHCache:
<ehcache>
    <!—设置缓存文件 .data 的创建路径。
         如果该路径是 Java 系统参数,当前虚拟机会重新赋值。
         下面的参数这样解释:
         user.home – 用户主目录
         user.dir      – 用户当前工作目录
         java.io.tmpdir – 默认临时文件路径 -->
    <diskStore path="java.io.tmpdir"/>

    <!—缺省缓存配置。CacheManager 会把这些配置应用到程序中。
        下列属性是 defaultCache 必须的:
        maxInMemory           - 设定内存中创建对象的最大值。
        eternal                        - 设置元素(译注:内存中对象)是否永久驻留。如果是,将忽略超
                                              时限制且元素永不消亡。
        timeToIdleSeconds  - 设置某个元素消亡前的停顿时间。
                                              也就是在一个元素消亡之前,两次访问时间的最大时间间隔值。
                                              这只能在元素不是永久驻留时有效(译注:如果对象永恒不灭,则
                                              设置该属性也无用)。
                                              如果该值是 0 就意味着元素可以停顿无穷长的时间。
        timeToLiveSeconds - 为元素设置消亡前的生存时间。
                                               也就是一个元素从构建到消亡的最大时间间隔值。
                                               这只能在元素不是永久驻留时有效。
        overflowToDisk        - 设置当内存中缓存达到 maxInMemory 限制时元素是否可写到磁盘
                                               上。
        -->
    <cache name="org.taha.cache.METHOD_CACHE"
        maxElementsInMemory="300"
        eternal="false"
        timeToIdleSeconds="500"
        timeToLiveSeconds="500"
        overflowToDisk="true"
        />
</ehcache>

     拦截器将使用 ”org.taha.cache.METHOD_CACHE” 区域缓存方法返回结果。下面利用 Spring IoC 让 bean 来访问这一区域。
<!-- ======================   缓存   ======================= -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
  <property name="configLocation">
    <value>classpath:ehcache.xml</value>
  </property>
</bean>
<bean id="methodCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
  <property name="cacheManager">
    <ref local="cacheManager"/>
  </property>
  <property name="cacheName">
    <value>org.taha.cache.METHOD_CACHE</value>
  </property>
</bean>
    
       构建我们的 MethodCacheInterceptor 该拦截器实现org.aopalliance.intercept.MethodInterceptor接口。一旦运行起来(kicks-in),它首先检查被拦截方法是否被配置为可缓存的。这将可选择性的配置想要缓存的 bean 方法。只要调用的方法配置为可缓存,拦截器将为该方法生成 cache key 并检查该方法返回的结果是否已缓存。如果已缓存,就返回缓存的结果,否则再次调用被拦截方法,并缓存结果供下次调用。
org.taha.interceptor.MethodCacheInterceptor
/*
* Copyright 2002-2004 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES or CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.taha.interceptor;
import java.io.Serializable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
/**
* @author <a href="mailto:irbouh@gmail.com">Omar Irbouh</a>
* @since 2004.10.07
*/
public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {
  private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);
  private Cache cache;
  /**
   * 设置缓存名
   */
  public void setCache(Cache cache) {
    this.cache = cache;
  }
  /**
   * 检查是否提供必要参数。
   */
  public void afterPropertiesSet() throws Exception {
    Assert.notNull(cache, "A cache is required. Use setCache(Cache) to provide one.");
  }
  /**
   * 主方法
   * 如果某方法可被缓存就缓存其结果
   * 方法结果必须是可序列化的(serializable)
   */
  public Object invoke(MethodInvocation invocation) throws Throwable {
    String targetName  = invocation.getThis().getClass().getName();
    String methodName  = invocation.getMethod().getName();
    Object[] arguments = invocation.getArguments();
    Object result;
    logger.debug("looking for method result in cache");
    String cacheKey = getCacheKey(targetName, methodName, arguments);
    Element element = cache.get(cacheKey);
    if (element == null) {
      //call target/sub-interceptor
      logger.debug("calling intercepted method");
      result = invocation.proceed();
      //cache method result
      logger.debug("caching result");
      element = new Element(cacheKey, (Serializable) result);
      cache.put(element);
    }
    return element.getValue();
  }
  /**
   * creates cache key: targetName.methodName.argument0.argument1...
   */
  private String getCacheKey(String targetName,
                             String methodName,
                             Object[] arguments) {
    StringBuffer sb = new StringBuffer();
    sb.append(targetName)
      .append(".").append(methodName);
    if ((arguments != null) && (arguments.length != 0)) {
      for (int i=0; i<arguments.length; i++) {
        sb.append(".")
          .append(arguments[i]);
      }
    }
    return sb.toString();
  }
}




MethodCacheInterceptor 代码说明了:
    默认条件下,所有方法返回结果都被缓存了(methodNames 是 null)
    缓存区利用 IoC 形成
    cacheKey 的生成还包括方法参数的因素(译注:参数的改变会影响 cacheKey)
使用 MethodCacheInterceptor
下面摘录了怎样配置 MethodCacheInterceptor:
<bean id="methodCacheInterceptor" class="org.taha.interceptor.MethodCacheInterceptor">
  <property name="cache">
    <ref local="methodCache" />
  </property>
</bean>
<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  <property name="advice">
    <ref local="methodCacheInterceptor"/>
  </property>
  <property name="patterns">
    <list>
      <value>.*methodOne</value>
      <value>.*methodTwo</value>
    </list>
  </property>
</bean>
<bean id="myBean" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target">
   <bean class="org.taha.beans.MyBean"/>
  </property>
  <property name="interceptorNames">
    <list>
      <value>methodCachePointCut</value>
    </list>
  </property>
</bean>
posted @ 2011-03-08 11:25 昊天 阅读(1232) | 评论 (0)编辑 收藏
     摘要: 第二章 Struts2的工作机制及分析 概述 本章讲述Struts2的工作原理。 读者如果曾经学习过Struts1.x或者有过Struts1.x的开发经验,那么千万不要想当然地以为这一章可以跳过。实际上Struts1.x与Struts2并无我们想象的血缘关系。虽然Struts2的开发小组极力保留Struts1.x的习惯,但因为Struts2的核心设计完全改变,从思想到设计到工作流程,都有了很...  阅读全文
posted @ 2011-02-28 14:59 昊天 阅读(387) | 评论 (0)编辑 收藏
     摘要: Struts2架构图         请求首先通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和Dispatcher;FilterDispatcher主要通过AcionMapper来决定需要调用哪个Action。  &n...  阅读全文
posted @ 2011-02-28 11:22 昊天 阅读(1101) | 评论 (0)编辑 收藏
仅列出标题
共7页: 上一页 1 2 3 4 5 6 7 下一页 

导航

<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

统计

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜