Velocity模板循环支持自定义步长

Velocity模板引擎中只有一个#foreach指令支持循环的遍历,每次都是循环一次。
但有时候会遇到在一行里面显示两列数据,也就是说需要在一次循环中读取两条数据,这就涉及到步长的概念。Velocity模板引擎的#foreach指令不支持设置步长,但可以通过下面的方式实现自定义步长:

  1. ## 假定$datas为传递到模板引擎的数组
  2. #set($step = 0) ## 设置步长
  3. #set($length = $datas.size())  ## velocity中数组的长度是size()而非length
  4. #foreach($count in [1..$length])
  5.   #if($count %2 != 0)
  6.     #if($step < $length)
  7.       #set($data = $datas.get($step);
  8.     #else
  9.       #set($data = '')
  10.     #end
  11.     姓名:$!data.name 年龄:$!data.age
  12.  
  13.     #if($step < $length)
  14.       #set($data = $datas.get($step);
  15.     #else
  16.       #set($data = '')
  17.     #end
  18.     姓名:$!data.name 年龄:$!data.age
  19.   #end
  20. #end

在Velocity中,数组会转成类是List的类型,所以获取数组长度使用$datas.size()而不是$datas.length,通过下标获取元素的方式使用$datas.get($step),而不是$datas[$step]。

通过以上方式就是在Velocity模板引擎中实现自定义步长,虽然有些繁琐但是非常有效的。

参考资料:
velocity 模板的 foreach 如何支持步长
How to access/get the size of an array/collection in velocity templates?

使用IDE的JUnit测试正常但使用mvn test失败的解决方法

经常会遇到一个问题,在Eclipse中执行JUnit测试是正常的,但使用Mave的test命令测试是失败的。在Maven中执行测试是使用Surefire插件进行的,所以需要在pom.xml文件中对Surefire插件进行设置。

首先对Surefire插件进行如下设置,基本上可以解决大多数这个问题:

  1.  <plugin>
  2.      <groupId>org.apache.maven.plugins</groupId>
  3.      <artifactId>maven-surefire-plugin</artifactId>
  4.      <version>2.12</version>
  5.      <configuration>
  6.          <forkMode>always</forkMode>
  7.      </configuration>
  8.  </plugin>

Surfire 2.14以上版本,forkMode配置项被废弃,使用下面的配置进行替代:

  1.  <plugin>
  2.      <groupId>org.apache.maven.plugins</groupId>
  3.      <artifactId>maven-surefire-plugin</artifactId>
  4.      <version>2.19.1</version>
  5.      <configuration>
  6.          <reuseForks>false</reuseForks>
  7.          <forkCount>1</forkCount>
  8.      </configuration>
  9.  </plugin>

昨天我执行Spring Dao的测试,中间使用jdbc:initialize-database通过自动导入sql文件的方式初始化数据库的数据,因为文件中有中文出现了乱码,造成初始化失败。另外,默认情况下会发现执行mvn test命令的结果里面包含有很多乱码,可以在argLine中指定文件的编码。偶尔也会有执行mvn test造成内存溢出的错误,也可以在argLine中进行配置,内容如下:

  1.  <plugin>
  2.      <groupId>org.apache.maven.plugins</groupId>
  3.      <artifactId>maven-surefire-plugin</artifactId>
  4.      <version>2.19.1</version>
  5.      <configuration>
  6.          <reuseForks>false</reuseForks>
  7.          <forkCount>1</forkCount>
  8.          <argLine>-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8 -Xverify:none</argLine> 
  9.      </configuration>
  10.  </plugin>

其实Eclipse下面的JUnit和mvn test的环境有很多是不一样的,比如执行JUnit正常,但mvn test会提示找不到某些资源文件,这是因为所有的非java文件都必须放到resources文件夹中,项目资源文件放在src/main/resources中,测试资源文件放在src/test/resources文件夹中。这一点一定要特别注意,有时候在webapp项目中进行测试的时候,需要WEB-INF文件夹放在Class Path中,配置如下:

  1.  <plugin>
  2.      <groupId>org.apache.maven.plugins</groupId>
  3.      <artifactId>maven-surefire-plugin</artifactId>
  4.      <version>2.19.1</version>
  5.      <configuration>
  6.          <reuseForks>false</reuseForks>
  7.          <forkCount>1</forkCount>
  8.          <argLine>-Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8 -Xverify:none</argLine>
  9.          <additionalClasspathElements>
  10.              <additionalClasspathElement>${basedir}/src/main/webapp/‌​WEB-INF/</additional‌​ClasspathElement>
  11.          </additionalClasspathElements>
  12.      </configuration>
  13.  </plugin>

