Java堂  


JRebel运行缓慢或提示内存不足的解决方法

Filed under: JavaPlateform — Jet @ 5:33 下午
Tags:
原文出处: JRebel运行缓慢或提示内存不足的解决方法
作者: Jet Mah from Java堂
声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!


JRebel是一个Java类热启动的工具,使Java开发(特别是Web开发)可以实现像PHP开发那样即时刷新的效果,这样再也不用修改java文件之后频繁的去重启Web Server了,用起来非常的顺手。

最近JRebel好像更新也非常频繁,8月份刚发布3.0版本,11月份就发布了3.5版本,而且这个新版本用下来非常给力,针对了spring增加了许多扩展(默认是打开的),比如可以实时更新所有Spring的配置文件等等,手痒痒的同学可以下载下来试试。BTW,我用的是正式版,:)

安装的方法也非常简单,就在vm后面增加一个-noverify 参数:

  1. -noverify -javaagent:E:\UsualTools\JRebel\jrebel.jar

当然还是记得将上面的路径修改成你自己本机JRebl的存放路径。

上面的都不是本文的重点,这里需要说一下的就是,或许3.5版本增加的功能特性太多的缘故(特别是针对Spring),刚开始启动的时候速度非常的慢,开始的时候我怀疑是spring插件的问题,于是使用 -Djrebel.spring_plugin=false将spring插件关闭了,之后启动的速度可以接受了,但是运行程序的时候速度非常非常的慢,点一个按钮要等半天。如果不使用JRebel的话程序运行正常,所以排除了程序本身的问题。

刚开始一直在怀疑是不是新版本的问题,因为用2.x和3.0的时候都很正常,所以在这方面查了很多资料,都没有发现好的解决办法。后来,无意中发现程序出现了“java.lang.OutOfMemoryError: PermGen space”的错误,突然才想到是否是给JRebel分配的内存太小了,于是使用下面的参数增加了内存:

  1. -noverify -javaagent:E:\UsualTools\JRebel\jrebel.jar -Xmx512M -Xms512M -XX:MaxPermSize=1024m

然后在运行的时候发现速度是非一般的快啊,使用JRebel的spring插件之后启动速度也非常的块了,看来就是这个问题,后来在JRebel官方上的Q&A上面也得到了印证。

需要说明一点的是,如果上面的参数无法启动,需要将内存数设置小一些;还有,增加Eclipse的内存对JRebl是没有任何效果的,虽然现在想起来和可笑,但是当时在这个方面也浪费了不少时间。BTW,Eclipse3.3+内置了一个内存查看的工具,可以在“Windows”-”Preferences”中,选择左边的”General”,然后选择右边的”Show heap status”。

参考资料:
I’m getting Java.lang.OutOfMemoryError: PermGen space!??
Javarebel启动程序java.lang.OutOfMemoryError: PermGen space问题的解决

Spring MVC中修改校验的异常信息

Filed under: JavaPlateform — Jet @ 4:58 下午
Tags:
原文出处: Spring MVC中修改校验的异常信息
作者: Jet Mah from Java堂
声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

使用一个例子来说明一下,Spring使用的是3.0.x:

  1. public class Account {
  2.     @Pattern(regexp = "[a-z0-9]{3,50}")
  3.     private String name;
  4.  
  5.     @NotNull
  6.     @NumberFormat(style=Style.CURRENCY)
  7.     private BigDecimal balance = new BigDecimal("1000");
  8.  
  9.     @DateTimeFormat(style="S-")
  10.     @Future
  11.     private Date renewalDate = new Date(new Date().getTime() + 31536000000L);
  12.  
  13.     // 省略 getter & setter...
  14. }

接下来使用注解创建一个Controller:

  1. @Controller
  2. @RequestMapping(value="/account")
  3. public class AccountController {
  4.  
  5.     /**
  6.      * 如果view采用JSP的话<code>@ModelAttribute</code>注解可以省略,但是如果是使用Velocity或FreeMarker
  7.      * 的话必须要加上,而且<code>@Valid</code>注解和后面的BindingResult类型的参数之间不能有其他参数。
  8.      */
  9.     @RequestMapping(method=RequestMethod.POST)
  10.     public String create(@Valid @ModelAttribute("account") Account account, BindingResult result) {
  11.         if (result.hasErrors()) {
  12.             return "account/createForm";
  13.         }
  14.         // ...
  15.         return "redirect:/account/" + account.getId();
  16.     }
  17.  
  18. }

另外还有一个view文件、配置文件等等,具体的可以查看Spring MVC Basic Sample

