置頂隨筆 #
升級Spring3.1RC2 和Hibernate4.0.0CR7遇到的一些問題及解決
Spring3.1RC2支持
1. Quartz2
2. Hibernate4,
3. New HandlerMethod-based Support Classes For Annotated Controller Processing
4. Consumes and Produces @RequestMapping Conditions
5. Working With URI Template Variables In Controller Methods
6. Validation For @RequestBody Method Arguments //and so on....
7. Spring MVC 3.1 的annotation可以參看下http://starscream.iteye.com/blog/1098880
Hibernate 4可以查看http://community.jboss.org/wiki/HibernateCoreMigrationGuide40
下面主要說一下我在升級過程中遇到的一些問題及解決辦法。
l Maven的repository始終無法升級到SpringRC2,可能服務器有問題吧,目前暫時是從官方下載的整個SpringRC2的zip包。版本號是:3.1.0.RC2
l Hibernate可以從repository中升級到4.0.0.CR7,新增的依賴包有jandex-1.0.3.Final.jar,jboss-logging-3.1.0.CR2.jar,jboss-transaction-api_1.1_spec-1.0.0.Final.jar。
l Quartz升級到2.1.1,Ehcache-core升級到2.5.0
l Spring3.1取消了HibernateTemplate,因為Hibernate4的事務管理已經很好了,不用Spring再擴展了。所以以前的Dao需要改寫,直接調用Hibernate 的Session進行持久化。
l Spring的配置:
sessionFactory從org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean換成org.springframework.orm.hibernate4.LocalSessionFactoryBean
l Spring的配置:
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>改為
<prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop>
EhCacheRegionFactory使用配置:
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
l 使用Hibernate所有的openSession()改為getCurrentSession()
l Spring 的配置:Hibernate transactionManager從3改為4,如下:
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
l Spring @ResponseBody輸出是亂碼的問題:原來使用的是:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
改為:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name = "messageConverters">
<list>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class = "org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name = "supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
這樣比每個Controller都加上@RequestMapping(value = "/showLeft", method = RequestMethod.GET)
produces = "text/plain; charset=utf-8"方便的多。
l Blob,以前配置:
@TypeDefs({@TypeDef(name="clob",typeClass=ClobStringType.class),@TypeDef(name="blob",typeClass=BlobByteArrayType.class)})
@Lob
@Type(type="blob")
public byte[] getPic() {
return pic;
}
現在改為:
@Lob
public byte[] getPic() {
return pic;
}
簡單很多。
l 待續。。。
Shiro權限框架
開發系統中,少不了權限,目前java里的權限框架有SpringSecurity和Shiro(以前叫做jsecurity),對于SpringSecurity:功能太過強大以至于功能比較分散,使用起來也比較復雜,跟Spring結合的比較好。對于初學Spring Security者來說,曲線還是較大,需要深入學習其源碼和框架,配置起來也需要費比較大的力氣,擴展性也不是特別強。
對于新秀Shiro來說,好評還是比較多的,使用起來比較簡單,功能也足夠強大,擴展性也較好。聽說連Spring的官方都不用Spring Security,用的是Shiro,足見Shiro的優秀。網上找到兩篇介紹:http://www.infoq.com/cn/articles/apache-shiro http://www.ibm.com/developerworks/cn/opensource/os-cn-shiro/,官網http://shiro.apache.org/ ,使用和配置起來還是比較簡單。下面只是簡單介紹下我們是如何配置和使用Shiro的(暫時只用到了Shiro的一部分,沒有配置shiro.ini文件)。
首先是添加過濾器,在web.xml中:
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
權限的認證類:
public class ShiroDbRealm extends AuthorizingRealm { @Inject private UserService userService ;
/** * 認證回調函數,登錄時調用. */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) UsernamePasswordToken token = (UsernamePasswordToken) authcToken; User user= userService.getUserByUserId(token.getUsername()); if (user!= null) { return new SimpleAuthenticationInfo(user.getUserId(), user.getUserId(), getName()); } else { return null; } } /** * 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用. */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String loginName = (String) principals.fromRealm(getName()).iterator().next(); User user= userService.getUserByUserId(loginName); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("common-user"); return info; } else { return null; } } } |
Spring的配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans > <description>Shiro Configuration</description> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroDbRealm" /> </bean> <bean id="shiroDbRealm" class="com.company.service.common.shiro.ShiroDbRealm" /> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/common/security/login" /> <property name="successUrl" value="/common/security/welcome" /> <property name="unauthorizedUrl" value="/common/security/unauthorized"/> <property name="filterChainDefinitions"> <value> /resources/** = anon /manageUsers = perms[user:manage] /** = authc </value> </property> </bean> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans> |
登錄的Controller:
@Controller @RequestMapping(value = "/common/security/*") public class SecurityController { @Inject private UserService userService; @RequestMapping(value = "/login") public String login(String loginName, String password, User user = userService.getUserByLogin(loginName); if (null != user) { setLogin(loginInfoVO.getUserId(), loginInfoVO.getUserId()); return "redirect:/common/security/welcome"; } else { return "redirect:/common/path?path=showLogin"; } }; public static final void setLogin(String userId, String password) { Subject currentUser = SecurityUtils.getSubject(); if (!currentUser.isAuthenticated()) { //collect user principals and credentials in a gui specific manner //such as username/password html form, X509 certificate, OpenID, etc. //We'll use the username/password example here since it is the most common. //(do you know what movie this is from? ;) UsernamePasswordToken token = new UsernamePasswordToken(userId, password); //this is all you have to do to support 'remember me' (no config - built in!): token.setRememberMe(true); currentUser.login(token); } };
@RequestMapping(value="/logout") @ResponseBody public void logout(HttpServletRequest request){ Subject subject = SecurityUtils.getSubject(); if (subject != null) { subject.logout(); } request.getSession().invalidate(); }; } |
注冊和獲取當前登錄用戶:
public static final void setCurrentUser(User user) { Subject currentUser = SecurityUtils.getSubject(); if (null != currentUser) { Session session = currentUser.getSession(); if (null != session) { session.setAttribute(Constants.CURRENT_USER, user); } } }; public static final User getCurrentUser() { Subject currentUser = SecurityUtils.getSubject(); if (null != currentUser) { Session session = currentUser.getSession(); if (null != session) { User user = (User) session.getAttribute(Constants.CURRENT_USER); if(null != user){ return user; } } } }; |
需要的jar包有3個:shiro-core.jar,shiro-spring.jar,shiro-web.jar。感覺shiro用起來比SpringSecurity簡單很多。
在我們開發的一個系統中,有定時任務,自然就想到了Quartz,由于框架采用的Spring,Quartz跟Spring的集成也非常簡單,所以就把Quartz配置到框架中,當系統啟動后,定時任務也就自動啟動。在開發的過程中一直沒有發現問題,但是最后上線的時候,采用的是weblogic cluster,啟動了4個節點,發現有的定時任務執行了不止一次,才恍然大悟,4個節點啟動了4個應用,也就啟動了4個定時任務,所以在同一個時間定時任務執行了不止一次。去網上搜索,發現Quartz也支持cluster,但是我覺得就我們的系統而言,沒有必要采用cluster的定時任務,也許是比較懶吧,就想讓定時任務只執行一次。在網上搜到了robbin的一篇文章(http://robbin.iteye.com/blog/40989 ),發現把quartz集中到webapp當中還是有一定的風險,同時同一個時間點執行也不止一次。Robbin的解決辦法就是自己單獨啟動一個Job Server,來quartz跑job,不要部署在web容器中。
我也比較同意這個辦法。鑒于時間比較緊,就想有沒有比較方便的方法。其實把原來的webapp當做一個quartz的容器就可以了。可以自己寫一個線程來跑應用,再寫一個command啟動這個線程就可以了。線程類很簡單,如下:
public class StartServer { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( System.out.println("start server...."); while (true) { try { Thread.sleep(900); } catch (InterruptedException ex) { } } }; } |
去掉了系統的controller配置servlet.xml,運行這個類就可以了。
在web-inf目錄下寫一個command來啟動這個java類:
setlocal ENABLEDELAYEDEXPANSION if defined CLASSPATH (set CLASSPATH=%CLASSPATH%;.) else (set CLASSPATH=.) FOR /R .\lib %%G IN (*.jar) DO set CLASSPATH=!CLASSPATH!;%%G Echo The Classpath definition is==== %CLASSPATH% set CLASSPATH=./classes;%CLASSPATH% java com.company.job.StartServer |
這個command需要把需要的jar(web-inf/lib中)包都放到classpath中。
每次啟動的時候執行這個command就可以了。跟原來的應用分開了,調試起定時任務也不用影響到原來的應用,還是比較方便的。部署的時候原樣拷貝一份,然后執行這個command就好了,部署起來也比較方便。
接上一篇Hibernate 動態HQL(http://www.aygfsteel.com/ghostzhang/archive/2011/09/08/358320.html ),開發中經常需要修改SQL或者HQL的語句,但是每次都要重啟服務器才能使之起作用,就想到在使用Spring配置多語言時有一個ReloadableResourceBundleMessageSource.java類,可以配置動態加載多語言文件,為了配合動態HQL并實現修改HQL語句不用重啟服務器,可以參考下這個類的實現。Java代碼如下:(ReloadableDynamicHibernate.java)
2 private final Map<String, XmlHolder> cachedXmls = new HashMap<String, XmlHolder>();
3 private org.springframework.beans.factory.xml.DocumentLoader documentLoader = new org.springframework.beans.factory.xml.DefaultDocumentLoader();
4
5 public void afterPropertiesSet() throws Exception {
6 refreshLoad2Cache(true);
7 };
8
9 protected String getSqlByName(String queryKey) {
10 refreshLoad2Cache(false);
11 Collection<XmlHolder> xmlHolders = cachedXmls.values();
12 for (XmlHolder holder : xmlHolders) {
13 String qlString = holder.getQl(queryKey);
14 if (StringUtils.isNotEmpty(qlString)) {
15 return qlString;
16 }
17 }
18 throw new RuntimeException("can not find ql in xml.");
19 };
20
21 private void refreshLoad2Cache(boolean isForce) {
22 for (int i = 0; i < fileNames.size(); i++) {
23 String fileName = ((String) fileNames.get(i)).trim();
24 if (resourceLoader instanceof ResourcePatternResolver) {
25 try {
26 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(fileName);
27 for (Resource resource : resources) {
28 getXmls(resource,isForce);
29 }
30 } catch (IOException ex) {
31 throw new RuntimeException("Could not resolve sql definition resource pattern [" + fileName + "]", ex);
32 }
33 } else {
34 Resource resource = resourceLoader.getResource(fileName);
35 getXmls(resource,isForce);
36 }
37 }
38 };
39
40 protected XmlHolder getXmls(Resource resource, boolean isForce) {
41 synchronized (this.cachedXmls) {
42 String filename = resource.getFilename();
43 XmlHolder cachedXmls = this.cachedXmls.get(filename);
44 if (cachedXmls != null && (cachedXmls.getRefreshTimestamp() < 0 || cachedXmls.getRefreshTimestamp() > System.currentTimeMillis())) {
45 return cachedXmls;
46 }
47 return refreshXmls(resource, cachedXmls, isForce);
48 }
49 };
50
51 protected XmlHolder refreshXmls(Resource resource, XmlHolder xmlHolder, boolean isForce) {
52 String filename = resource.getFilename();
53 long refreshTimestamp = System.currentTimeMillis();
54 if (resource.exists()) {
55 long fileTimestamp = -1;
56 try {
57 fileTimestamp = resource.lastModified();
58 if (!isForce && xmlHolder != null && xmlHolder.getFileTimestamp() == fileTimestamp) {
59 if (LOGGER.isDebugEnabled()) {
60 LOGGER.debug("Re-caching properties for filename [" + filename + "] - file hasn't been modified");
61 }
62 xmlHolder.setRefreshTimestamp(refreshTimestamp);
63 return xmlHolder;
64 }
65 } catch (IOException ex) {
66 if (LOGGER.isDebugEnabled()) {
67 LOGGER.debug(resource + " could not be resolved in the file system - assuming that is hasn't changed", ex);
68 }
69 fileTimestamp = -1;
70 }
71 try {
72 Map qlMap = loadQlMap(resource);
73 xmlHolder = new XmlHolder(qlMap, fileTimestamp);
74 } catch (Exception ex) {
75 if (LOGGER.isWarnEnabled()) {
76 LOGGER.warn("Could not parse properties file [" + resource.getFilename() + "]", ex);
77 }
78 xmlHolder = new XmlHolder();
79 }
80 } else {
81 if (LOGGER.isDebugEnabled()) {
82 LOGGER.debug("No properties file found for [" + filename + "] - neither plain properties nor XML");
83 }
84 xmlHolder = new XmlHolder();
85 }
86 xmlHolder.setRefreshTimestamp(refreshTimestamp);
87 this.cachedXmls.put(filename, xmlHolder);
88 return xmlHolder;
89 };
90
91 protected Map<String,String> buildHQLMap(Resource resource) throws Exception {
92 Map<String, String> qlMap = new HashMap<String, String>();
93 try {
94 InputSource inputSource = new InputSource(resource.getInputStream());
95 org.w3c.dom.Document doc = this.documentLoader.loadDocument(inputSource, null, null, org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_NONE, false);
96 Element root = doc.getDocumentElement();
97 List<Element> querys = DomUtils.getChildElements(root);
98 for(Element query:querys){
99 String queryName = query.getAttribute("name");
100 if (StringUtils.isEmpty(queryName)) {
101 throw new Exception("DynamicHibernate Service : name is essential attribute in a <query>.");
102 }
103 if(qlMap.containsKey(queryName)){
104 throw new Exception("DynamicHibernate Service : duplicated query in a <query>.");
105 }
106 qlMap.put(queryName, DomUtils.getTextValue(query));
107 }
108 } catch (Exception ioe) {
109 throw ioe;
110 }
111 return qlMap;
112 };
113
114 protected Map loadQlMap(Resource resource) {
115 Map qlMap = new HashMap<String, String>();
116 InputStream is = null;
117 try {
118 is = resource.getInputStream();
119 return buildHQLMap(resource);
120 } catch (Exception e) {
121 e.printStackTrace();
122 } finally {
123 try {
124 if (null != is) {
125 is.close();
126 }
127 } catch (Exception e) {
128 e.printStackTrace();
129 }
130 }
131 return qlMap;
132 };
133
134 protected class XmlHolder {
135 private Map<String, String> qlMap; //查詢的映射
136 private long fileTimestamp = -1;
137 private long refreshTimestamp = -1;
138 public String getQl(String key) {
139 if (null != qlMap) {
140 return qlMap.get(key);
141 } else {
142 if (LOGGER.isErrorEnabled()) {
143 LOGGER.debug("error is occured in getQl.");
144 }
145 return "";
146 }
147 }
148
149 public XmlHolder(Map<String, String> qlMap, long fileTimestamp) {
150 this.qlMap = qlMap;
151 this.fileTimestamp = fileTimestamp;
152 }
153 public XmlHolder() {
154 }
155 public Map<String, String> getQlMap() {
156 return qlMap;
157 }
158 public long getFileTimestamp() {
159 return fileTimestamp;
160 }
161 public void setRefreshTimestamp(long refreshTimestamp) {
162 this.refreshTimestamp = refreshTimestamp;
163 }
164 public long getRefreshTimestamp() {
165 return refreshTimestamp;
166 }
167 }
168 }
Spring 配置如下:
<property name="sessionFactory" ref="sessionFactory" />
<property name="simpleTemplate" ref="simpleTemplate" />
<property name="fileNames">
<list>
<value>classpath*:hibernate/dynamic/dynamic-hibernate-*.xml</value>
</list>
</property>
</bean>
這樣就實現了每次修改SQL or HQL語句后不用重啟服務器,立刻看到結果,加快了開發速度。
在開發的時候,很多時候都遇到過需要動態拼寫SQL,有的是在配置文件中寫SQL,有的是在Java代碼中拼寫SQL,以配置文件拼SQL的可以拿IBatis為代表,但是很多時候是使用Hibernate的,這個時候就想要是Hibernate能像IBatis那樣寫就好了。
這個時候就想到了模板語言和配置文件的結合。模板引擎可以選擇Velocity,簡單而不失強大,配置文件可以模仿Hibernate的sql-query 的XML文件。
Sq-query的示例代碼如下(SQL or HQL):
<!DOCTYPE dynamic-hibernate PUBLIC "-//ANYFRAME//DTD DYNAMIC-HIBERNATE//EN"
"http://www.anyframejava.org/dtd/anyframe-dynamic-hibernate-mapping-4.0.dtd">
<dynamic-hibernate>
<query name="selectUserSQL">
<![CDATA[
SELECT USER_ID,NAME
FROM users_table Where 1=1
#if($name && $name.length() > 1)
AND name =:name
#end
]]>
</query>
<query name="selectUserHQL">
<![CDATA[
FROM users
Where 1=1
#if($name && $name.length() > 1)
AND name =:name
#end
]]>
</query>
在系統加載時,需要把配置文件加載到系統中。加載代碼關鍵部分如下:
2 public void afterPropertiesSet() throws Exception {
3 for (int i = 0; i < fileNames.size(); i++) {
4 String fileName = ((String) fileNames.get(i)).trim();
5 if (resourceLoader instanceof ResourcePatternResolver) {
6 try {
7 Resource[] resources=((ResourcePatternResolver) resourceLoader).getResources(fileName);
8 buildHQLMap(resources);
9 } catch (IOException ex) {
10 throw new Exception("Could not resolve sql definition resource pattern [" + fileName + "]", ex);
11 }
12 } else {
13 Resource resource = resourceLoader.getResource(fileName);
14 buildHQLMap(new Resource[] { resource });
15 }
16 }
17 }
18 protected void buildHQLMap(Resource[] resources) throws Exception {
19 for (int i = 0; i < resources.length; i++) {
20 buildHQLMap(resources[i]);
21 }
22 }
23 private void buildHQLMap(Resource resource) throws Exception {
24 try {
25 InputSource inputSource = new InputSource(resource.getInputStream());
26 org.w3c.dom.Document doc = this.documentLoader.loadDocument(inputSource, null, null, org.springframework.util.xml.XmlValidationModeDetector.VALIDATION_NONE, false);
27 Element root = doc.getDocumentElement();
28 List<Element> querys = DomUtils.getChildElements(root);
29 for(Element query:querys){
30 String queryName = query.getAttribute("name");
31 if (StringUtils.isEmpty(queryName)) {
32 throw new Exception("DynamicHibernate Service : name is essential attribute in a <query>.");
33 }
34 if(statements.containsKey(queryName)){
35 throw new Exception("DynamicHibernate Service : duplicated query in a <query>."+queryName);
36 }
37 statements.put(queryName, DomUtils.getTextValue(query));
38 }
39 } catch (SAXParseException se) {
40 throw se;
41 } catch (IOException ioe) {
42 throw ioe;
43 }
44 }
Spring的配置文件示例如下:
<property name="simpleTemplate" ref="simpleTemplate" />
<property name="fileNames">
<list>
<value>classpath*:hibernate/dynamic/dynamic-hibernate-*.xml</value>
</list>
</property>
</bean>
下一步是在使用時調用sql并調用模板方法,進行sql動態化。
還是DynamicHibernateImpl這個類
2 Context context = generateVelocityContext(params);
3 Query query = findInternal(queryName, context);
4 if (pageIndex > 0 && pageSize > 0) {
5 query.setFirstResult((pageIndex - 1) * pageSize);
6 query.setMaxResults(pageSize);
7 }
8 return query.list();
9 };
10 private Context generateVelocityContext(Map<String, Object> params) {
11 VelocityContext context = new VelocityContext();
12 if (null == params) {
13 return null;
14 }
15 Iterator<String> iterator = params.keySet().iterator();
16 while (iterator.hasNext()) {
17 String key = iterator.next();
18 Object value = params.get(key);
19 if (null == value) {
20 continue;
21 }
22 context.put(key, value);
23 }
24 return context;
25 };
26 private Query findInternal(String queryName, Context context) throws Exception {
27 String sql = findSQLByVelocity(queryName, context);
28 Query query = sessionFactory.getCurrentSession().createQuery(sql);
29 String[] namedParams = query.getNamedParameters();
30 setProperties(query, context, namedParams);
31 return query;
32 };
33 private String findSQLByVelocity(String queryName, Context context) throws Exception {
34 if (context == null)
35 context = new VelocityContext();
36 String sql = getSqlByName(queryName);
37 StringWriter writer = new StringWriter();
38 Velocity.evaluate(context, writer, "Hibernate", sql);
39 sql = writer.toString();
40 return sql;
41 };
42 protected String getSqlByName(String queryKey) {
43 return statements.get(queryKey);
44 }
就這些。
大家也許有更好的方法,歡迎交流。
QQ:24889356