其他相关的目录如果也想加入到Class Path中的话方法也是类似的。

以上可以说能够解决95%以上的上述问题,但昨天我遇到一个非常奇葩的情况,简单来说有个Maven项目除了一个主pom.xml文件之外,包含两个子模块moduleA和moduleB,结构如下:

main
 |-- ModuleA
 |-- ModuleB
 pom.xml

ModuleB依赖ModuleA,在Eclipse中对ModuleB进行单元测试正常,但对ModuleB执行mvn test命令的时候总是出现NullPointerException,不过我对出现NullPointerException异常的变量进行日志输出或调试的时候是有值的,非常奇怪。后来经过近两个小时的调试和排查发现原来ModuleB中测试的类中用到了ModuleA中类的某个方法,而这个方法是新增加的,并且ModuleA没有install,在Eclipse中默认会将ModuleA放入到环境变量中,所以不会出错。但执行mvn test的时候并不会将依赖的项目放入到ClassPath中,所以出现了错误。不过这个错误也的确怪异了一些,最后先将ModuleA使用mvn install之后,在对ModuleB进行mvn test就可以通过测试了。

参考资料:
JUnit tests pass in Eclipse but fail in Maven Surefire
Spring JUnit test keeps return nullpointerexception on my service class
spring junit testing

ng-model绑定的变量在controller中为undefined

举例来说有如下AngularJS代码:

  1.  angular.module('starter', ['ionic', 'starter.controllers', 'starter.services'])
  2.  .config(function($stateProvider, $urlRouterProvider) {
  3.    $stateProvider.state('app', {
  4.      url: '/app',
  5.      abstract: true,
  6.      templateUrl: 'template/app.html'
  7.    })
  8.    .state('app.search', {
  9.      url: '/search',
  10.      views: {
  11.        'content': {
  12.          templateUrl: 'templates/search.html',
  13.          controller: 'SearchCtrl'
  14.        }
  15.      }
  16.    });
  17.  
  18.    $urlRouterProvider.otherwise('/app/search');
  19.  });
  20.  
  21.  angular.module('starter.controllers', [])
  22.  .controller('SearchCtrl', function($scope) {
  23.      $scope.onSearch= function() {
  24.          console.log($scope.keyword);
  25.      };
  26.  });

模板文件search.html内容如下:

  1.  <ion-view>
  2.      <ion-content class="has-header">
  3.          <form ng-submit="onSearch()" class="bar bar-header item-input-inset">
  4.              <label class="item-input-wrapper">
  5.                  <i class="icon ion-ios7-search placeholder-icon"></i>
  6.                  <input type="search" placeholder="Search..." ng-model="keyword">
  7.              </label>
  8.          </form>
  9.      </ion-content>
  10.  </ion-view>

这个时候无论在文本框中输入任何东西按“搜索”之后,keyword的值始终是undefined。不过如果在ion-content中增加ng-controller=”SearchCtrl”之后则会正常赋值,但这样的话过于繁琐了。

正确的做法是,首先ng-model不要直接设置成字符串,而是设置成一个object对象,然后需要在controller对这个对象进行初始化。代码如下:

  1.  angular.module('starter.controllers', [])
  2.  .controller('SearchCtrl', function($scope) {
  3.      // 对query进行初始化
  4.      $scope.query = {};
  5.  
  6.      $scope.onSearch= function() {
  7.          console.log($scope.query.keyword);
  8.      };
  9.  });

模板文件search.html内容如下:

  1.  <ion-view>
  2.      <ion-content class="has-header">
  3.          <form ng-submit="onSearch()" class="bar bar-header item-input-inset">
  4.              <label class="item-input-wrapper">
  5.                  <i class="icon ion-ios7-search placeholder-icon"></i>
  6.                  <!-- ng-model 采用对象的形式 -->
  7.                  <input type="search" placeholder="Search..." ng-model="query.keyword">
  8.              </label>
  9.          </form>
  10.      </ion-content>
  11.  </ion-view>

