Grails的核心依赖必须保证项目中版本一致!

好久没写语录式大标题了,实在是今天玩Grails受害很深很深。

Grails因为本身是通过ivy来管理依赖的,虽然后续的版本和maven的集成不断加深,但是本身还是通过ivy来解析pom.xml。也就是说最后的活还是ivy干的。如果你的项目比较大,选择了通过maven来管理项目,而你的依赖中又牵扯了很多其他的依赖,那么你危险了。

:: UNRESOLVED DEPENDENCIES ::

:: log4j#log4j;1.2.16: configuration not found in log4j#log4j;1.2.16: ‘master’. It was required from …;1.7.0 runtime

对maven用户这是一条多么发指的报错!(比如这个

它的原因其实是你的dependency中有!=1.2.16版本你的log4j,比如1.2.12,即使你在pom里将它添加到了exclusion中也没有用,必须要保持版本的一致!至于你用什么办法让他们保持一致,还是干脆放弃用maven管理你的grails项目:It’s up to you.

同样的问题还出现在其他核心依赖上,例如commons-pool。

Mapping Geometry in Grails and MySQL

针对地理数据的ORM,有一个Hibernate的扩展HibernateSpatial项目可以将JTS对象映射到MySQL/PostGIS/Oracle中。这个扩展同样可以用在Grails里,这里有一篇简单的介绍,关于在Grails和MySQL中管理地理数据:
http://www.grails.org/MySQL+GIS-Geometry+with+Grails

不过按照这个文章里介绍的方法用,很可能会遭遇这样的报错:

org.hibernate.MappingException: No Dialect mapping for JDBC type: 2003

这个问题最终在这里得到了解答:
http://n2.nabble.com/No-Dialect-mapping-for-JDBC-type-2003-td1141106.html
按照邮件列表里的反映,上面的配置在Postgis里是可以work的,但是如果用Mysql还需要指定JPA的columnDefinition,对应的Hibernate属性是sql-type。虽然作者承诺会在今后的版本里修改这个问题,不过眼下的M2版本还没有修正这个问题。为此,Grails的用户特地提出在Grails中加入sql-type的支持:
http://jira.codehaus.org/browse/GRAILS-3201
现在按照下面文档的说明,可以在mapping里指定sqlType了:
http://grails.org/doc/latest/ref/Database%20Mapping/column.html

实例代码里的domain定义应该改成:

import com.vividsolutions.jts.geom.Polygon
import org.hibernatespatial.GeometryUserType

public class MyPoly {
    String name
    Polygon poly

    static mapping = {
        poly type: GeometryUserType, sqlType:"GEOMETRY"
    }

}

于是,再也没有莫名其妙的No Dialect报错了。

Inside Grails Flash Scope

Grails的在Servlet API的基础上增加了一个非常实用的FlashScope,FlashScope的生命周期为两次请求(也就是在一次重定向)。它的典型应用是POST方式提交后显示服务器端发给用户的提示信息,在平时的应用中会经常使用。

Grails的FlashScope接口定义在org.codehaus.groovy.grails.web.servlet包中,这个接口继承了Map接口,并定义了一个方法next()。

Grails的默认实现在org.codehaus.groovy.grails.web.servlet包中,GrailsFlashScope。这个实现内部定义了两个Map(生命周期为两个请求),current和next,这两个Map不断滚动,保持在一个请求中可以且仅可以访问到当前和前一次请求的上下文。
[codesyntax lang="java"]
public void next() {
current.clear();
current = (HashMap)next.clone();
next.clear();
reassociateObjectsWithErrors(current);
}
[/codesyntax]

put的时候,Grails只把新制放到next表中,因为next将在下一次请求时继续保存
[codesyntax lang="java"]
public Object put(Object key, Object value) {
// create the session if it doesn’t exist
registerWithSessionIfNecessary();
if(current.containsKey(key)) {
current.remove(key);
}
storeErrorsIfPossible(next,value);

return next.put(key,value);
}
[/codesyntax]

get的时候,Grails在两个Map中查找
[codesyntax lang="java"]
public Object get(Object key) {
if(next.containsKey(key))
return next.get(key);
return current.get(key);
}
[/codesyntax]

此外,FlashScope本身还是被放在session中
[codesyntax lang="java"]
private void registerWithSessionIfNecessary() {
GrailsWebRequest webRequest = (GrailsWebRequest) RequestContextHolder.currentRequestAttributes();
HttpSession session = webRequest.getCurrentRequest().getSession(true);
if(session.getAttribute(GrailsApplicationAttributes.FLASH_SCOPE) == null)
session.setAttribute(GrailsApplicationAttributes.FLASH_SCOPE, this);
}
[/codesyntax]

然后,滚动FlashScope的行为在GrailsWebRequestFilter中被调用,GrailsWebRequestFilter继承自spring-web的OncePerRequestFilter
[codesyntax lang="java"]
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

// Set the flash scope instance to its next state. We do
// this here so that the flash is available from Grails
// filters in a valid state.
FlashScope fs = webRequest.getAttributes().getFlashScope(request);
fs.next();

}
[/codesyntax]