JSR 303是标准的bean validation框架, 还没有发布正式版,最新的版本是1.0CR5。通过JSR 303,我们可以为域模型里添加@NotNull, @Email等注解,然后在应用程序的不同层里使用这些注解对于域模型做验证。
JSR 303目前有两个实现,分别是agimatec-validation和Hibernate Validator 4.0,这二者目前都没有正式版,但是测试版已经可以使用了。
Spring 3.0刚刚发布了RC1版,里面提供了对于JSR303的初步支持,试了一下,感觉还不错。
测试程序使用maven, 用的是hibernate的实现。首先,需要在pom里加入相关依赖:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.CR5</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.0.0.CR1</version>
</dependency>
在Spring的配置文件里加入validator factory:
<bean id="validator" >
<property name="providerClass"
value="org.hibernate.validator.HibernateValidator"/>
</bean>
测试用的model:
public class User{
private String username;
@NotNull(message = "用户名不能为空!")
@Size(min = 3, max = 16, message = "用户名最少 {min}, 最大 {max} 个字符")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
测试用例:
@ContextConfiguration(locations = { "classpath:applicationContext.xml" })
@TransactionConfiguration(defaultRollback = true)
public class TestValidator{
@Autowired
@Qualifier("validator")
private Validator validator;
@Test
public void testValidator(){
User user = new User();
user.setUsername("a");
Set<ConstraintViolation<User>> vals = validator.getInvalidValues(user);
Assert.assertEquals(1, vals.size());
}
}
运行 mvn test,测试通过。
update: 如果要配合Spring MVC合用,则需要在applicationContext里配置:
<bean>
<property name="webBindingInitializer">
<!-- Configures Spring MVC DataBinder instances -->
<bean>
<property name="validator" ref="validator" />
</bean>
</property>
</bean>
这样,对于标注为@Controller类里的@Valid标记的对象,Spring会自动做验证。
从知名度和用户量来看,Sakai不算是一个成功的学习管理系统(LMS),从这里的比较就可以看出来。但Sakai有大量资金支持,是集众多专业开发者的力量打造而成的,其软件设计方面还是颇值得研究的。
Sakai有一个内核,负责提供公用的服务并且管理不同的组件。此外,还有一些独立的web应用来实现多种不同的功能,这些web彼此之间相互隔离,但是可以调用Sakai的公用服务。这一功能是通过Sakai自有的Component Manager机制实现的。
Component Manager
Component Manager负责将Sakai的各部分组装起来,它使得service之间能够通讯,并且各个web app能够使用这些service。在通常情况下,servlet 容器里的web应用是独立运行的,它们之间不能通讯,并且不能输出可共享的服务。Component Manager通过自定义ClassLoader的方式,提供了一些可共享的服务,以供各个web应用使用,同时,Sakai也提供了不用关闭整个服务而重启插件应用的方法。Sakai通过Component Manager来避开servlet容器的限制。Sakai里共享的服务API运行于servlet容器的特定位置,这个位置对所有的web应用可见。
与Component Manager功能相似的工业标准
Sakai的Component Manager是一个非标准实现,有两个实现类似功能的标准:
- EJB容器,有JBoss, Weblogic, Websphere等相关实现
- OSGI,有Apache felix, Eclipse Equinox等实现。Spring提供对于OSGI的支持,新版本3.0的组件都做成了OSGI bundle的形式。还有Sping DM项目对OSGI提供支持。
Component Manager的工作原理
Sakai的结构如下图所示:

在第一个web app启动时,Component Manager会随之启动。Component Manager为每一个component创建一个用来加载它的ClassLoader:
/**
* Create the class loader for this component package
*
* @param dir
* The package's root directory.
* @return A class loader, whose parent is this class's loader,
which has the classes/ and jars for this component.
*/
protected ClassLoader newPackageClassLoader(File dir)
{
// collect as a List, turn into an array after
List urls = new Vector();
File webinf = new File(dir, "WEB-INF");
// put classes/ on the classpath
File classes = new File(webinf, "classes");
if ((classes != null) && (classes.isDirectory()))
{
try
{
URL url = new URL("file:" + classes.getCanonicalPath() + "/");
urls.add(url);
}
catch (Throwable t)
{
}
}
// put each .jar file onto the classpath
File lib = new File(webinf, "lib");
if ((lib != null) && (lib.isDirectory()))
{
File[] jars = lib.listFiles(new FileFilter()
{
public boolean accept(File file)
{
return (file.isFile() && file.getName().endsWith(".jar"));
}
});
if (jars != null)
{
for (int j = 0; j < jars.length; j++)
{
try
{
URL url = new URL("file:" + jars[j].getCanonicalPath());
urls.add(url);
}
catch (Throwable t)
{
}
}
}
}
// make the array from the list
URL[] urlArray = (URL[]) urls.toArray(new URL[urls.size()]);
// make the classloader - my loader is parent
URLClassLoader loader = new URLClassLoader(urlArray, getClass().getClassLoader());
return loader;
}
Component的ClassLoader和每一个web应用有共同的父ClassLoader,这个父ClassLoader可以看见/WEB-INF/classes下的类,和所有/WEB-INF/lib/下的jar包。Component的ClassLoader和web应用的ClassLoader的行为很像,只是有一点不同,这个ClassLoader是标准的URLClassloader,它会首先委托自己的父ClassLoader来加载类,只有当父ClassLoader无法找到类时,才会自己的类路径下去寻找。而web应用的ClassLoader的行为和标准的ClassLoader的行为是不一样的,因为根据Servlet规范,web应用的ClassLoader需要首先寻找自己的类路径下的类和jar包,然后才会去寻找父ClassLoader的类路径。
Google日历不支持事件按农历年循环。要添加农历生日事件,就只能一年一年的添加,用起来很不方便,于是自己操刀,做了一个可以添加按年循环的农历生日事件的工具。
应用基于GAE,地址是http://spur.appspot.com,操作界面如下图:

填写相关信息和需要重复的年份范围,导出ics文件,再将ics文件导入到Google日历里就可以了。
目前只能通过导ics文件的方式添加,后续会考虑通过gdata访问Google Calendar API的方式。

从Greg博客里的书评知道了这本书:Search User Interfaces。
这本书的官网上提供了免费的HTML版,如果你想买一本的话,到这里 。
我还没有看这本书,不过从Greg列出的节录来看,本书对于搜索的界面设计做了深入的研究,并且回答了我们经常会提出 的一些问题,摘录几条:
为什么搜索的界面如此简单?
人们做搜索是要到达一个目的地,而搜索本身不是目的。当人们在寻找信息的时候,他们通常是沉浸在一些重要的工作里,而不希望被打断,阅读时被分散的精力越小,搜索的可用性越高。
关于细节:
一些小的细节的改变可能会产生很大的效果,比如,Franzen and Karlgren发现将表单的输入项变宽会使用户倾向于输入更多的内容。
另外一个例子是,Google早期的拼写纠正提示是放在搜索结果页面上方的。而用户更关注搜索结果,他们通常不会注意到这个提示,他们会一直向下拉页面找相关的结果,在搜索词拼错的情况下,用户很难找到他们想要搜索的结果,但并不会意识到自己输错词了,因为上方的拼写提示已经被忽略。于是用户会放弃搜索并抱怨。后来Google就改成了在页面下方也同样放上拼写纠正提示。
如何给用户提供信息?
提供一条带有用户搜索词的摘要信息是很重要的,它被称为keyword-in-context(KWIC)提取。通常提取摘要是为了概述文章的主要内容,这可能不包含用户输入的关键词,面向查询(query-oriented)的关键词提取则需要展示关键词是怎样在文档内被展示的。
高亮关键词有助于将用户的注意力吸引到文档中最和关键词相关的部分,并展示出关键词和文档中相应的词的语义是否相近。对过多的词做高亮显示也是不合适的,这会减少高亮所带来的好处。
用户在搜索过程中的行为
50%的搜索过程包含着重新构造查询词,拼写纠正的使用次数大约占其展示次数的35%。
10-15%的查询包含着拼写错误,用户更喜欢这些错误被自动纠正,而不是通过鼠标点击纠正词进行再次查询。
用户很少看第一页以外的搜索结果,如果他们在第一页无法找到想要的结果,用户通常会放弃或者更换查询词。用户期望他们想出的结果出现在前两条。

继High Performance Web Sites之后,Steve Souders在今年6月又推出了他的新作Even Faster Web Sites。前一本书完全是由Steve Souders一人所写,后一本书则Steve Souders与从多高手合作完成的。
High Performance Web Sites一书从网站配置方面阐述了各种网站性能调优的方法,其中所列出的各条原则对于开发者来说已经是耳熟能详。Even Faster Web Sites一书不仅提出了一些新的原则,而且深入分析了程序设计方面细节对于网站性能的影响。
Steve Souders的两本书给人的感觉就是压榨!压榨!从通常开发者所不关注的地方压榨出最后一滴性能,其结论多有出人意料之处,其分析问题的过程和方法颇值得学习。
列一下各章目录:
- Chapter 1. Understanding Ajax Performance, Douglas Crockford.
- Chapter 2. Creating Responsive Web Applications, Ben Galbraith and Dion Almaer.
- Chapter 3. Splitting the Initial Payload.
- Chapter 4. Loading Scripts Without Blocking.
- Chapter 5. Coupling Asynchronous Scripts.
- Chapter 6. Positioning Inline Scripts.
- Chapter 7. Writing Efficient JavaScript, Nicholas C. Zakas.
- Chapter 8. Scaling with Comet, Dylan Schiemann.
- Chapter 9. Going Beyond Gzipping, Tony Gentilcore.
- Chapter 10. Optimizing Images, Stoyan Stefanov and Nicole Sullivan.
- Chapter 11. Sharding Dominant Domains.
- Chapter 12. Flushing the Document Early.
- Chapter 13. Using Iframes Sparingly.
- Chapter 14. Simplifying CSS Selectors.
- Appendix. Performance Tools.