参考资料:
ng-model is undefined in controller
Nested Scopes in AngularJS

使用Jenkins自动将maven webapp发布到远程演示服务器上

安装并配置Jenkins成功之后,接下来还需要将自动构建的war包发布到远程的演示服务器上,为了使操作更加灵活,可以使用SSH Shell的方式进行。为了有更好的体验,如果发布过程中出现错误自动发送邮件通知。

1. 配置邮件服务器及邮件模板
首先保证已经成功安装了Email Extension Plugin插件,然后依次点击左边的“Manage Jenkins” – “Configure System”,首先在Jenkins Location项目下面设置系统管理员的邮件地址,这里填写的邮箱地址必须跟下面设置的发件人邮箱一致,否则不能成功发送邮件。

接下来在 Extended E-mail Notification 项下面设置SMTP服务器和邮件模板,这里推荐使用QQ邮箱:

Default Recipients填写默认的接收人邮箱地址,多个用英文逗号(,)进行分割,邮件模板可以使用下面参考资料里面提供的模板。
Default Content Type内容:

  1. 构建通知:$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!

Default Subject内容:

  1. (本邮件是程序自动下发的,请勿回复,<span style="color:red">请相关人员fix it,重新提交到git 构建</span>)<br/><hr/>
  2.  
  3. 项目名称:$PROJECT_NAME<br/><hr/>
  4.  
  5. 构建编号:$BUILD_NUMBER<br/><hr/>
  6.     
  7. GIT版本号:${GIT_REVISION}<br/><hr/>
  8.     
  9. 构建状态:$BUILD_STATUS<br/><hr/>
  10.     
  11. 触发原因:${CAUSE}<br/><hr/>
  12.     
  13. 构建日志地址:<a href="${BUILD_URL}console">${BUILD_URL}console</a><br/><hr/>
  14.     
  15. 构建地址:<a href="$BUILD_URL">$BUILD_URL</a><br/><hr/>
  16.     
  17. 变更集:${JELLY_SCRIPT,template="html"}<br/><hr/>

PS: 下面的 E-mail Notification 项是系统默认的邮件通知设置,如果安装了Email Extension Plugin插件可以不用设置了。

2. 配置SSH服务器
首先保证已经安装了Publish Over SSH插件,然后依次点击左边的“Manage Jenkins” – “Configure System”,在Publish over SSH项下面设置登录到远程服务器的配置信息,可以使用用户名/密码的方式,也可以使用证书的方式,这里建议使用后者。

为了安全性考虑,不建议使用root用户。可以使用下面的方法创建一个和root同等权限的账号:

  1.  useradd -o -u 0 -g 0 -M -d /root -s /bin/bash tom
  2.  passwd tom

使用非root用户(以tom为例)登录远程服务器,执行下面的命令生成密钥对

  1.  ssh-keygen -t rsa -C "tom@qq.com"

其中C参数用于设置注释,可以知道其用途。执行命令之后会分别提示文件存放的位置(默认为当前用户下的.ssh目录下)、文件名(默认为id_rsa和id_rsa.pub)、私钥的口令(passphrase,建议设置)。执行完成之后将公钥合并成authorized_keys文件:

  1.  cd /root/.ssh
  2.  cat id_rsa.pub > authorized_keys

这样在当前用户的home目录(root为/root,其他账号为/home/<user>)的.ssh目录下面就有了三个文件:id_rsa、id_rsa.pub和authorized_keys,如果是在root用户下执行的上述命令,必须确保三个文件一定要在/<user_home>/.ssh目录下,如果不存在需要手动创建,并且三个用户的权限owner应该为<user>。

接下来就可以在Publish over SSH项设置Passphrase和Key,分别是私钥的密码和id_rsa文件的内容,点击下面的Add按钮增加SSH Servers,如果不使用上面公共的密钥设置或者使用用户名密码可以将 Use password authenticatio, or use a different key 选项勾选。