OK,接下来说重点。我参考上面的Spring MVC Basic Sample中的做法也做了一个类似的表单,在表单中的balance中输入非数字的时候旁边的错误信息会出现下面的异常信息:

Failed to convert property value of type java.lang.String to required type java.math.BigDecimal for property balance; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value “eff” from type java.lang.String to type java.math.BigDecimal; nested exception is java.lang.IllegalArgumentException: Unable to parse eff

这个就很郁闷了,明显是格式转换出错,但是列出来一堆的异常信息,对用户来说非常不友好。于是开始在Google中搜索也没有找到比较好的说法,后来运行了Spring MVC Basic Sample,不显示上面的异常信息,而只是显示一句话:”could not be parsed“,这个效果正是我们所期待的。于是我将例子中的所有spring配置文件、Bean信息、Controller都做了对比,甚至JSP模板都一样,但是一到我自己的项目中就会出现那个异常,当时非常郁闷。

后来无意中在Google中查到到了参考资料中的一篇帖子,里面提到只要在messages.properties文件中设置typeMismatch属性就可以了,于是我猛然想到Spring MVC Basic Sample中也有个messages.properties文件,打开之后赫然看到果然有一条typeMismatch属性:

  1. typeMismatch=could not be parsed

原来是在这里设置的,真的很郁闷。还好,参考资料中的帖子中还提到可以设置具体的类型,比如可以这样设置:

  1. ## 全局的配置信息
  2. typeMismatch=输入的数据格式不正确
  3. ## 针对BigDecimal类型的错误信息
  4. typeMismatch.java.math.BigDecimal=请输入数字
  5. ## 针对Date类型的错误信息
  6. typeMismatch.java.util.Date=请在{0}中输入正确的日期

参考资料:
Spring MVC Data Binding and user-friendly error messages

Resin中对日志输出的配置

Filed under: JavaPlateform,Web&Server — Jet @ 2:17 下午
Tags:
原文出处: Resin中对日志输出的配置
作者: Jet Mah from Java堂
声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

Resin中对日志的设置主要有如下参数:log、logger、access-log、stdout-log和stderr-log。前两个放在<resin>标签,也就是主标签下面,其中log主要用于配置JDK logging API,logger用于指定需要log的包及level,跟log4j中的用法相似,只不过resin中level有个特殊的选项就是off,用于关闭日志输出;而后面三个*-log放在<host>或<host-default>下面,access用于HTTP输出,stdout用于标准输出(System.out),stderr用于错误输出(System.err,对应log4j中的log.error)。

