整合即时通讯的软件和网站

今天偶然看到身边的一个西人同事在使用浏览器在聊天,就用Google查了一下,发现整合的即时通讯软件和网站还不少。在此列一下,方便那些在某些地方不让使用这些软件的人可以继续网上的聊天。
1. Meebo
一个纯web的IM整合环境,可以将你的多个IM全部注册在里边,让你绝不错过任何一个。
 
2. Trillian
具有和meebo一样的纯web整合环境,更具有客户端软件,并支持IPhone, BlackBerry的手持终端。
 
3. Pidgin
它是一款跨平台、开源且完全免费的通讯软件,支持多种IM账号协议,例如 QQ、MSN、Jabber(Gtalk)、AIM、Yahoo! 、ICQ、IRC、SILC、Novell GroupWise、Napster、Zephyr 和Gadu-Gadu。通过插件,Pidgin甚至还能支持新浪点点通、飞信等等。 对与AIM、ICQ、MSN、GTalk甚至可以支持新邮件通知,非常方便。不过暂不支持QQ的新邮件通知。Pidgin的前身是Gaim,相信很多朋友都会有所耳闻了。(软件介绍http://www.iplaysoft.com/pidgin.html
website:http://www.pidgin.im/
 
4. Digsby
集成市面上的主流聊天软件外(不包括QQ),它还能让你收发邮件(提供邮件提醒功能)、上Facebook查看同学近况、在twitter上灌水等等,可谓说是非常全能的哟!(软件介绍http://www.iplaysoft.com/digsby.html
 
5.Instanbird
Mozilla 家族迎来了它的最新成员Instanbird,一个支持多种即时消息服务的客户端程序,目前支持:AIM、Gadu-Gadu、Google Talk、ICQ、MSN、QQ、XMPP 和Yahoo! 通过它你可以同时连接到你所有的即时通信账户!这个项目目前还是Beta版本,但是那些喜欢冒险的用户,现在可以尝试编译一个属于自己的版本了!
 
上边各款,均是国外的产品,下面来一个中国的。
6. 新浪UC2009II
最新版本的新浪UC已经做得非常不错了,处了整合IM聊天工具之外,新浪UC还有4G的网络硬盘、支持500人的群、多人语音聊天等功能。对于大部分用QQ的人来说,新浪UC作为一个二线的聊天工具(例如用来上MSN与GTalk),的确是一个不错的补充。方便一些不喜国外软件的朋友吧,有需要的就试试,不适合自己的无视吧……(软件介绍http://www.iplaysoft.com/sina-uc.html
 
7.金山加加
将主流即时通讯软件QQ、MSN、雅虎通集成到一起,轻而一举切换到其它IM软件;整合局域网即时通讯软件功能,无需接入互联网,就能与局域网中好友聊天、发送文件等;整合金山词霸搜索等金山产品与服务。进入安装后可以按照向导顺利完成安装。QQ是企鹅、UC是小鸟,而金山加加代言形象是一只小猴。(软件介绍http://soft.yesky.com/tools/196/2018196.shtml
 
怎么样,试一下!
Posted in Computers and Internet | Leave a comment

Spring HTTP Invoker

http://zhenggm.javaeye.com/blog/562216

 

概念 
Spring HTTP invoker是spring框架中的一个远程调用模型,执行基于HTTP的远程调用(意味着可以通过防火墙),并使用java的序列化机制在网络间传递对象。 
效率 
远程调用效率: rmi>spring http invoker>webservice 
优点 
1.spring http invoker 采用java的序列化机制,并采用类似rmi的方式调用,既能够使用http协议轻松穿过防火墙,又能够保证高效的远程调用。 
2.轻量级的远程调用模型,对于采用spring的项目可以无缝嵌入,侵入性小。 
3.非常简单,容易上手。 
缺点 
只支持spring. 
例子 
服务端 
1.web.xml 

  1. <servlet>    
  2.         <servlet-name>service</servlet-name>    
  3.         <servlet-class>    
  4.             org.springframework.web.servlet.DispatcherServlet     
  5.         </servlet-class>    
  6.         <load-on-startup>1</load-on-startup>    
  7. </servlet>  
  8. <servlet-mapping>    
  9.         <servlet-name>service</servlet-name>    
  10.         <url-pattern>/httpservice/*</url-pattern>    
  11. </servlet-mapping>  

2. spring配置文件 

Xml代码  

  1. <beans>  
  2.     <bean id="userInfoServiceProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">  
  3.         <property name="service" ref="userInfoService"/>  
  4.         <property name="serviceInterface" value="cn.gov.zjport.skeleton.sso.UserInfoService"/>  
  5.     </bean>  
  6. </beans>  

其中userInfoService为项目原有的service 
3. 增加一个service-servlet.xml,放在web-inf目录下 

Xml代码  

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd&quot;>  
  3.   
  4. <beans>  
  5.     <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">    
  6.         <property name="mappings">    
  7.             <props>    
  8.                   <prop key="/userInfoService">userInfoServiceProxy</prop>  
  9.             </props>    
  10.         </property>    
  11.     </bean>   
  12. </beans>  

至此服务端发布完毕。 
客户端 
1. spring配置文件 

Xml代码  

  1. <bean id="userInfoService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">    
  2.         <property name="serviceUrl" value="http://<server&gt;:<port>/<domain>/httpservice/userInfoService"/>    
  3.         <property name="serviceInterface" value="cn.gov.zjport.skeleton.sso.UserInfoService"/>    
  4.     </bean>  

2. 调用代码 

Java代码  

  1. String xml=userInfoService.getUserInfo("simpleadmin"true);  

与非远程调用代码一摸一样。 
所以说对于采用spring的项目,只需要简单配置,即可实现分布式部署与远程调用。

Posted in Spring 编程 | Leave a comment

Java日志框架:SLF4J, Apache Common-Logging, Log4J和Logback

Log4j
Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务 器、NT的事件记录器、UNIX Syslog守护进程等;用户也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,用户能够更加细致地控制日志的生成过程。这些可以通过一个 配置文件来灵活地进行配置,而不需要修改程序代码。
 
LOGBack 
Logback是由log4j创始人设计的又一个开源日记组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日记系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日记的功能。

 
Log4J vs. LOGBack
LOGBack作为一个通用可靠、快速灵活的日志框架,将作为Log4j的替代和SLF4J组成新的日志系统的完整实现。LOGBack声称具有极佳的性能,“ 某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在LogBack中需要3纳秒,而在Log4J中则需要30纳秒。 LogBack创建记录器(logger)的速度也更快:13微秒,而在Log4J中需要23微秒。更重要的是,它获取已存在的记录器只需94纳秒,而 Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的”。


另外,LOGBack的所有文档是全面免费提供的,不象Log4J那样只提供部分免费文档而需要用户去购买付费文档。

SLF4J 
简单日记门面(Facade)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的loging APIs实现。 Logging API实现既可以选择直接实现SLF4J接的loging APIs如: NLOG4J、SimpleLogger。也可以通过SLF4J提供的API实现来开发相应的适配器如Log4jLoggerAdapter、JDK14LoggerAdapter。 

Apache Common-Logging

目前广泛使用的Java日志门面库。通过动态查找的机制,在程序运行时自动找出真正使用的日志库。但由于它使用了ClassLoader寻找和载入底层的日志库, 导致了象OSGI这样的框架无法正常工作,由于其不同的插件使用自己的ClassLoader。 OSGI的这种机制保证了插件互相独立,然而确使Apache Common-Logging无法工作。
 
SLF4J vs. Apache Common-Logging
SLF4J库类似于Apache Common-Logging。但是,他在编译时静态绑定真正的Log库。使用SLF4J时,如果你需要使用某一种日志实现,那么你必须选择正确的SLF4J的jar包的集合。 如此便可以在OSGI中使用了。
另外,SLF4J 支持参数化的log字符串,避免了之前为了减少字符串拼接的性能损耗而不得不写的if(logger.isDebugEnable()),现在你可以直接写:logger.debug(“current user is: {}”, user)。拼装消息被推迟到了它能够确定是不是要显示这条消息的时候,但是获取参数的代价并没有幸免。同时,日志中的参数若超过三个,则需要将参数以数组的形式传入,如:

Object[] params = {value1, value2, value3};

logger.debug(“first value: {}, second value: {} and third value: {}.”, params);


现在,Hibernate、Jetty、Spring-OSGi、Wicket和MINA等项目都已经迁移到了SLF4J,由此可见SLF4J的影响力不可忽视。


使用CommonLog接口而实际由SLF4J和Log4j实现的过程

1、项目中照常使用
import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory;
编写日志。

2、仍然在src下使用log4j.properties文件进行配置。

3、使用的所有jar文件:
   1)log4j-1.2.15.jar 这是log4j的库。 SLF4J并不改变这个底层实现库。
   2)slf4j-api-1.5.2.jar 这是SLF4J库。 
   3)slf4j-log4j12-1.5.2.jar 这包含Log4j的适配器和静态绑定log4j底层实现。
   4)jcl-over-slf4j-1.5.2.jar 这提供了Commons-Logging接口,以及使用common-loggin的接口,底层还是由SLF4J来决定哪种实现机制 。

这里,我们需要使用Log4j的原生库,但是不需要Commons-Logging的原生库。

一切就绪,把上面这4个jar包复制到lib下,导入项目中,就可以像以往一样继续使用Apache Common-Logging编写日志了。
 
看一看logback.xml的配置

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <substitutionProperty name="log.base" value="../logs/hzg" />

    <jmxConfigurator />

    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">

        <layout class="ch.qos.logback.classic.PatternLayout">

            <pattern>%date [%thread] %-5level %logger{80} – %msg%n</pattern>

        </layout>

    </appender>

    <!– 文件输出日志 (文件大小策略进行文件输出,超过指定大小对文件备份)–>

    <appender name="logfile" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <Encoding>UTF-8</Encoding>

        <File>${log.base}.log</File>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <FileNamePattern>${log.base}.%d{yyyy-MM-dd}.log.zip</FileNamePattern>

        </rollingPolicy>

          <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">

            <MaxFileSize>2MB</MaxFileSize>

        </triggeringPolicy>

        <layout class="ch.qos.logback.classic.PatternLayout">

            <pattern>%date [%thread] %-5level %logger{80} – %msg%n</pattern>

        </layout>

    </appender>

   

    <!– 需要记录日志的包 –>

    <logger name="org.springframework">

        <level value="WARN" />

    </logger>

    <logger name="org.hibernate">

        <level value="WARN" />

    </logger>

    <logger name="org.hibernate.SQL">

        <level value="WARN" />

    </logger>

        <logger name="org.hibernate.cache">

        <level value="ERROR" />

    </logger>

    <root>

        <level value="INFO" />

        <appender-ref ref="stdout" />

        <appender-ref ref="logfile" />

    </root>

</configuration>

 

 

使用slf4j+logback的优势:

 

  • 支持按文件大小或基于时间的切分方式,可自定义命名模式
  • 支持文件打包(触发器方式)
  • 支持OSGI环境

如果在单纯的logging环境中,使用SLF4J意义不大。如果想在各种logger API中切换,SELF4J是理想选择,另外在新的项目中,使用SLF4J+Logback是比较好的日志框架选型。

 
Posted in LOG 编程 | Leave a comment

Java加解密的基础

在Java的安全包中,包括了三部分内容:
1、JCA/JCE(Java Cryptography Architecture & JavaCryptography Extensions)
2、JSSE( Java Secure-Sockets Extension)
3、JAAS( Java Authentication & AuhorizationService)

在这里,将仅介绍加解密的实现。

Java 中的加解密的实现,是由JCA(Java Cryptography Architecture) 和JCE(Java Cryptography Extension)共同组成。 在JDK1.4之前,相关的包需要单独下载和安装。现在,Java加密扩展(JCE)已经成为Java SDK 1.4的核心组成部分。JCE是一组提供了加密框架并实现了一些加密,密钥生成算法和协议,消息认证码(MAC)等算法的Java包。

什么是Provider?
  JCA/JCE并不执行各种算法,它们只是连接应用和实际算法实现程序的一组接口。软件开发商根据JCE接口,将各种算法实现后,打包成一个Provider,可以动态地加到Java运行环境中。由于美国出口控制规定,JCA是可出口的(JCA和一个Sun的默认实现包括在Java2中),但是JCE对部分国家是限制出口的。因此,要实现一个完整的安全结构,就需要一个或多个第三方厂商提供的JCE产品,称为安全提供者(Provider)。
Provider是特定加密算法的实现者,有的供应商提供的加密技术是免费的,有的不免费。这些厂商有IBM,Bouncy Castle和RSA。Sun也给出了实现自己的Provider时需要遵循一些约定。

 在低版本JDK上JCE的安装
1.静态安装

    在安装和使用JCE之前,你需要从 Sun Web site(这里是以暗中sun的提供者为例)获得安装包。JCE中已经办函Sun自己的安全Provider – SunJCE,为了把SunJCE静态的安装到默认的Provider列表中,你需要修改安全属性文件:
•    <java-home>\jre\lib\security\java.security (Win32) 
•    <java-home>/jre/lib/security/java.security (UNIX)
假如你把JDK安装在C:\jdk1.3,你需要编辑以下文件:
C:\jdk1.3\jre\lib\security\java.security 
为了安装SunJCE,你需要在以上文件中加入:
security.provider.n=com.sun.crypto.provider.SunJCE
把n用你加入的提供者的优先级代替(注意:序号要保持递增,不能跳过,但可以调整前后顺序)。
代码A,用于查看你安装过的提供者的信息,结果在显示清单B中列出,显示Provider的能力,比如说可用的加密算法。
代码A: ProviderInformation.java
import java.security.Provider;
import java.security.Security;
import java.util.Set;
import java.util.Iterator;
public class ProviderInformation {
    public static void main(String[] args) {
        Provider[] providers = Security.getProviders();
        for (int i = 0; i < providers.length; i++) {
            Provider provider = providers[i];
            System.out.println("Provider name: " + provider.getName());
            System.out.println("Provider information: " + provider.getInfo());
            System.out.println("Provider version: " + provider.getVersion());
            Set entries = provider.entrySet();
            Iterator iterator = entries.iterator();
           while (iterator.hasNext()) {
                System.out.println("Property entry: " + iterator.next());
            }
        }
    }
}
显示清单B:
ProviderInformation.java output
Provider name: SUN
Provider information: SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore)
Provider version: 1.2
Property entry: Alg.Alias.KeyFactory.1.2.840.10040.4.1=DSA
Property entry: Alg.Alias.Signature.1.2.840.10040.4.3=SHA1withDSA
Property entry: Alg.Alias.KeyPairGenerator.OID.1.2.840.10040.4.1=DSA
Property entry: Signature.SHA1withDSA KeySize=1024
Property entry: Signature.SHA1withDSA ImplementedIn=Software

动态安装:
代码C说明了如何在运行时动态加载Provider,要注意的是,当你用Security.addProvider(…)加载Provider时,它是对整个JVM环境都可用的。
代码C: DynamicProvider.java
import java.security.Security;
public class DynamicProvider {
    public static void main(String[] args) {
        // This is all there is to it!
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
    }
}
    如前所述,当你安装一个Provider时,你用n来指明此提供者的优先级,但一个算法的实例被调用时,JVM将按照提供的优先级来在已经安装的提供者中查找可用的实现,并使用他首先找到的可用算法。你也可用在调用时加上附加参数来指明要在哪个提供者中寻找使用的算法。

实现细节:
JCE API包含了大量的为实现安全特性的类和接口,首先,我们做一个DES对称(Symmetric)加密的例子。

生成密钥:
下面的代码展示了如何使用密钥生成器来生成密钥。
代码D: DESKeyGenerator.java
 
import javax.crypto.KeyGenerator;
import java.security.Key;
import java.security.NoSUChAlgorithmException;
import java.security.Security;
public class DESKeyGenerator {
    public static void main(String[] args) {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        try {
            KeyGenerator kg = KeyGenerator.getInstance("DES");
            Key key = kg.generateKey();
            System.out.println("Key format: " + key.getFormat());
            System.out.println("Key algorithm: " + key.getAlgorithm());
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}

为了生成密钥,首先要初始化密钥生成器,这一步可以通过调用KeyGenerator类的静态方法getInstance来实现。所用的DES算法没有模式和填充模型。你同样可以(在getInstance(""))传入DES/ECB/PKCS5Padding来指明模式(ECB)和填充模式(PKCS5Padding),也可以传入另外一个参数指明所用的Provider,不过这是可选的。在获得密钥生成器的实例后,通过调用其generateKey()方法,我们就可以获得一个密钥。
 
生成密码(Cipher):
生成Cipher的过程跟生成密钥类似,需要调用Cipher类的getInstance方法,参数要跟生成密钥时用的参数保持一致。
代码E说明了如果操作。
代码E: DESCipherGenerator.java
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.Security;
import java.security.NoSuchAlgorithmException;
public class DESCipherGenerator {
    public static void main(String[] args) {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        try{
            Cipher cipher = Cipher.getInstance("DES");
            System.out.println("Cipher provider: " + cipher.getProvider());
            System.out.println("Cipher algorithm: " + cipher.getAlgorithm());
        }catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }
}
 
加解密数据
加密是对字节的,所以保密行比较高,当你准备好了密钥和Cipher时,你已经做好了加密的准备。要注意的是,同一个算法要用相同的密钥和密码(Cipher)。比如说,你不能用DESsede的密钥,用DES的密码。Cipher对象用同一个方法对数据进行加密和解密,所有你要首先初时化,让他知道你要干什么:
 
cipher.init(Cipher.ENCRYPT_MODE, key);
 
这就将初始化Cipher类,以准备好去加密数据。最简单的加密方法就是对传入的字节数组调用doFinal方法:
byte[] data = “Hello World!”.getBytes();
byte[] result = cipher.doFinal(data);
 
以下是详细的代码
代码F: DESCryptoTest.java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.BadPaddingException;
import java.security.Key;
import java.security.Security;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
public class DESCryptoTest {
    public static void main(String[] args) {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());
        try {
            KeyGenerator kg = KeyGenerator.getInstance("DES");
            Key key = kg.generateKey();
            Cipher cipher = Cipher.getInstance("DES");
            byte[] data = "Hello World!".getBytes();
           
            System.out.println("Original data : " + new String(data));
           
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] result = cipher.doFinal(data);
           
            System.out.println("Encrypted data: " + new String(result));
           
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] original = cipher.doFinal(result);
           
            System.out.println("Decrypted data: " + new String(original));
        }catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }catch (InvalidKeyException e) {
            e.printStackTrace();
        }catch (IllegalStateException e) {
            e.printStackTrace();
        }catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }catch (BadPaddingException e) {
            e.printStackTrace();
        }
    }
}
 
总结
JCE是个功能强大的API,提供了众多的加密方法和其他安全相关的属性,在这里,已经讲解了怎样动态和静态安装JCE,并用DES对一段简单的信息进行了加密和解密。以上内容仅对对称(Symmetric)加密举例介绍,对于非对称及其他数字签名等,可参照Java的文档。 
 

Posted in Play Java | Leave a comment

Maven设置Local Repository存放自行开发的Jar

在Maven框架里, 若在Local Repository找不到pom.xml所定义的jar,那会首先到预设的repo2.maven.org/maven2去找,或从pom.xml中设置的其它的Repository所指定的URL找。
 
如何在Local Repository设定自行开发的Jar?
首先,找到Local Repository的位置,如我的在%USERPROFILE%\.m2\repository,假设我的JAR叫xyz.jar,group ID也是xyz好了,那么就在Local Repository目录下建立xyz/xyz/1.0的子目录,把xyz.jar改名为xyz-1.0.jar放到最底层子目录,並在该目录下新增一个xyz-1.0.pom的maven配置文件,內容如下(<?xml version="1.0">可不用设):
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>xyz</groupId>
  <artifactId>xyz</artifactId>
  <version>1.0</version>
</project>
接下来,要在引用xyz.jar的Project里的pom.xml文件添加如下dependency:
<dependency>
    <groupId>xyz</groupId>
    <artifactId>xyz</artifactId>
    <version>1.0</version>
</dependency>
  这样的话,不只可以使用普通的测试,如JUnit Test,也可以使用mvn test来测试(当然也要dependency JUnit)。
Posted in Maven | Leave a comment

实时更改Quartz配置

 
B/S下的Quartz应用配置涉及到三个文件:
1.配置启动Quartz服务的web.xml;
2.配置Quartz运行时环境的quartz.properties;
3.配置任务明细的任务单–job.xml.
(PS:后两个配置文件的名称可变. )
 
问题1.
应用服务器启动状态下,当对任务进行了修改时(即修改了job.xml中的任务明细),Quartz无法响应这种变化.也就是说,Quartz并没有进行"有状态"作业!
需求:
无论是修改了任务明细中的参数列表–JobDataMap,或是CronExpression中的定时表达式,都应该立即做出响应,并按照新的配置参数去执行这个任务.
解决: 
在quartz.properties中加入下面两行配置即可:
#自动扫描任务单并发现改动的时间间隔,单位为秒
org.quartz.plugin.jobInitializer.scanInterval = 10
#覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
 
问题2:
测试任务单变更自检的问题时,突然发现,当通过web页面提供的任务配置接口进行报表订阅任务的新增、修改等操作时,由于job.xml是在classes目录下,所以tomcat会进行自动的reload(hot deploy).这看起来好象没什么问题,但在实际的应用环境下却非常危险,因为web.xml中配置的随tomcat启动而启动的程序都会reload.可能这些程序很简单,并不会产生什么问题,但我们的软件则不同,随tomcat的启动,会有很多服务被启动并进行着极为复杂的操作,所以classes目录下的程序配置项都不会轻易修改.即使是修改,那也会重新启动tomcat使服务正常运转. 修改了任务单后发现某个服务出现了Illegal Access问题.
解决:
Quartz的两个主要配置文件名称可变,而且是从web.xml加载quartz.properties,又从quartz.properties找到job.xml,那么job.xml没必要放到classes目录下。拿到web目录下,就放到config/job.xml,然后修改quartz.properties文件,将文件指向修改成绝对路径:
org.quartz.plugin.jobInitializer.fileName = D:/tomcat/webapps/report/config/job.xml
job.xml无论如何变更,tomcat都不会reload,因为它已经不在classes目录下了。
 
问题3:
在Debug过程中,发现Quartz的一个小问题,可能会对资源造成无谓的占用,那就是当一个任务从job.xml中被删除时,Quartz是不会响应这种减少任务的改变且此任务的进程仍然被占用,而且任务还会被执行。
解决:
可以考虑在JobDataMap中增加是否执行的配置项,即使任务会执行,但根据这种配置项,仍然可以拒绝下一步的操作.当然了,修改CronExpression使之成为一个永远不会执行到的时间也是一个办法.
 
问题4:
如何使Quartz加载多个job.xml
解决:
实现SchedulerPlugin接口并提供多任务文件加载功能,将会是解决这个事情的好方法.
 
Posted in Quartz 编程 | Leave a comment

如何知道方法的调用者

比如有2个类:ClassA,ClassB
ClassA的一个实例调用了ClassB的一个方法,通过动态代理可以截取这个调用,但是不能获得是谁调用了ClassB的方法,如何才能截取到呢,
下面给个思路.
java代码:
public static String getCaller(){
    int i;
    StackTraceElement stack[] = (new Throwable()).getStackTrace();
    for (i=0; i < stack.length; i++) {
      StackTraceElement ste=stack;
      System.out.println(ste.getClassName()+"."+ste.getMethodName()+"(…)");
      System.out.println(i+"–"+ste.getMethodName());
      System.out.println(i+"–"+ste.getFileName());
      System.out.println(i+"–"+ste.getLineNumber());
    }
  }
线程运行栈信息的获取
一、问题的引入
我们在Java程序中使用日志功能(JDK Log或者Log4J)的时候,会发现Log系统会自动帮我们打印出丰富的信息,格式一般如下:
[运行时间] [当前类名] [方法名]
INFO: [用户信息]
具体例子如Tomcat启动信息:
Jul 9, 2004 11:22:41 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on port 8080
看起来这毫无神奇之处,不就是打印了一条信息吗?但如果好奇心重一点,追寻后面的实现原理,会发现这确实很神奇。
上面的Log信息的[当前类名] [方法名]部分 不是用户自己添加的,而是Log系统自动添加的。这意味着Log系统能够自动判断当前执行语句是哪个类的哪个方法。这是如何做到的?
我们翻遍java.lang.reflection package,幻想着找到一个Statement语句级别的Reflection类,通过这个Statement对象获得Method,然后通过这个Method获得declared Class。这不就获得对应的Class和Method信息了吗?这是一个不错的构想,但也只能是一个构想;因为没有这个Statement对象。
再想一下。对了,Java不是有一个Thread类吗?Thread.currentThread()方法获取当前线程,我们能不能通过这个当前线程获取当前运行的Method和Class呢?很遗憾,如果你还在用JDK1.4或以下版本,那么找不到这样的方法。(JDK1.5的情况后面会讲)
再想一下。对了,我们都有很深刻的印象,当系统抛出Exception的时候,总是打印出一串的信息,告诉我们Exception发生的位置,和一层一层的调用关系。我们也可以自己调用Exception的printStackTrace()方法来打印这些信息。这不就是当前线程运行栈的信息吗?找到了,就是它。
Exception的printStackTrace()方法继承自Throwable,那么我们来看一下,JDK的Throwable的printStackTrace()方法是如何实现的。
我们先来看JDK1.3的源代码,会发现Throwable.printStackTrace()方法调用了一个native printStackTrace0()方法。我们找不到任何线索,可以用在我们自己的Java代码中。
那怎么办?Throwable.printStackTrace()的输出结果字符串里面不是包含了当前线程运行栈的所有信息吗?我们可以从这个字符串中抽取自己需要的信息。JDK1.3的时代,也只能这么做了。
二、Log4J 1.2的相关实现
Log4J 1.2是JDK1.3时代的作品。我们来看相关源代码。
java代码:
/**
Instantiate location information based on a Throwable. We
expect the Throwable t, to be in the format
 
java.lang.Throwable

at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
at callers.fully.qualified.className.methodName(FileName.java:74)
*/
public LocationInfo(Throwable t, String fqnOfCallingClass) {
String s;

t.printStackTrace(pw);
s = sw.toString();
sw.getBuffer().setLength(0);
…. // 这里的代码省略
}
 
这里我们可以看到整体的实现思路。
首先,t.printStackTrace(pw); 获得stack trace字符串。这个t是 new Throwable()的结果。用户程序调用Log4J方法之后,Log4J自己又进行了4次调用,然后才获得了 t = new Throwable() :
at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
那么,往下走4行,就可以回到用户程序本身的调用信息:
at callers.fully.qualified.className.methodName(FileName.java:74)
这一行里面,类名、方法名、文件名、行号等信息全有了。解析这一行,就可以获得需要的所有信息。
三、JDK1.4 Log的相关实现
Log4J大获成功,Sun决定在JDK1.4中引入这个Log功能。
为了免去解析StackTrace字符串的麻烦,JDK1.4引入了一个新的类,StackTraceElement。
public final class StackTraceElement implements java.io.Serializable {
// Normally initialized by VM (public constructor added in 1.5)
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
可以看到,恰好包括类名、方法名、文件名、行号等信息。
我们来看JDK1.4 Log的相关实现。
LocationInfo.java 的infoCaller方法(推算调用者)
// Private method to infer the caller’s class and method names
private void inferCaller() {

// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();
// First, search back to a method in the Logger class.
…. // 这里的代码省略
// Now search for the first frame before the "Logger" class.
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (!cname.equals("java.util.logging.Logger"))
// We’ve found the relevant frame.
… // 这里的代码省略
}
// We haven’t found a suitable frame, so just punt. This is
// OK as we are only committed to making a "best effort" here.
}
从注释中就可以看出实现思路。过程和Log4J异曲同工。只是免去了解析字符串的麻烦。
四、Log4J 1.3 alpha的相关实现
既然JDK1.4中引入了StackTraceElement类,Log4J也要与时俱进。LocationInfo类也有了相应的变化。
/**
Instantiate location information based on a Throwable. We
expect the Throwable t, to be in the format
 
java.lang.Throwable

at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
at org.apache.log4j.Category.callAppenders(Category.java:131)
at org.apache.log4j.Category.log(Category.java:512)
at callers.fully.qualified.className.methodName(FileName.java:74)
 
However, we can also deal with JIT compilers that "lose" the
location information, especially between the parentheses.
*/
public LocationInfo(Throwable t, String fqnOfInvokingClass) {
if(PlatformInfo.hasStackTraceElement()) {
StackTraceElementExtractor.extract(this, t, fqnOfInvokingClass);
} else {
LegacyExtractor.extract(this, t, fqnOfInvokingClass);
}
}
可以看到,Log4J首先判断Java平台是否支持StackTraceElement,如果是,那么用StackTraceElementExtractor,否则使用原来的LegacyExtractor。
下面来看StackTraceElementExtractor.java
/**
* A faster extractor based on StackTraceElements introduced in JDK 1.4.
*
* The present code uses reflection. Thus, it should compile on all platforms.
*
* @author Martin Schulz
* @author Ceki G&lc&
*
*/
public class StackTraceElementExtractor {
protected static boolean haveStackTraceElement = false;
private static Method getStackTrace = null;
private static Method getClassName = null;
private static Method getFileName = null;
private static Method getMethodName = null;
private static Method getLineNumber = null;
…. // 以下代码省略
可以看到,Log4J 1.3仍然兼容JDK1.3,而且为JDK1.4也做了相应的优化。
五、JDK1.5的Thread Stack Trace
JDK1.5在Thread类里面引入了getStackTrace()和getAllStackTraces()两个方法。这下子,我们不用 (new Throwable()).getStackTrace ();可以调用
Thread.getCurrentThread().getStackTrace()来获得当前线程的运行栈信息。不仅如此,只要权限允许,还可以获得其它线程的运行栈信息。
/**
* Returns an array of stack trace elements representing the stack dump
* of this thread. This method will return a zero-length array if
* this thread has not started or has terminated.
* If the returned array is of non-zero length then the first element of
* the array represents the top of the stack, which is the most recent
* method invocation in the sequence. The last element of the array
* represents the bottom of the stack, which is the least recent method
* invocation in the sequence.
*
*
If there is a security manager, and this thread is not
* the current thread, then the security manager’s
* checkPermission method is called with a
* RuntimePermission("getStackTrace") permission
* to see if it’s ok to get the stack trace.
*
*
Some virtual machines may, under some circumstances, omit one
* or more stack frames from the stack trace. In the extreme case,
* a virtual machine that has no stack trace information concerning
* this thread is permitted to return a zero-length array from this
* method.
*
* @return an array of StackTraceElement,
* each represents one stack frame.
*
* @since 1.5
*/
public StackTraceElement[] getStackTrace() {
if (this != Thread.currentThread()) {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
}
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
Thread[] threads = new Thread[1];
threads[0] = this;
StackTraceElement[][] result = dumpThreads(threads);
return result[0];
}
/**
* Returns a map of stack traces for all live threads.
*
* @since 1.5
*/
public static Map getAllStackTraces() {
// check for getStackTrace permission
// Get a snapshot of the list of all threads
}
六、总结
从总的发展趋势来看,JDK不仅提供越来越多、越来越强的功能,而且暴露给用户的控制方法越来越多,越来越强大。
 
关于直接获取调用类名的方法。
我们来看sun.reflect.Reflection的getCallerClass()方法的说明。
java代码:
/** Returns the class of the method realFramesToSkip
frames up the stack (zero-based), ignoring frames associated
with java.lang.reflect.Method.invoke() and its implementation.
The first frame is that associated with this method, so
getCallerClass(0) returns the Class object for
sun.reflect.Reflection. Frames associated with
java.lang.reflect.Method.invoke() and its implementation are
completely ignored and do not count toward the number of "real"
frames skipped. */
public static native Class getCallerClass(int realFramesToSkip);
这是一个native方法。原理也是根据StackFrame(运行栈)获取相应类的信息。这个方法直接返回一个Class名字,直接有效。参数realFramesToSkip用来选取你所需要的Stack层次,所以,你可以用这个方法获得任何层次的上的调用类名。
Posted in Play Java | Leave a comment

Quartz 在 Spring 中如何动态配置时间(6)

 

在simpleService里面注入一个继承HibernateDaoSupport的类,这个继承HibernateDaoSupport的类也必须实现序列化接口,simpleService类被序列化保存到数据库表 qrtz_job_details的job_class_name字段中,quartz在运行时会读取qrtz_job_details表中的 job_class_name将其反序列化。这也是为什么simpleService和其中注入各属性需要实现Serializable序列化接口的原因,所以你每次修改simpleService类或者其中的继承HibernateDaoSupport的类都要删除 qrtz_job_details表对应的job记录,否则可能会出现空指针异常,因为你如果你没有删除qrtz_job_details表中的记录,你修改的东东并不会自动更新到qrtz_job_details中,你用的还是原来旧版本的simpleService类。 

在simpleService里面注入一个继承HibernateDaoSupport的类,这个继承HibernateDaoSupport的类也必须实现序列化接口,simpleService类被序列化保存到数据库表 qrtz_job_details的job_class_name字段中,quartz在运行时会读取qrtz_job_details表中的 job_class_name将其反序列化。这也是为什么simpleService和其中注入各属性需要实现Serializable序列化接口的原因,所以你每次修改simpleService类或者其中的继承HibernateDaoSupport的类都要删除 qrtz_job_details表对应的job记录,否则可能会出现空指针异常,因为你如果你没有删除qrtz_job_details表中的记录,你修改的东东并不会自动更新到qrtz_job_details中,你用的还是原来旧版本的simpleService类。 

你的这个问题在我另一篇文章《Quartz任务监控管理》有人提到过,你也可以到http://www.javaeye.com/topic/441951?page=1看看。 

要做的并不是简单的从数据库读取cronExpression,而是能够通过前端(比如Web页面)的修改并且不需要重启服务的情况下就可以动态修改配置任务调度时间,并且对于quartx的数据持久化是透明的,只需在数据库增加12张表,修改一下quartx.properties文件的配置,其它并不需要你做些什么额外的斯工作。 

JdbcPlaceholderConfigurer继承自PropertyPlaceholderConfigurer 
,将原来配置在一个properties文件中的内容转移到数据库而己。JdbcPlaceholderConfigurer只是应用启动时简单的将cronExpression从数据库读取出来,每次修改完数据库后就都需要重启服务,新的修改才会生效。其实JdbcPlaceholderConfigurer还是一种静态配置,只是将原来写在 

引用
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean"> 
<property name="jobDetail" ref="jobDetail" /> 
<property name="cronExpression" value="0 0/50 * ? * * *" /> 
</bean>

中的cronExpression写到另外一个地方:数据库。 

simpleService和其中注入各属性需要实现Serializable序列化接口,你的BakcupDao继承自HibernateDaoSupport虽然也实现了序列化接口,但是HibernateDaoSupport里的HibernateTemplate并没有实现序列化接口,所以你取得的HibernateTemplate永远为null。因此获取HibernateTemplate必须换一种方式,你的BakcupDao不能继承自HibernateDaoSupport。HibernateTemplate没有实现序列化接口,而SessionFactory是实现序列化接口的,在bakcupDao注入SessionFactory,通过SessionFactory获取HibernateTemplate。 

你的bakcupDao可以这样写 

  1. import java.io.Serializable;  
  2.   
  3. import org.hibernate.SessionFactory;  
  4. import org.slf4j.Logger;  
  5. import org.slf4j.LoggerFactory;  
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Repository;  
  8.   
  9. import com.sundoctor.example.service.SimpleService;  
  10.   
  11. @Repository("bakcupDao")  
  12. public class BakcupDao implements Serializable {  
  13.   
  14.     /** 
  15.      *  
  16.      */  
  17.     private static final long serialVersionUID = 1L;      
  18.     private SessionFactory sessionFactory;    
  19.   
  20.     @Autowired  
  21.     public void setSessionFactory(SessionFactory sessionFactory) {  
  22.         this.sessionFactory = sessionFactory;         
  23.     }  
  24.   
  25.   
  26.     public boolean backupDateabase(String dbname, String bfname) {  
  27.         final String dbName = dbname;  
  28.         final String bfname1 = bfname;  
  29.             HibernateTemplate  hibernateTemplate = new HibernateTemplate(sessionFactory);  
  30.         return (Boolean)hibernateTemplate.execute(new HibernateCallback() {  
  31.             public Object doInHibernate(Session session) {  
  32.                 boolean flag = true;  
  33.                 PreparedStatement pstmt = null;  
  34.   
  35.                 try {  
  36.                     pstmt = session.connection().prepareStatement("{call p_Backup_Or_Restore(?,?,?)}");  
  37.                     pstmt.setString(1, bfname1);  
  38.                     pstmt.setString(2, dbName);  
  39.                     pstmt.setInt(31);  
  40.                     pstmt.execute();  
  41.                     System.out.println("数据库已备份");  
  42.                 } catch (Exception e) {  
  43.                     flag = false;  
  44.                     e.printStackTrace();  
  45.                 }  
  46.                 return flag;  
  47.             }  
  48.         });  
  49.     }  
  50. }     

 

Posted in Quartz 编程 | Leave a comment

Quartz 在 Spring 中如何动态配置时间(5)

首先实现多个JobDeatail并注册,比如: 

引用
<bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailBean"> 
<property name="jobClass"> 
<value>com.sundoctor.example.service.MyQuartzJobBean1</value> 
</property> 

<bean id="jobDetail2" class="org.springframework.scheduling.quartz.JobDetailBean"> 
<property name="jobClass"> 
<value>com.sundoctor.example.service.MyQuartzJobBean2</value> 
</property> 

<bean id="jobDetail3" class="org.springframework.scheduling.quartz.JobDetailBean"> 
<property name="jobClass"> 
<value>com.sundoctor.example.service.MyQuartzJobBean3</value> 
</property> 
 

其次将多个JobDeatail放到一个HashMap中 

引用
<util:map id = "jobDeatailMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="org.springframework.scheduling.quartz.JobDetailBean"> 
<entry key="jobDetail1" ref="jobDetail1"/> 
<entry key="jobDetail2" ref="jobDetail2"/> 
<entry key="jobDetail3" ref="jobDetail3"/> 
</util:map>

然后在SchedulerService 注入jobDeatailMap 

  1. @Service("schedulerService")    
  2. public class SchedulerServiceImpl implements SchedulerService {    
  3.      
  4.      private Scheduler scheduler;    
  5.      private Map<String,JobDetailBean> jobDeatailMap;    
  6.      
  7.      @Autowired    
  8.      public void setJobDeatailMap(@Qualifier("jobDeatailMap") Map<String,JobDetailBean> jobDeatailMap) {    
  9.          this.jobDeatailMap = jobDeatailMap;    
  10.      }    
  11.      @Autowired    
  12.      public void setScheduler(@Qualifier("quartzScheduler") Scheduler scheduler) {    
  13.          this.scheduler = scheduler;    
  14.      }   
  15. …  

最后,修改SchedulerServiceImpl中的schedule方法,增加以jobDeatailMap KEY名字为参数: 

  1.      @Override    
  2.      public void schedule(String jobDetailName,String name, CronExpression cronExpression) {    
  3.          if (name == null || name.trim().equals("")) {    
  4.              name = UUID.randomUUID().toString();    
  5.          }    
  6.      
  7.          //这个时候JobDetail根据jobDetailName从jobDeatailMap获取  
  8.          JobDetail jobDetail = jobDeatailMap.get(jobDetailName);  
  9.          try {    
  10.              scheduler.addJob(jobDetail, true);    
  11.      
  12.              CronTrigger cronTrigger = new CronTrigger(name, Scheduler.DEFAULT_GROUP, jobDetail.getName(),    
  13.                      Scheduler.DEFAULT_GROUP);    
  14.              cronTrigger.setCronExpression(cronExpression);    
  15.              scheduler.scheduleJob(cronTrigger);    
  16.              scheduler.rescheduleJob(name, Scheduler.DEFAULT_GROUP, cronTrigger);    
  17.          } catch (SchedulerException e) {    
  18.              throw new RuntimeException(e);    
  19.          }    
  20.      }  
  21. 其它多态方法一样修改,增加jobDetailName参数。      

调用时,传不同的jobDetailName参数就可以调用不用的JobDetail。 

  1. SchedulerService schedulerService = (SchedulerService)springContext.getBean("schedulerService");   
  2.   
  3. schedulerService.schedule("jobDetail1","审计任务","0/10 * * ? * * *");     
  4.   
  5. schedulerService.schedule("jobDetail2","发放任务","0/10 * * ? * * *");   
  6.   
  7. schedulerService.schedule("jobDetail3","AAA任务","0/10 * * ? * * *");   

 

其实很多时候只需要一个JobDetail就可以了,也可以达到多个JobDetail一样的效果,一个JobDetail的时候可以在Trigger名称上做扩展,可以在调度任务时给Trigger名称加上不同的前缀或后缀,比如Trigger名称增加一个前缀参数, 

  1. @Override    
  2. public void schedule(String name, String prefix ,CronExpression cronExpression) {    
  3.     if (name == null || name.trim().equals("")) {    
  4.         name = UUID.randomUUID().toString();    
  5.     }    
  6.   
  7.     try {    
  8.         scheduler.addJob(jobDetail, true);    
  9.           
  10.         //给Trigger名秒加上前缀  
  11.         name = prefix + name;  
  12.   
  13.         CronTrigger cronTrigger = new CronTrigger(name, Scheduler.DEFAULT_GROUP, jobDetail.getName(),    
  14.                 Scheduler.DEFAULT_GROUP);    
  15.         cronTrigger.setCronExpression(cronExpression);    
  16.         scheduler.scheduleJob(cronTrigger);    
  17.         scheduler.rescheduleJob(name, Scheduler.DEFAULT_GROUP, cronTrigger);    
  18.     } catch (SchedulerException e) {    
  19.         throw new RuntimeException(e);    
  20.     }    
  21. }    

然后在QuartzJobBean中的executeInternal方法取到Trigger名秒,然后根据其前缀或后缀调用不同的业务逻辑 

  1. public class MyQuartzJobBean extends QuartzJobBean {    
  2.     
  3.     private SimpleService simpleService;    
  4.         
  5.     public void setSimpleService(SimpleService simpleService) {    
  6.         this.simpleService = simpleService;    
  7.     }    
  8.     
  9.     @Override    
  10.     protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException {    
  11.         Trigger trigger = jobexecutioncontext.getTrigger();   
  12.         //取得Trigger名称,判断名称前缀或后缀调用不同的业务逻辑  
  13.         String triggerName = trigger.getName();    
  14.         if(tirggerName …){  
  15.             simpleService.testMethod(triggerName);    
  16.         }else if(tirggerName …){  
  17.             simpleService.testMethod2(triggerName);   
  18.         }else{  
  19.             …  
  20.         }  
  21.     }    
  22.     
  23. }  
Posted in Quartz 编程 | Leave a comment

Quartz 在 Spring 中如何动态配置时间(4)

五、实现自己的org.quartz.JobDetail 
在上一步中SchedulerServiceImpl需要注入org.quartz.JobDetail,在以前的静态配置中 

引用
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> 
<property name="targetObject" ref="simpleService" /> 
<property name="targetMethod" value="testMethod" /> 
</bean>
中使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean。在这里使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean。会报 
引用

Caused by: java.io.NotSerializableException: Unable to serialize JobDataMap for insertion into database because the value of property ‘methodInvoker’ is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean 
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.serializeJobData(StdJDBCDelegate.java:3358) 
at org.quartz.impl.jdbcjobstore.StdJDBCDelegate.insertJobDetail(StdJDBCDelegate.java:515) 
at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1102) 
… 11 more 
异常,google了一下,没有找到解决方法。所以在这里不能使用org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean。,不能pojo了,需要使用org.springframework.scheduling.quartz.JobDetailBean和org.springframework.scheduling.quartz.QuartzJobBean实现自己的QuartzJobBean,如下: 
  1. package com.sundoctor.example.service;  
  2.   
  3. import org.quartz.JobExecutionContext;  
  4. import org.quartz.JobExecutionException;  
  5. import org.quartz.Trigger;  
  6. import org.springframework.scheduling.quartz.QuartzJobBean;  
  7.   
  8. public class MyQuartzJobBean extends QuartzJobBean {  
  9.   
  10.     private SimpleService simpleService;  
  11.       
  12.     public void setSimpleService(SimpleService simpleService) {  
  13.         this.simpleService = simpleService;  
  14.     }  
  15.   
  16.     @Override  
  17.     protected void executeInternal(JobExecutionContext jobexecutioncontext) throws JobExecutionException {  
  18.         Trigger trigger = jobexecutioncontext.getTrigger();  
  19.         String triggerName = trigger.getName();       
  20.         simpleService.testMethod(triggerName);  
  21.     }  
  22.   
  23. }  

MyQuartzJobBean继承org.springframework.scheduling.quartz.QuartzJobBean,注入的SimpleService如下: 

  1. package com.sundoctor.example.service;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. import org.slf4j.Logger;  
  6. import org.slf4j.LoggerFactory;  
  7. import org.springframework.stereotype.Service;  
  8.   
  9. @Service("simpleService")  
  10. public class SimpleService implements Serializable{  
  11.       
  12.     private static final long serialVersionUID = 122323233244334343L;  
  13.     private static final Logger logger = LoggerFactory.getLogger(SimpleService.class);  
  14.       
  15.     public void testMethod(String triggerName){  
  16.         //这里执行定时调度业务  
  17.         logger.info(triggerName);  
  18.     }  
  19.       
  20.     public void testMethod2(){  
  21.         logger.info("testMethod2");  
  22.     }  
  23. }  
SimpleService主要执行定时调度业务,在这里我只是简单打印一下log日志。SimpleService需要实现java.io.Serializable接口,否则会报 
引用
Caused by: java.io.InvalidClassException: com.sundoctor.example.service.SimpleService; class invalid for deserialization 
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:587) 
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1583) 
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1496) 
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1732) 
… 64 more
异常。 

配置applicationContext-quartz.xml文件: 

引用
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"&gt; 

<beans> 
<bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> 
<property name="dataSource">  
<ref bean="dataSource" />  
</property> 
<property name="applicationContextSchedulerContextKey"  value="applicationContextKey" /> 
<property name="configLocation" value="classpath:quartz.properties"/> 
</bean> 

<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailBean"> 
<property name="jobClass"> 
<value>com.sundoctor.example.service.MyQuartzJobBean</value> 
</property> 

<property name="jobDataAsMap"> 
<map> 
<entry key="simpleService"> 
<ref bean="simpleService" /> 
</entry> 
</map> 
</property> 

</bean>
</beans> 

quartzScheduler中没有了 

引用
<property name="triggers"> 
<list> 
…      
</list> 
/property> 
配置,通过SchedulerService动态加入CronTrigger或SimpleTrigger。 

在红色的 

引用

<property name="jobDataAsMap"> 
<map> 
<entry key="simpleService"> 
<ref bean="simpleService" /> 
</entry> 
</map> 
</property> 
中需要注入调度业务类,否则会报空指指错误。 

dataSource:项目中用到的数据源,里面包含了quartz用到的12张数据库表; 
applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中把spring上下文以key/value的方式存放在了quartz的上下文中了,可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文; 
configLocation:用于指明quartz的配置文件的位置,如果不用spring配置quartz的话,本身quartz是通过一个配置文件进行配置的,默认名称是quartz.properties,里面配置的参数在quartz的doc文档中都有介绍,可以调整quartz,我在项目中也用这个文件部分的配置了一些属性,代码如下: 

引用
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.misfireThreshold = 60000 

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

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 
#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate 
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate 
#org.quartz.jobStore.useProperties = true 
org.quartz.jobStore.tablePrefix = QRTZ_  
org.quartz.jobStore.isClustered = false  
org.quartz.jobStore.maxMisfiresToHandleAtATime=1 

这里面没有数据源相关的配置部分,采用spring注入datasource的方式已经进行了配置。 

六、测试 
运行如下测试类 

  1. package com.sundoctor.example.test;  
  2.   
  3. import java.text.ParseException;  
  4. import java.text.SimpleDateFormat;  
  5. import java.util.Date;  
  6.   
  7. import org.springframework.context.ApplicationContext;  
  8. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  9.   
  10. import com.sundoctor.quartz.service.SchedulerService;  
  11.   
  12. public class MainTest {  
  13.   
  14.     /** 
  15.      * @param args 
  16.      */  
  17.     public static void main(String[] args) {  
  18.         ApplicationContext springContext = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext.xml","classpath:applicationContext-quartz.xml"});  
  19.         SchedulerService schedulerService = (SchedulerService)springContext.getBean("schedulerService");  
  20.           
  21.         //执行业务逻辑…  
  22.           
  23.         //设置调度任务  
  24.         //每10秒中执行调试一次  
  25.         schedulerService.schedule("0/10 * * ? * * *");   
  26.           
  27.         Date startTime = parse("2009-06-01 22:16:00");  
  28.         Date endTime =  parse("2009-06-01 22:20:00");  
  29.           
  30.         //2009-06-01 21:50:00开始执行调度  
  31.         schedulerService.schedule(startTime);  
  32.   
  33.         //2009-06-01 21:50:00开始执行调度,2009-06-01 21:55:00结束执行调试  
  34.         //schedulerService.schedule(startTime,endTime);  
  35.           
  36.         //2009-06-01 21:50:00开始执行调度,执行5次结束  
  37.         //schedulerService.schedule(startTime,null,5);  
  38.   
  39.         //2009-06-01 21:50:00开始执行调度,每隔20秒执行一次,执行5次结束  
  40.         //schedulerService.schedule(startTime,null,5,20);  
  41.           
  42.         //等等,查看com.sundoctor.quartz.service.SchedulerService          
  43.     }  
  44.       
  45.     private static Date parse(String dateStr){  
  46.         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  47.         try {  
  48.             return format.parse(dateStr);  
  49.         } catch (ParseException e) {  
  50.             throw new RuntimeException(e);  
  51.         }  
  52.     }  
  53.   
  54. }  
输出 
引用
[2009-06-02 00:08:50]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:10:20]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:10:30]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:10:40]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:10:50]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:11:00]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
[2009-06-02 00:11:10]INFO  com.sundoctor.example.service.SimpleService(line:17) -2059c26f-9462-49fe-b4ce-be7e7a29459f 
这样只是简单的将quartz trigger名称打印出来。 

这样通过SchedulerService就可以动态配置调度时间。其实SchedulerService 还可扩展,比如可以注入多个JobDetail,调度不同的JobDetail。 

Posted in Quartz 编程 | Leave a comment