设置完成之后可以点击下面的Test Configuration按钮,如果无误的话会出现Success的提示。
PS:如果对远程服务器进行了安全设置,需要将 /etc/ssh/sshd_config 文件中的 PermitRootLogin 项目设置为 without-password,这样root账号不能用密码直接远程登录,但可以使用密钥对进行登录。

3. 构建完成之后使用SSH发布到演示服务器上
详细创建Jenkins项目请见使用Jenkins实现自动构建Maven项目这篇文章,在项目的配置项中的Post Steps项中选中 Send files or execute commands over SSH:

接下来就可以设置执行的任务内容了:

SSH Server里面选中上面创建的SSH Server,下面的Source files设置需要拷贝的war包或jar包路径和文件名,文件路径可以使用相对路径,根目录为整个项目的目录;Remove prefix可以把多余的路径前缀去掉,否则上传到服务器上的时候会自动创建对应的目录;Remote directory上传到服务器上的路径,可以不设置,默认为用户根目录;Exec command设置用于执行的脚本路径。新版本的Jenkins会有“Either Source files, Exec command or both must be supplied”的错误提示,要求同时设置上传文件和执行的命令,但是设置了还是会报这个错误提示,可以忽略,实际运行的时候不会出错。
需要注意的是,设置的SSH登录用户必须有操作远程服务器脚本的权限,既然shell脚本是自定义的,这样操作起来就非常灵活了。下面是用于发布到resin上的脚本内容:

  1.  # stop resin
  2.  java -jar /usr/resin/lib/resin.jar stop
  3.  # unzip
  4.  unzip /root/demo-webapp.war -d /root/demo-webapp/
  5.  # remove lib & classes
  6.  rm -rf /usr/wwwroot/demo/WEB-INF/lib/
  7.  rm -rf /usr/wwwroot/demo/WEB-INF/classes/
  8.  # copy to webroot
  9.  \cp -a /root/demo-webapp/. /usr/wwwroot/demo/
  10.  # TODO execute sql file
  11.  
  12.  # remove war & files
  13.  rm -rf /root/demo-webapp/
  14.  rm -rf /root/demo-webapp.war
  15.  # start resin
  16.  java -jar /usr/resin/lib/resin.jar start

4. 设置邮件通知
点击Post-build Actions下面的 Add post-build action 按钮,选择 Editable Email Notification,出现对邮件通知的设置,上面的都使用默认的就可以,需要注意的是必须添加Triggers,否则不会发送邮件通知。点击下面的 Advanced Settings 按钮设置Trigger触发的条件。

Trigger可以设置很多出发条件,常用的主要有三类:Failure-Any(各种情况下的构建失败)、Success(构建成功)、Unstable (Test Failures)(测试失败)。Send To是指发送邮件给谁,默认发送给Developer,也就是对这次构建有过修改动作的人。关于邮件接收人,这里有必要说明一下Developers和Culprits两个角色:

Developers – 根据VCS(SVN或git等)中最后一次代码的提交人。邮件地址的规则是VCS提交人的用户名加上在Jenkins总设置中设置的默认邮箱后缀(Manage Jenkins – Configure System – Extended E-mail Notification – Default user E-mail suffix),比如SVN中组后一次提交人的用户名是 tom,默认邮箱后缀设置的是@qq.com,那么开发者的邮箱就是 tom@qq.com。
Culprits – 上面的Developers是最后一次代码的提交人,Culprits就是相关源码的所有提交人,邮箱地址的规则同上。

这样设置保存之后就可以根据设置的条件发送邮件通知了,非常方便。

参考资料:
「Jenkins+Git+Maven+Shell+Tomcat持续集成」经典教程
jenkins通过 ssh插件部署war包
Jenkins(Hudson)实现远程部署Web项目
解决非root用户用SSH不能无密码登陆本机问题。
Jenkins实践二:如何让你的构建失败通知所有人?——邮件,简聊,slack
Email-ext plugin Triggers

Jenkins构建时出现PermGen space错误的解决方法

安装好Jenkins并创建Maven项目之后,开始进行Build,结果程序立马死掉并出现下面的PermGen space错误。

查看日志也是 java.lang.OutOfMemoryError: PermGen space 错误,典型的内存溢出错误,很简单,加大jvm的内存,但是没想到开始了下面曲折的解决之路:

开始是将Jenkins扔到tomcat下面的,于是先在tomcat安装目录的bin目录下面执行 service.bat install 命令将Tomcat安装成Windows服务,然后运行 tomcat7w 设置jvm的内存,然后启动,但是开始building依然出错;

然后在Jenkins的Configure System中设置全局的 MAVEN_OPTS-Xmn128m -Xms1024m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=518m,重启Jenkins运行building,依然报上面的错误;

于是想到是不是直接使用mvn命令在项目下面运行是否可以,于是进入到项目根目录下面执行 mvn clean install 果然出现了几个错误,逐一解决掉之后继续重启Jenkins并运行building,虽然错误的界面不同,但依然界面挂起然后进程占用CPU;

又想到是不是tomcat的内存设置的没有生效,于是安装了Monitoring插件运行之后设置的内存是生效的;后来又想到是不是把tomcat和maven的jvm内存都设置的太大了,在一起启动不起来,于是调低了内存并使用 java -Xmn128m -Xms1024m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=518m -version 逐一进行测试,依然如故。

在这个过程中发现了两个特点:一是执行svn的时候没有问题,只要一执行mvn命令就会死在原界面;二是每次挂起的时候tomcat整个也无法运行了。于是先将Jenkins安装成一个独立的程序,然后在jenkins.xml文件中设置jvm内存,并独立启动再运行依然出现错误。

偶然间在jenkins数据目录(默认为USER_HOME/.jenkins)下面查看job的日志,突然出现了一个 java -e -B -Dmaven.test.skip=true -Xmn128m -Xms1024m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=518m … 的日志,后来等了一会儿就消失了,然后出现PermGen space错误,于是想到怎么Jenkins将mvn的参数当作了jvm参数,于是发现在项目中将 -e -B -Dmaven.test.skip=true 参数放在了项目的 MAVEN_OPTS 参数中,原来应该向下面这样放在 Golas and options 里面:

最后运行building,速度很快并成功了。原来前面设置好jenkins(或java web容器)和maven的jvm内存就可以了,只是因为将mvn的参数误设置到了 MAVEN_OPTS 中,但很奇怪依然产生了PermGen space错误。

其他安装并初始化Jenkins的信息参考上一篇文章

参考资料:
Builds failing with OutOfMemoryErrors
How do I increase memory on Tomcat 7 when running as a Windows Service?

使用Jenkins实现自动构建Maven项目

自动构建(CI)是敏捷开发中非常重要的一个环节,Jenkins是最常用的开源的自动构建工具,本文将实现使用Jenkin实现每日自动构建并发布到远程开发服务器上给客户进行演示。

1. 安装Jenkins
Jenkins官网下载最新版的Jenkins(本文下载的版本是2.3.1),是一个war包,可以直接扔到tomcat的webapps目录下或其他Java Web容器中运行,也可以直接执行java命令进行安装,建议以第二种方式,程序独立并且可以自由设置。方法如下:

执行 java -jar jenkins.war --httpPort=8099 命令运行Jenkins,然后打开浏览器访问 http://localhost:8099 就可以运行Jenkins,非常简单。

上面的命令中 httpPort 用来设定端口,默认为8080,跟多的参数请访问 Starting and Accessing Jenkins
需要注意的是,Jenkins 2.x要求JDK的版本必须是1.7及以上,否则无法运行。

Jenkins 2.x 首次安装运行增加了安全认证,根据屏幕提示访问指定的initialAdminPassword文件内容,将里面的字符串输入验证之后才可以继续,第一次启动的时候需要等待系统初始化,然后根据向导来初始化系统,配置信息都放在了当前用户目录下的.jenkins目录中,也可以自定义目录。

如果想要Jenkins自动运行,Linux下直接将启动的命令放在/etc/rc.local中即可,Windows下面需要将其按章程服务,如果使用命令行运行的Jenkins,可以在Manage Jenkins下面点击的Install as Windows Service按钮进行安装:

执行的时候可以重新设置jenkins的目录,如果设置成新的目录之后默认的.jenkins目录就可以删除了。卸载的时候只要进入到数据目录,然后执行 jenkins uninstall 即可。