三个*-log中间的属性是相同的,所以我们集中来说明一下:

  • path: 用于设定日志文件的路径,非常有意思的是它支持所谓的El Variables and Functions,也就是resin中的变量,比如${host.name}就是虚拟站点的id名称,所以如果将它放在<host-default>下面的话,将path设置为 logs/${host.name}/access.log的话可以将不同站点的日志存放在不同的目录下面。
  • archive-format: 这个参数可以设置日志归档的格式,如设置为access-%Y%m%d.log可以在归档的时候自动按日期进行归档命名。另外还有一个特性需要说明的是,resin竟然支持自动压缩log文件,而且设置的方法非常简单,只要后缀名是gz就可以了,如access-%Y%m%d.log.gz,这样归档的时候会自动压缩,而且支持windows和linux系统。
  • format: 用于设置每条日志输出的格式,这个非常简单,而且通常使用系统内置的格式就可以了。
  • rollover-size: 用来设置归档日志文件的最小尺寸,单位可以设置成kb、mb等等,默认为1mb。
  • rollover-period: 用来设置归档日志文件的周期,单位可以是1D(一天)、1W(一周)、1M(一个月)等。
  • 最后给出一个范例供大家参考:

    1. <!--
    2.    - Resin 3.1 configuration file.
    3.   -->
    4. <resin xmlns="http://caucho.com/ns/resin"
    5.        xmlns:resin="http://caucho.com/ns/resin/core">
    6.   <!--
    7.      - Logging configuration for the JDK logging API.
    8.     -->
    9.   <log name="" level="off" path="stdout:"
    10.        timestamp="[%H:%M:%S.%s] {%{thread}} "/>
    11.  
    12.   <!--
    13.      - 'info' for production
    14.      - 'fine' or 'finer' for development and troubleshooting
    15.     -->
    16.   <logger name="com.caucho" level="info"/>
    17.  
    18.   <logger name="com.caucho.java" level="config"/>
    19.   <logger name="com.caucho.loader" level="config"/>
    20.  
    21.     <host-default>
    22.       <!--
    23.          - With another web server, like Apache, this can be commented out
    24.          - because the web server will log this information.
    25.         -->
    26.       <access-log path="logs/${host.name}/access.log" 
    27.             archive-format="access-%Y%m%d.log.gz"
    28.             format='%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"'
    29.             rollover-size="10mb"
    30.             rollover-period="1D"/>
    31.       <!--
    32.          - stdout log and stderr log
    33.         -->
    34.       <stdout-log path="logs/${host.name}/stdout.log" 
    35.             archive-format="stdout-%Y%m%d.log.gz"
    36.             timestamp="[%Y.%m.%d %H:%M:%S.%s]" 
    37.             rollover-size="10mb"
    38.             rollover-period="1D"/>
    39.       <stderr-log path="logs/${host.name}/stderr.log" 
    40.             archive-format="stderr-%Y%m%d.log.gz"
    41.             timestamp="[%Y.%m.%d %H:%M:%S.%s]" 
    42.             rollover-size="10mb"
    43.             rollover-period="1D"/>
    44.     </host-default>
    45. </resin>

    最后还有一个事情需要说明一下,如果在windows系统下将resin注册成服务程序之后就会在log目录下产生jvm-defautl.log文件,而且这个文件会一直累加,所以会变的文件非常大而影响resin的性能。原来在Apache和Resin产生大容量日志的解决办法这篇文件中提到的使用httpd -jvm-log NUL的方法在3.1中无法使用,而且查找了大量的文档也没有找到合适的方法,后来索性将log目录中的写入权限去掉了,重启resin也没有任何影响,算是解决了这个问题。

    参考资料:

    http://caucho.com/resin-3.1/doc/config-log.xtp

    http://caucho.com/resin-3.1/doc/el-var.xtp#host

    Apache和Resin组合时间歇性出现503错误的解决方法

    Filed under: JavaPlateform,Web&Server — Jet @ 5:12 下午
    Tags: ,
    原文出处: Apache和Resin组合时间歇性出现503错误的解决方法
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    使用Apache和Resin进行组合时,如果Resin关闭或中断的时会出现如下的HTTP 503错误:

    Service Temporarily Unavailable
    The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.

    造成这个错误的原因是因为Resin无法启动,可以单独访问resin(如8080端口)查看具体的错误。

    我这里要说的是,在Resin和Apache都非常非常正常的情况下,间歇性的出现上述错误,也就是说不是一直有,而是偶尔出现这个错误,刷新下页面就好了。这个问题非常郁闷,后来在resin的bug报告中(见参考资料)竟然发现了解决的办法:

    The important configuration are load-balance-socket-timeout, socket-timeout, load-balance-idle-time and keepalive-timeout. The two load-balance-* configure mod_caucho. socket-timeout and keepalive-timeout configure the Resin side.

    load-balance-socket-timeout needs to be large enough to handle any server-side processing delays before the first data.

    socket-timeout and keepalive-timeout need to be larger than load-balance-idle-time because they need to keep the connection open for a keepalive request.

    For a diagram, see

    http://caucho.com/resin/admin/load-balancing.xtp

    也就是说,Resin在同Apache进行组合通讯的时候主要依靠四个参数:load-balance-socket-timeout, socket-timeout, load-balance-idle-time 和 keepalive-timeout,其中两个load-balance-*参数用来设置mod_caucho(就是与Apache组合的so文件),socket-timeout和keepalive-timeout用来设置Resin。至于参数数值的大小顺序依此是:load-balance-socket-timeout > socket-timeout 和 keepalive-timeout > load-balance-idle-time。上述四个参数的官方说明地址为:http://caucho.com/resin-3.1/doc/server-tags.xtp

    那为什么会间歇性出现503错误呢?这个是因为resin默认的socket-timeout为65s,时间太短了,有些比较大的请求在这个时间之内还没有进行完。综合上面所说,参考的配置如下:

    1. <resin ...>
    2.     <cluster id="app-tier">
    3.         <server-default>
    4.             <socket-timeout>180s</socket-timeout>
    5.             <keepalive-max>3000</keepalive-max>
    6.             <keepalive-timeout>180s</keepalive-timeout>
    7.             <load-balance-idle-time>120s</load-balance-idle-time>
    8.         </server-default>
    9.     </cluster>
    10. </resin>

    从此,这个世界清净了。

    参考资料:
    Apache 2.2.x + Resin 3.1.x: frequent 503 status error messages (Service Temporarily Unavailable)
    0002862: Apache + Resin and 503 HTTP Error
    resin apache问题

    在Maven中引用jcaptcha出现Missing artifact错误的解决办法

    Filed under: JavaPlateform — Jet @ 11:26 上午
    Tags: ,
    原文出处: 在Maven中引用jcaptcha出现Missing artifact错误的解决办法
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    今天在Maven中引用jcaptcha,dependency内容如下:

    1. <dependency>
    2.             <groupId>com.octo.captcha</groupId>
    3.             <artifactId>jcaptcha</artifactId>
    4.             <version>1.0</version>
    5.         </dependency>

    添加完之后在重新生成Maven工程的时候出现下面的错误:

    Missing artifact com.jhlabs:imaging:jar:01012005:compile

    显然是jcaptcha所倚赖的一个jar包不在常用的仓库里面,后来在其官方网站上终于找到一个包含此包的仓库地址:atlassian-m2-repository http://repository.atlassian.com/maven2 添加到pom.xml文件中即可。

    参考资料:
    Dependency Repository Locations

    给Spring Security中的j_spring_security_check等改名

    Filed under: JavaPlateform — Jet @ 11:43 上午
    原文出处: 给Spring Security中的j_spring_security_check等改名
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    默认情况下,Spring Security(SS)的登录验证提交的Servlet默认名称为j_spring_security_check,但是很多情况下想将这个名称修改掉,后来终于在Spring的官方论坛上找到了一个哥们儿提出的类似的问题(见参考资料),原来只要修改<form-login>节点login-processing-url属性就可以了,其他的一些设置可以参考官方的文档:B.1.5 The <form-login> Element

    对于SS的使用及配置文件的含义可以参考这篇文章:spring-security3 配置和使用,总结的非常到位了。

    参考资料:
    Rename j_spring_security_check to j_security_check

    SimpleJdbcInsert使用executeAndReturnKeyHolder方法返回主键时需要注意的一个地方

    Filed under: JavaPlateform — Jet @ 1:32 下午
    Tags:
    原文出处: SimpleJdbcInsert使用executeAndReturnKeyHolder方法返回主键时需要注意的一个地方
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    Spring的SimpleJdbcInsert发挥了Simple风格,与SimpleJdbcTemplate同属于Simple体系。该类为向数据库中插入数据提供了一个非常快捷的方式,另外它还提供了一套用于返回插入数据的主键的方法:executeAndReturnKeyHolder、executeAndReturnKey。

    查看API的时候可以看到executeAndReturnKey这个方法的返回类型是Number类型,当时我就再想如果主键的类型是String类型呢,比如UUID。后来看到还有一个executeAndReturnKeyHolder方法,返回的是一个KeyHolder对象,可以通过keyHolder#getKeys()获取主键的值,另外还有一个getKeyList()方法用于复合主键的情况,这里先撇开不说。

    看完API之后那就可以动手了,代码如下:

    1. // jdbcInsert是SimpleJdbcInsert对象
    2. Map<String, Object> data = Maps.newHashMap();
    3. data.put("id", "t0001");
    4. data.put("name", "Tom");
    5. data.put("age", 24);
    6. KeyHolder keyHolder = jdbcInsert.withTableName("t_tablename")
    7.         .usingColumns("id", "name", "age")
    8.         .usingGeneratedKeyColumns("id")
    9.         .executeAndReturnKeyHolder(data);
    10. // 下面主要是对keyHoder进行分析
    11. if(keyHolder == null) {
    12.     return null;
    13. }
    14. Map<String, Object> keys = keyHolder.getKeys();
    15. if(keys == null || keys.size() == 0 || keys.values().size() == 0) {
    16.     return null;
    17. }
    18. Object key = keys.values().toArray()[0];
    19. if(key == null || !(key instanceof Serializable)) {
    20.     return null;
    21. }
    22. if(key instanceof Number) {
    23.     Long k = (Long)key;
    24.     return (idType == int.class || idType == Integer.class) ?
    25.             k.intValue() : k;
    26. } else if(key instanceof String) {
    27.     return (String)key;
    28. } else {
    29.     return (Serializable)key;
    30. } // end of if(key instanceof Number)

    如果主键id的类型是int或long上面的代码没有任何问题,但是如果是自定义的UUID等String类型则问题出现了,提示下面的错误:

    org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL []; SQL state [HY000]; error code [1364]; Field ‘id’ doesn’t have a default value; nested exception is java.sql.SQLException: Field ‘id’ doesn’t have a default value

    有些奇怪了吧?明明id的值已经传入了,但是错误的提示应该是没有传入id的值。进入到Spring的源代码中,发现代码里面有一些debug信息,于是在log4j中将debug打开:

    1. log4j.logger.org.springframework.jdbc.core=debug

    从打印出来的信息中看,Spring自动生成的Insert语句中竟然没有id字段!!!继续最终源代码,先在org.springframework.jdbc.core.simple.AbstractJdbcInsert类中找到protected void compileInternal()方法,在代码前加上一个debug信息:

    1. protected void compileInternal() {
    2.     logger.debug("getGeneratedKeyNames: " + getColumnNames());
    3.     tableMetaDataContext.processMetaData(getJdbcTemplate().getDataSource(), getColumnNames(), getGeneratedKeyNames());
    4.     ...
    5. }

    这个时候打印的列表中有id字段,继续最终,最后终于在TableMetaDataContext#createInsertString(java.lang.String[])方法里面找到,关键的代码片段如下:

    1. for (String columnName : this.getTableColumns()) {
    2.             // 这里将SimpleJdbcInsert#usingGeneratedKeyColumns方法中所设置的字段去除了
    3.             if (!keys.contains(columnName.toUpperCase())) {
    4.                 columnCount++;
    5.                 if (columnCount > 1) {
    6.                     insertStatement.append(", ");
    7.                 }
    8.                 insertStatement.append(columnName);
    9.             }
    10.         }

    看到这里,我也猛然恍然大悟了。既然在INSERT INTO语句中设置了UUID的值,那这里就不需要再使用KeyHolder进行返回了,直接获取就是了。这也是为什么KeyHolder中的getKey()方法的返回类型是Number的原因了,因为通常来说需要Spring返回的就是插入数据库中的自增类型的主键值。

    common-configuration中CompositeConfiguration类的一个需要注意的地方

    Filed under: JavaPlateform — Jet @ 4:05 下午
    原文出处: common-configuration中CompositeConfiguration类的一个需要注意的地方
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    Common-Configuration中,CompositeConfiguration类用来对多个配置文件进行组合处理,该类有一个getNumberOfConfigurations()方法,用来读取Configuration对象的个数,但是如果执行下面的代码:

    1. CompositeConfiguration config = new CompositeConfiguration();
    2. int number = config.getNumberOfConfigurations();
    3. System.out.println(number);

    这个时候会发现number的值是1而不是0,虽然没有向其中添加任何Configuration对象。CompositeConfiguration构造方法的代码如下:

    1. public CompositeConfiguration()
    2.     {
    3.         clear();
    4.     }
    5.  
    6.     public void clear()
    7.     {
    8.         configList.clear();
    9.         // recreate the in memory configuration
    10.         inMemoryConfiguration = new BaseConfiguration();
    11.         ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    12.         ((BaseConfiguration) inMemoryConfiguration).setListDelimiter(getListDelimiter());
    13.         ((BaseConfiguration) inMemoryConfiguration).setDelimiterParsingDisabled(isDelimiterParsingDisabled());
    14.         configList.add(inMemoryConfiguration);
    15.     }

    可以看到默认构造方法中调用了clear()方法,而clear方法中自动添加了一个BaseConfiguration对象,该对象用于存放自身的一些配置信息。所以这个时候的数量是1而不是0,这一点容易让人迷惑。

    参考资料:
    Re: [Configuration] CompositeConfiguration.getNumberOfConfigurations( ) question

    客户真正的需求原来是这样的

    Filed under: JavaPlateform — Jet @ 9:21 上午
    原文出处: 客户真正的需求原来是这样的
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    m2eclipse插件使用中提示jar包找不到的解决方法

    Filed under: JavaPlateform — Jet @ 4:33 下午
    原文出处: m2eclipse插件使用中提示jar包找不到的解决方法
    作者: Jet Mah from Java堂
    声明: 可以非商业性任意转载, 转载时请务必以超链接形式标明文章原始出处、作者信息及此声明!

    在Eclipse中使用m2eclipse开发Maven项目的时候,如果是创建Module项目的时候经常会莫名的出现找不到子模块包的错误信息,但是pom.xml已经加入了改包的引用,并且使用mvn deploy命令等编译也没有任何错误,后来在Eclipse中的Marks View中发现一个项目的warning信息:

    Classpath entry org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER will not be exported or published. Runtime ClassNotFoundExceptions may result.

    后来Google了一下,解决这个warning的步骤如下:
    1. 首先右键项目,然后选择“Properties”,在左边选择Java Build Path,然后在右边的“Order andExport”标签中选中最下面的“Maven Dependencies”,然后确定;

    2. 选择“Marks”或“Problems”视图,然后选中那个warning信息,右键选择“Quick Fix”,然后直接选择“Finish”,这样就可以了。

    如果还是出现错误的信息,直接在上面的视图中右键删除提示就可以了。

    参考资料:
    Why the “MAVEN2_CLASSPATH_CONTAINER will not be exported or published”

    下一页 »