基于注解与自动扫描的某些实体没被映射的原因之一
在尝试修改基于SpringSide 3.1.3的mini-web过程中,又遇到了一个奇怪的问题。一个hql如下:
StringBuilder hql = new StringBuilder();
hql.append("SELECT new ");
hql.append(CommentBean.class.getName());
hql.append(" (o,u.name,u.image) ");
hql.append(" FROM ");
hql.append(Comment.class.getSimpleName()).append(" o, ");
hql.append(User.class.getSimpleName()).append(" u ");
hql.append(" WHERE o.userId=u.id AND o.infoId=? ");
hql.append(" ORDER BY o.commentTime DESC");
Comment这个实体类的全限定名是:org.shoopman.entity.Comment
但在运行时却报错:
Comment is not mapped [SELECT new org.shoopman.service.dto.CommentBean (o,u.name,u.image) FROM Comment o, org.shoopman.entity.user.User u WHERE o.userId=u.id AND o.infoId=? ORDER BY o.commentTime DESC]
org.hibernate.hql.ast.QuerySyntaxException: Comment is not mapped [SELECT new org.shoopman.service.dto.CommentBean (o,u.name,u.image) FROM Comment o, org.shoopman.entity.user.User u WHERE o.userId=u.id AND o.infoId=? ORDER BY o.commentTime DESC]
at org.hibernate.hql.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:181)
at org.hibernate.hql.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:110)
at org.hibernate.hql.ast.tree.FromClause.addFromElement(FromClause.java:93)
at org.hibernate.hql.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:277)
而其他实体的查询却没有问题。先看一下spring的applicationContext.xml关于Hibernate的配置:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">${hibernate.ehcache_config_file}</prop>
</props>
</property>
<property name="packagesToScan" value="org.shoopman.entity.*" />
</bean>
问题就在这个自动描述packagesToScan的配置上。如上的配置,Spring只会扫描到org.shoopman.entity这个包下的子包里的实体类,而直接放在这个包下的实体却没有描述。
无奈,只好在新建一个org.shoopman.entity.common包,把Comment移到此包下。到此,问题解决。
题外话:看上面的hql生成过程,在使用User这个实体类时,我用的是User.class.getSimpleName(),为什么Hibernate在生成hql却是全限定名。当把Comment移到common包下,控制台里打印出来的hql又是SimpleName。这是Hibernate的一个容错处理吗?
GFW又K了一个ghs的IP?
下午还能通过blog.shoopman.org来访问我的博客,刚才通过Windows Live Write来发表文章,却不成功。现在只能通过shoopmanlog.appspot.com来访问了。看一下友情连接里的GAE伙伴们,徐明的挂了,Keengle的也挂了,Qing的还没有。这样还好,不用费那么多事去找新的ghs IP了。
5月26日左右,ghs挂了一个IP;6月15日左右,又挂了一个;今天,7月16日,再一个。看来,在GFW的驱动下,广大中国的GAE Blogger们都要习惯每月一次的修改域名A记录或者类似的事情了。
已在GoDaddy修改了域名的A记录,看多久后才能生效吧。现在,只有等待并且忍受,等哪天实在忍受不了了,就去给美国增加一点儿GDP吧。
为Micolog添加了Google翻译功能
今天在Google Analytics里看到,大概30%的IP流量是来自国外,而语言是en-us的流量更多(我的操作系统也是英文版的)。所以,如果能提供一个翻译按钮给那些老外,他们会更明白我在说什么。Google Translate已经提供了这样一个小工具,只需要简单的几个步骤,就可以往自己网站添加上这个翻译功能。下面就为我这个基于Micolog的博客加上这个小功能吧。
1. 获取代码
到http://translate.google.cn/translate_tools?hl=zh-CN去,代码马上就生成了,如下:
<script src="http://www.gmodules.com/ig/ifr?url=http://www.google.com/ig/modules/translatemypage.xml&up_source_language=zh-CN&w=160&h=60&title=&border=&output=js"></script>
其中,w和h这两个参数是控制Google翻译小工具的宽度和高度,可以自行修改。
2. 修改主题文件
找到你要添加小工具的网页,把上面的代码粘贴进去。比如我是在themes\default\templates\sidebar.html里添加了小工具。
3. 上传文件
至此,Google翻译小工具就添加进去了,你可以看一下页面右上方。样子是有点丑,将就一下了。
Google还提供了更多可以往自己网页里放的小工具,可以到http://www.google.com/webmasters/gadgets/找一下。
为Micolog添加Google自定义搜索
由于Google App Engine数据存储与关系数据库不一样,Micolog也无法像传统应用那样提供基于数据库某些字段的搜索。作为搜索引擎老大,Google对自家的App Engine里的内容收录还是相当及时的,比如我这博客发布的文章,一般两天内就收录了。因此我们可以利用Google搜索引擎收录索引来解决Blog站内搜索这个问题。Micolog默认主题里就提供的站内搜索,即加上site:blog.shoopman.org,比如这个搜索。有个不爽的地方,就是搜索结果页完全跳出了自己的博客。于是,我利用了Google自定义搜索来为Micolog博客加入站内搜索。
先看一下最后的效果:
http://blog.shoopman.org/google-search?q=java&cx=004316150475193113567%3Ah9vz95zkor4&cof=FORID%3A11&ie=UTF-8
还不错吧,页面的顶部、底部以及右边都是Micolog原本的风格和内容,只有页面主体是搜索结果。下面开始动手了。
1. 创建Google自定义搜索引擎
用你的Google帐号登陆到 http://www.google.com/coop/cse/,按照提示创建一个自定义搜索(图)。
2. 获取代码
转到自定义搜索的控制面板–获取代码,选择”使用 iframe 使搜索结果位于我的网站上”,并指定搜索结果页地址,如/google-search(图),这时可得到搜索框和搜索结果页的代码,先保存一下。
3. 修改Micolog主题
转到你正使用的主题,如我是用默认主题,即到themes\default\templates下,编辑base.html,把搜索框处理的代码修改如下:
<form method="get" id="cse-search-box" action="/google-search">
<input type="text" value="Search…" onfocus="if (this.value == ‘Search…’) {this.value = ”;}" onblur="if (this.value == ”) {this.value = ‘Search…’;}" name="q" id="s" />
<input type="image" src="/themes/{{ blog.theme_name }}/images/transparent.gif" id="go" alt="Search" title="Search" />
<input type="hidden" name="cx" value="004316150475193113567:h9vz95zkor4" />
<input type="hidden" name="cof" value="FORID:11" />
<input type="hidden" name="ie" value="UTF-8" />
</form>
注意,form中action处的google-search后面再说,名为cx的隐藏域应该是自己自定义搜索的标识。在第二步里的搜索框代码里,还有一行<script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse-search-box&lang=zh-Hans"></script>,不要加进去,这会改变原来搜索框的样式。
4. 创建搜索结果Javascript
由于无法在Micolog的编辑器添加有效的Javascript代码,我们将Google自定义搜索结果页的Javascript代码放到一个独立的js文件里,内容如下:
var googleSearchIframeName = "cse-search-results";
var googleSearchFormName = "cse-search-box";
var googleSearchFrameWidth = 600;
var googleSearchDomain = "www.google.com";
var googleSearchPath = "/cse";
把此文件命名为google_search.js,并放在/static/js目录下。(你可以修改js文件名及路径,但建议放在static目录下)
5. 创建搜索结果显示页
登陆到Micolog后台管理,创建页面,内容如下:
<div id="cse-search-results"> </div>
<script src="http://blog.shoopman.org/static/js/google_search.js" type="text/javascript"></script><script src="http://www.google.com/afsonline/show_afs_search.js" type="text/javascript"></script>
其中前面的js就是上一步创建的那个js文件。
在此页面里,还要指定名称(slug)为google-search,即第2步、第3步中搜索框Form中的action;把页面级别设置为1,那么在博客顶部的菜单就不会出现此页面的连接(图)。
6. 上传文件
把修改过的base.html和新建的google_search.js上传到Google App Engine中,就大功告成了!
Update1:这文章在发布后几分钟就被Google收录了,很快!
Update2:这里有个更方便的办法:http://www.houkai.com/2009/07/7/google-search.html
SessionListener与Spring
在SessionListener里,需要使用被Spring管理起来的其他Bean,如某些DAO或者Manager、Service。在实现这个过程是这样的:
1.把SessionListener也交给Spring来管理。
由于使用了Spring Annotation,所以在SessionListener上加了@Service标记,并且把需要用到的其他Bean在SessionListener中定义为成员变量并加上@Autowried。但结果是失败的,SessionListener里的其他Bean没有被注入。
2.直接通过Spring的Context来获得相应的DAO等的实例。
Spring Annotation默认是ByType的方式来注入Bean,因此写了一个静态方法来获取相关的Bean,不知道有没有其他更好的办法,请告诉我吧:
/**
* 根据类型从Spring中取得Bean
*
* @param <T> Bean的类型
* @param beanClass Bean的Class对象
* @param servletContext
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getSpringBean(Class<T> beanClass,
ServletContext servletContext) {
WebApplicationContext appContext = (WebApplicationContext) servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
Map beans = appContext.getBeansOfType(beanClass);
if (beans == null || beans.isEmpty()) {
return null;
}
return (T) beans.values().toArray()[0];
}
一次WebService调试的遗留问题
在上一篇《一次WebService调试》里,简单的完成了WebService的开发与调用,但还存在着两个问题:
1.客户端生成的代码里,RemoteUser这个对象,每个属性并不是预想中的String,而是javax.xml.bind.JAXBElement<String>。
这个问题可以使用aegis来处理,在RemoteUser所在的包下添加一个RemoteUser.aegis.xml文件,如下:
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns:tns="http://www.xxx.com/services/nopService”>
<mapping name="tns:RemoteUser">
<property minOccurs="1" maxOccurs="1" nillable="true" name="fax" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="mail" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="mobile" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="name" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="post" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="pwd" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="sex" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="telephone" />
<property minOccurs="1" maxOccurs="1" nillable="true" name="uid" />
</mapping>
</mappings>
2.客户端生成的代码里,getAccount()的返回值GetAccountResponse里的并不是预想中的List<RemoteUser>,而是一个ArrayOfRemoteUser对象中再包了一个类型为List<RemoteUser>的属性。
现在这个问题还没有解决。。。。。。
一次WebService调试
项目背景:中移动某省的一个管理系统A,而本公司另一产品线则负责此移动公司的门户P,现在需要进行用户数据的同步及实现单点登陆。门户P有一套简单的关于用户数据同步的WebService接口规范,定义了方法名、返回值及参数。
系统A技术框架:JDK1.4 + Struts1.2.x + Spring + XFire。
我对XFire不太熟悉,幸好系统A原先就已经使用了XFire向其他系统提供了WebService,我只需要依葫芦画瓢就行。还是简单的说一下XFire及与Spring集成的配置吧。
1. web.xml的配置,配置xfire的servlet及URI映射
<servlet>
<servlet-name>xfire</servlet-name>
<servlet-class>org.codehaus.xfire.spring.XFireSpringServlet</servlet-class>
</servlet><servlet-mapping>
<servlet-name>xfire</servlet-name>
<url-pattern>/services/*</url-pattern>
</servlet-mapping>
2. services.xml的配置,位于WEB-INF/META-INF/xfire下,指定WebService的名称、路径及类接口及实现类
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xfire.codehaus.org/config/1.0">
<service>
<name>NopService</name>
<namespace>/services/NopService</namespace>
<serviceClass>com.xxx.project.service.NopService</serviceClass>
<implementationClass>com.xxx.project.service.NopServiceImpl</implementationClass>
</service>
</beans>
服务接口类如下:
package com.xxx.project.service
public interface NopService {
/**
* 获得用户信息
* @param flag
* @return
*/
public List getAccount(String flag);}
getAccount返回的List包含的是对象是RemoteUser,一个纯粹的JavaBean,如下:
package com.xxx.project.service
public class RemoteUser {
private String uid;
private String name;
private String pwd;
private String sex;
private String mobile;
private String telephone;
private String mail;
private String post;
private String fax;/** 省略掉的getter与setter */
}
3. spring beans 的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC
"-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<import resource="classpath:org/codehaus/xfire/spring/xfire.xml" />
<bean
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/nopService">
<ref bean="xfire.nopService"/>
</entry>
</map>
</property>
</bean>
<!– common xfire exporter,set the parent to this bean –>
<bean id="commonXFireExporter"
class="org.codehaus.xfire.spring.remoting.XFireExporter"
lazy-init="false" abstract="true">
<property name="serviceFactory" ref="xfire.serviceFactory" />
<property name="xfire" ref="xfire" />
</bean>
<!– Declare a parent bean with all properties common to both services –>
<bean id="xfire.nopService" parent="commonXFireExporter">
<property name="name" value="nopService"/>
<property name="namespace" value="http://www.xxx.com/services/nopService"/>
<property name="serviceBean" ref="nopService" />
<property name="serviceInterface"
value="com.xxx.project.service.NopService" />
</bean>
</beans>
4.aegis的配置,由于接口上的返回类型是集合类,需要配置一个NopService.aegis.xml的文件来告诉xfire包含在集合中的是什么类型,注意到文件名是接口名+.aegis.xml,位置跟接口的一样。
<?xml version="1.0" encoding="UTF-8"?>
<mappings>
<mapping>
<method name="getAccount">
<return-type
componentType="com.xxx.project.service.RemoteUser"
minOccurs="1" maxOccurs="1" />
</method>
</mapping>
</mappings>
部署到Tomcat中,通过浏览器访问http://localhost:8080/services/nopService?wsdl,得到了类似如下的WSDL:
<?xml version="1.0
" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://www.xxx.com/services/nopService"
xmlns:tns="http://www.xxx.com/services/nopService" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://project.xxx.com"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc11="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:soapenc12="http://www.w3.org/2003/05/soap-encoding" xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<wsdl:types>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified"
targetNamespace="http://www.xxx.com/services/nopService">
<xsd:element name="getAccount">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="1" name="in0"
nillable="true" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="getAccountResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element maxOccurs="1" minOccurs="1" name="out"
nillable="true" type="ns1:ArrayOfRemoteUser" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="qualified" elementFormDefault="qualified"
targetNamespace="http://project.xxx.com">
<xsd:complexType name="ArrayOfRemoteUser">
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0"
name="RemoteUser" nillable="true" type="ns1:RemoteUser" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="RemoteUser">
<xsd:sequence>
<xsd:element minOccurs="0" name="fax" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="mail" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="mobile" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="name" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="post" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="pwd" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="sex" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="telephone" nillable="true"
type="xsd:string" />
<xsd:element minOccurs="0" name="uid" nillable="true"
type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types><!– 省略了部分 –>
</wsdl:definitions>
wsdl文件都出来,算是成功一半了,接着就是生成客户端来测试了。像Axis和cfx一样,XFire也带有代码生成工具,也有ant任务,如下:
<project name="xfire-client" default="wsgen" basedir=".">
<path id="classpath">
<fileset dir="lib">
<include name="**/*.jar" />
</fileset>
</path>
<target name="wsgen">
<taskdef name="wsgen" classname="org.codehaus.xfire.gen.WsGenTask" classpathref="classpath" />
<wsgen outputDirectory="appClientModule" wsdl="http://localhost:8080/services/nopService?wsdl" package="com.xxx.project.client" overwrite="true" />
</target>
</project>
客户代码生成后,再写一个测试类来调用WebService:
public class NopServiceTest {
public static void main(String[] args) {
nopServiceClient client = new nopServiceClient();
nopServicePortType service = client.getnopServiceHttpPort("http://localhost:8080/services/nopService");
ArrayOfRemoteUser rets = service.getAccount("");
List<RemoteUser> list = rets.getRemoteUser();
System.out.println(list.size());
}}
OK!测试成功。赶紧打包部署到测试服务器上,让门户P的开发人员来测试。对方很快来了反应:wsdl不符合他们的规范,在将wsdl导入门户P时报错—-无法识别类型“ns1:ArrayOfRemoteUser”。我能得到的错误信息就这么多,以及一份符合所谓规范的wsdl样例,貌似是由Axis生成的:
<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions targetNamespace="http://security.interfaces.eoms.yyy.com"
xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:impl="http://security.interfaces.eoms.yyy.com"
xmlns:intf="http://security.interfaces.eoms.yyy.com" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!–
WSDL created by Apache Axis version: 1.3 Built on Oct 05, 2005
(05:23:37 EDT)
–>
<wsdl:types>
<schema elementFormDefault="qualified"
targetNamespace="http://security.interfaces.eoms.yyy.com" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="getAccount">
<complexType>
<sequence>
<element name="flag" type="xsd:string" />
</sequence>
</complexType>
</element>
<element name="getAccountResponse">
<complexType>
<sequence>
<element maxOccurs="unbounded" name="getAccountReturn"
type="impl:RemoteUser" />
</sequence>
</complexType>
</element>
<complexType name="RemoteUser">
<seque
nce>
<element name="fax" nillable="true" type="xsd:string" />
<element name="mail" nillable="true" type="xsd:string" />
<element name="mobile" nillable="true" type="xsd:string" />
<element name="name" nillable="true" type="xsd:string" />
<element name="post" nillable="true" type="xsd:string" />
<element name="pwd" nillable="true" type="xsd:string" />
<element name="sex" nillable="true" type="xsd:string" />
<element name="telephone" nillable="true" type="xsd:string" />
<element name="uid" nillable="true" type="xsd:string" />
</sequence>
</complexType>
</schema>
</wsdl:types>
由于非技术原因,此问题得由我来处理。通过对比两个wsdl文件、长时间及多次的测试,最终锁定了门户P无法识别”ns1:ArrayOfRemoteUser”的原因:系统A的wsdl有两个<xsd:schema>节点。又经过多次尝试,才终于把xfire生成的wsdl的schema节点合并为一个,方法是,把前面第3步的spring beans配置中的标红处修改如下,跟接口类颠倒的包名保持一致:
<bean id="xfire.nopService" parent="commonXFireExporter">
<property name="name" value="nopService"/>
<property name="namespace" value="http://service.project.xxx.com"/>
<property name="serviceBean" ref="nopService" />
<property name="serviceInterface"
value="com.xxx.project.service.NopService" />
</bean>
后来在网上找到可能的原因:https://issues.apache.org/jira/browse/CXF-1117,门户P的cxf包可能较旧,不支持多个xmlns。
Struts2中OGNL设置泛型属性值的问题
有这么一个封装了CRUD操作的Action基类:
public abstract class CrudAction extends ActionSupport{
protected T entity;
protected PK id;
public void setId(PK id) {
this.id = id;
}/** 显示新建或编辑页前的数据准备 */
public String input() throws Exception {
}
/** other code */
}
然后是一个继承此CrudAction的用户CRUD操作类:
public class UserAction extends CrudAction {
@Autowired
private UserManager userManager;
@Override
public String input() throws Exception {entity = userManager.get(id);
}
}
但是通过类似这样的连接http://localhost:8080/test/user!input.action?id=uid来访问时,在UserAction中得到的id值是一个长度为1的String数组,导致后面Hibernate查询报错了。不知道为什么获得的值是数组,一开始只好以最直接的办法来获取正确的id值:判断id是否为数组,如果是即取索引为0的值。
后来,尝试在子类用实际的类型覆盖id的setter方法:
@Override
public void setId(String id) {
this.id = id;
}
结果是可以正确的获得值,而不需要经过上面那丑陋的转换。