因为Jenkins会执行一些定时任务,所以需要比较大的内存,需要对web server分配足够的内存,如果是放在Java Web容器中的话在容器中设置,命令行的方式可以直接在命令行中增加参数,采用上面独立安装的方式的话,可以在数据目录下的jenkins.xml文件中设置,如下图所示:

为了提升运行的效率,Jenkins每次启动的时候会将配置信息读取到内存中,所以如果修改了Jenkins的配置信息的话,需要在Manage Jenkins下面点击 Reload Configuation from Disk,重新加载配置信息。

Jenkins以其拥有大量的插件而著称,接下来需要安装常用的插件,本文需要确保已经安装了如下插件:

Email Extension Plugin (邮件通知)
Deploy to container Plugin (部署到Java容器中的插件)
Publish Over SSH (远程Shell)
Maven Integration plugin (Maven集成,否则新建的时候没有Maven Project)
Monitoring (监控Jenkins所消耗的系统资源,如内存使用等)

在Plugin Manager中可以查看已经安装的插件,也可以通过在线的方式进行安装。

2. 对Jenkins进行配置
接下来在Global Tool Configuration中配置jdk和maven,见下图所示:

设置的时候可以直接让Jenkins在线安装,也可以设置成本机的路径。

3. 创建一个maven任务
回到主界面,点击左上角的New Item:

保存之后进入下一步,分别设置源代码管理,这里以SVN为例:

设置集成的方式,选中 Build periadically设置定时集成的表达式,规则和linux下的定时任务表达式规则是一致的:

设置项目的pom.xml文件(相对路径)和maven的命令:

上面 clean install 前面是maven的参数,分别表示指定profile、显示详细的错误信息、自动应答需要交互的流程和忽略测试。还可以加上 -U 参数强制更新Maven依赖包。点开下面的Advanced按钮之后可以设置MAVEN_OPTS等信息,主要设置jvm的一些参数,防止构建的时候出现内存溢出错误。

可以在此处设置一个针对当前项目的设置,也可以在Configure System设置中设置一个全局的配置:

经过这样设置保存之后,就可以进入欢迎页看到创建的项目了,然后点击最右边的 Build 按钮进行构建测试。在构建的时候可以点击项目的 Console Output 按钮查看控制台输出的日志,查看构建的过程。

还有几个问题请参考其他相关文章:
Jenkins构建时出现PermGen space错误的解决方法
使用Jenkins自动将maven webapp发布到远程演示服务器上

参考资料:
《Jenkins权威指南》
「Jenkins+Git+Maven+Shell+Tomcat持续集成」经典教程
Starting and Accessing Jenkins
使用Jenkins可持续集成maven项目
How to give Jenkins more heap space when it´s started as a service under Windows?
Maven实战(四)——基于Maven的持续集成实践
Skip a submodule during a maven build

WebStorm中自定义web server

在WebStorm中创建网页,可以将鼠标移到网页的右上方,会出现浏览器的图标,点击即可通过浏览器打开类似http://localhost:63342/project_name/dir/demo.html的地址:
20161202235519

但如果想将demo.html放到根目录下面的话,需要本地安装了类似Apache或Nginx之类的web server程序,然后打开 Tools – Development – Configuration菜单,在对话框中点击+新建一个server,类型选择Local or moounted folder,然后在Mappings指定Local Path。同时需要在web server程序中设定对应的目录,这样设置之后在点击右上角浏览器图标之后就打开自定义的web server了。
20161203000201

在Nginx中使用Godaddy的SSL证书

首先在Godaddy付款购买SSL证书,成功之后打开管理面板,找到刚购买的SSL证书,点击新建证书,这个时候Godaddy会让提供CSR文件内容,可以通过下面的命令行生成csr内容:

  1. openssl genrsa -des3 -out <name of your certificate>.key 2048
  2. openssl req -new -key <name of your certificate>.key -out <name of your certificate>.csr

将上面替换成真实的文件名,第一个命令行生成的是证书,在第二步的时候提示输入一些信息,其中Common Name (e.g. server FQDN or YOUR name) 是需要使用的证书的域名。对于普通的DV(域名验证) SSL证书 只需要保证CN(Common Name)字段填上你的域名即可,而对于高级的OV(组织验证),EV(扩展验证)证书都需要向CA提交相关资料。

将生成的csr文件内容复制粘贴到Godaddy的文本框中,提交之后会向域名所有人的邮箱发送一个域名验证的链接,验证成功之后就可以下载证书。Godaddy会提供两个crt文件,一个是随机字符串命名(如7d3ed5e3ef729196.crt),另一个以gd_bundle开头(如gd_bundle-g2-g1.crt),需要执行下面的命令将这两个文件合并成一个文件:

  1. cat 7d3ed5e3ef729196.crt gd_bundle-g2-g1.crt > <name of your certificate>.csr

如果想在Nginx中使用需要Nginx支持SSL,可以参考 让Nginx多个虚拟主机支持SSL 这篇文章。

将上面的key文件和合并后的csr文件放到nginx的conf/ssl目录下,然后修改成如下设置:

  1. server {
  2.         listen 443;
  3.         server_name www.examples.com;
  4.         index index.php;
  5.  
  6.         ssl on;
  7.         ssl_certificate ssl/domain.crt;
  8.         ssl_certificate_key ssl/domain.key;
  9.         ssl_session_timeout 5m;
  10.         ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  11.         ssl_ciphers ALL:!ADH:!EXPORT56:!RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
  12.         ssl_prefer_server_ciphers on;
  13.  
  14.         # 正常的服务器配置...

不过这样设置之后,如果启动Nginx的时候会要求输入key证书的密码,可以生成一个解密的key文件:

  1. openssl rsa -in domain.key -out domain.key.unsecure

输入证书的密码就可以了,然后将上述的domain.key替换成domain.key.unsecure。

参考资料:
使用OpenSSL生成CSR文件以申请SSL证书
Nginx设置godaddy ssl证书HTTPS方法详解
怎么让nginx配置SSL安全证书重启免输入密码

Spring中@Transactional无法用在接口的实现类

以Spring 3.x为例,代码如下:

  1.  // IProcessor.java
  2.  package example;
  3.  
  4.  public interface IProcessor {
  5.    void execute();
  6.  }
  7.  
  8.  // JavatangProcessor.java
  9.  package example.impl;
  10.  
  11.  import example.IProcessor;
  12.  
  13.  @Component
  14.  @Transactional
  15.  public class JavatangProcessor implements IProcessor {
  16.  
  17.    @Override
  18.    public void execute() {
  19.     
  20.    }
  21.  }

xml配置文件内容如下:

  1.  ...
  2.  <context:component-scan base-package="example" />
  3.  <tx:annotation-driven transaction-manager="transactionManager" />

如果在其他地方使用@Autowired注入JavatangProcessor的话,在启动工程的时候会出现类似下面的错误信息:

java.lang.IllegalArgumentException: Can not xxx.javatangProcessor field JavatangProcessor to $Proxy41
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
at java.lang.reflect.Field.set(Field.java:657)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:502)

这是因为tx:annotation-driven默认情况下Spring使用的是基于接口的代理模式,在这种情况下子类无法直接进行初始化并进行引用,解决方法是在tx:annotation-driven中将proxy-target-class属性设置为true,即创建子类来代理业务类,xml配置文件修改成:

  1.  <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

如果是Spring 2.x的话需要设置成:

  1.  <aop:aspectj-autoproxy/>

参考资料:
Spring3事务管理——使用@Transactional 注解
Spring @Transactional doesn’t work with other annotations?

使用C3P0连接MySQL出现大量的Sleep状态连接的处理方法

在MySQL中使用show processlist命令查看的时候,发现有大量Sleep状态的连接,消耗了大量的资源,尤其在使用阿里云RDS的时候会出现连接数超标的情况。
因为数据库连接池使用的是C3P0,原来minPoolSize和initialPoolSize都设置的是1,需要将二者的值都修改成0,即只有在使用的时候才进行数据库连接。还需要将MySQL的wait_timeout的值设置的小一些,比如90,同时还需要注意将C3P0中的maxIdleTime值小于wait_timeout的值。

参考资料:

c3p0 how to shutdown all the database connections and re-open them when need?
c3p0 maxIdleTime is same as wait_timeout of mysql?