首先,不得不吐槽一下,現在還在用這些技術,真的稍微有點落後了。無奈自己遇上了這樣的事情,面對複雜的JSP頁面以及煩瑣的配置檔案,真得讓人看着就頭大,難道前後端分離加微服務它不香嘛?但是面對業務及種種原因,最後還是決定對現有的系統進行一下軟體版本的更新。廢話不多說了,這種事情既然讓我遇上了,就要認真負責的幹好。下面就開始記錄一下項目更新的全過程。
一、背景介紹
系統是ssh架構的系統,主要更新對象是JDK、Hibernate、Spring、Struts2、Tomcat,具體版本見下圖:
二、更新思路
這裡還要說一下,老大的要求是,在不影響系統運作的前提下,把上述軟體版本更新到最新版本。最新!!再我的再三勸阻下,最後決定不一定是最新,相對較新的版本即可(因為最新的各個軟體的相容性很有可能會出現問題)。在這個前提下,我的第一思路是,首先先把涉及到的jar包全部替換成最新版本,然後遇到問題後再一一排查。但是面對衆多的軟體jar包,各個版本之間的相容性,一個個試的話,那工作量可就太大了。于是我決定先确定一套可以互相相容的Spring、Hibernate、Struts2、JDK的版本,基于這套版本,再調整其他軟體。這裡在說一下Hibernate,本來确定的是5.x的版本,但是由于系統中使用的jbpm版本是4.4,而Hibernate5.x版本和jbpm6才相容。而jbpm如果想從4.4更新到6.x的版本,還需要對庫裡的資料表結構和資料進行修改和遷移,總的來說過程就很複雜了。綜合考慮後才決定使用Hibernate4.3的版本。
總結一下整體思路就是:
1、确定可以相容的各軟體架構的版本;
2、根據架構的版本,調整其他軟體的版本;
3、調整好版本後,解決遇到的問題,對相應的配置檔案及代碼進行調整;
4、系統可以運作了之後,進行測試,對出現的問題進行解決。
三、軟體jar包情況
這裡我沒想好如何向大家展示所有需要的jar包,于是就先把我更改了的jar包全貼在這裡了,如果有需要的朋友,可以私信聯系我。
antlr-2.7.7.jar |
aspectjrt.jar |
aspectjweaver.jar |
byte-buddy-1.10.2.jar |
c3p0-0.9.2.1.jar |
classmate-1.3.0.jar |
commons-fileupload-1.4.jar |
commons-io-2.6.jar |
commons-lang3-3.8.1.jar |
commons-logging-1.2.jar |
freemarker-2.3.28.jar |
geronimo-jta_1.1_spec-1.1.1.jar |
hibernate-c3p0-5.1.13.Final.jar |
hibernate-commons-annotations-4.0.4.Final.jar |
hibernate-core-4.3.1.Final.jar |
hibernate-jpa-2.1-api-1.0.0.Final.jar |
jandex-2.0.3.Final.jar |
javassist-3.20.0-GA.jar |
jboss-logging-3.3.0.Final.jar |
log4j-api-2.11.1.jar |
mchange-commons-java-0.2.3.4.jar |
mysql-connector-5.1.8.jar |
ognl-3.1.21.jar |
spring-aop-5.0.6.RELEASE.jar |
spring-aspects-5.0.6.RELEASE.jar |
spring-beans-5.0.6.RELEASE.jar |
spring-context-5.0.6.RELEASE.jar |
spring-context-indexer-5.0.6.RELEASE.jar |
spring-context-support-5.0.6.RELEASE.jar |
spring-core-5.0.6.RELEASE.jar |
spring-expression-5.0.6.RELEASE.jar |
spring-instrument-5.0.6.RELEASE.jar |
spring-jcl-5.0.6.RELEASE.jar |
spring-jdbc-5.0.6.RELEASE.jar |
spring-jms-5.0.6.RELEASE.jar |
spring-messaging-5.0.6.RELEASE.jar |
spring-orm-4.0.1.RELEASE.jar |
spring-oxm-5.0.6.RELEASE.jar |
spring-test-5.0.6.RELEASE.jar |
spring-tx-5.0.6.RELEASE.jar |
spring-web-5.0.6.RELEASE.jar |
spring-webflux-5.0.6.RELEASE.jar |
spring-webmvc-5.0.6.RELEASE.jar |
spring-websocket-5.0.6.RELEASE.jar |
struts2-core-2.5.20.jar |
struts2-spring-plugin-2.5.14.1.jar |
org.springframework.aop-3.0.5.RELEASE.jar |
org.springframework.asm-3.0.5.RELEASE.jar |
org.springframework.aspects-3.0.5.RELEASE.jar |
org.springframework.beans-3.0.5.RELEASE.jar |
org.springframework.context.support-3.0.5.RELEASE.jar |
org.springframework.context-3.0.5.RELEASE.jar |
org.springframework.core-3.0.5.RELEASE.jar |
org.springframework.expression-3.0.5.RELEASE.jar |
org.springframework.instrument.tomcat-3.0.5.RELEASE.jar |
org.springframework.instrument-3.0.5.RELEASE.jar |
org.springframework.jdbc-3.0.5.RELEASE.jar |
org.springframework.jms-3.0.5.RELEASE.jar |
org.springframework.orm-3.0.5.RELEASE.jar |
org.springframework.oxm-3.0.5.RELEASE.jar |
org.springframework.test-3.0.5.RELEASE.jar |
org.springframework.transaction-3.0.5.RELEASE.jar |
org.springframework.web.portlet-3.0.5.RELEASE.jar |
org.springframework.web.servlet-3.0.5.RELEASE.jar |
org.springframework.web.struts-3.0.5.RELEASE.jar |
org.springframework.web-3.0.5.RELEASE.jar |
struts2-jasperreports-plugin-2.3.35.jar |
struts2-jfreechart-plugin-2.3.35.jar |
xwork-core-2.3.32.jar |
ejb3-persistence.jar |
hibernate-annotations.jar |
四、難點及解決辦法
1、jbpm4.4與Hibernate4不相容的問題
jbpm4.4預設使用的是Hibernate3,如果想要使用Hibernate4整合,則需要自己寫一下下面三個類,覆寫jbpm jar包裡的類。
代碼如下:
package org.jbpm.pvm.internal.lob;
import java.sql.SQLException;
import org.apache.struts2.ServletActionContext;
import org.hibernate.Hibernate;
import org.hibernate.LobHelper;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.internal.SessionImpl;
import org.jbpm.api.JbpmException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Repository;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
public class BlobStrategyBlob implements BlobStrategy {
public void set(byte[] bytes, Lob lob) {
if (bytes != null) {
lob.cachedBytes = bytes;
ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());
SessionFactory sessionFactory = (SessionFactory)ac.getBean("sessionFactory");
Session session = sessionFactory.getCurrentSession();
lob.blob = session.getLobHelper().createBlob(bytes);
}
}
public byte[] get(Lob lob) {
if (lob.cachedBytes != null) {
return lob.cachedBytes;
}
java.sql.Blob sqlBlob = lob.blob;
if (sqlBlob != null) {
try {
return sqlBlob.getBytes(1, (int) sqlBlob.length());
} catch (SQLException e) {
throw new JbpmException("couldn't extract bytes out of blob", e);
}
}
return null;
}
}
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.pvm.internal.processengine;
import org.hibernate.cfg.Configuration;
import org.jbpm.api.ProcessEngine;
import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.cfg.ConfigurationImpl;
import org.jbpm.pvm.internal.env.EnvironmentFactory;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.env.PvmEnvironment;
import org.jbpm.pvm.internal.env.SpringContext;
import org.jbpm.pvm.internal.wire.descriptor.ProvidedObjectDescriptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
/**
* this environment factory will see only the singleton beans.
*
* The created {@link SpringEnvironment}s will see the prototype beans and it
* will cache them.
*
* @author Andries Inze
*/
public class SpringProcessEngine extends ProcessEngineImpl implements EnvironmentFactory, ProcessEngine {
private static final Log log = Log.getLog(SpringProcessEngine.class.getName());
private static final long serialVersionUID = 1L;
private ApplicationContext applicationContext;
public static ProcessEngine create(ConfigurationImpl configuration) {
SpringProcessEngine springProcessEngine = null;
ApplicationContext applicationContext = null;
if (configuration.isInstantiatedFromSpring()) {
applicationContext = (ApplicationContext) configuration.getApplicationContext();
springProcessEngine = new SpringProcessEngine();
springProcessEngine.applicationContext = applicationContext;
springProcessEngine.initializeProcessEngine(configuration);
LocalSessionFactoryBean localSessionFactoryBean = springProcessEngine.get(LocalSessionFactoryBean.class);
Configuration hibernateConfiguration = localSessionFactoryBean.getConfiguration();
springProcessEngine.processEngineWireContext
.getWireDefinition()
.addDescriptor(new ProvidedObjectDescriptor(hibernateConfiguration, true));
springProcessEngine.checkDb(configuration);
} else {
String springCfg = (String) configuration.getProcessEngineWireContext().get("spring.cfg");
if (springCfg==null) {
springCfg = "applicationContext.xml";
}
applicationContext = new ClassPathXmlApplicationContext(springCfg);
springProcessEngine = (SpringProcessEngine) applicationContext.getBean("processEngine");
}
return springProcessEngine;
}
public EnvironmentImpl openEnvironment() {
PvmEnvironment environment = new PvmEnvironment(this);
if (log.isTraceEnabled())
log.trace("opening jbpm-spring" + environment);
environment.setContext(new SpringContext(applicationContext));
installAuthenticatedUserId(environment);
installProcessEngineContext(environment);
installTransactionContext(environment);
return environment;
}
@SuppressWarnings("unchecked")
@Override
public <T> T get(Class<T> type) {
String[] names = applicationContext.getBeanNamesForType(type);
if (names.length >= 1) {
if (names.length > 1 && log.isWarnEnabled()) {
log.warn("Multiple beans for type " + type + " found. Returning the first result.");
}
return (T) applicationContext.getBean(names[0]);
}
return super.get(type);
}
@Override
public Object get(String key) {
if (applicationContext.containsBean(key)) {
return applicationContext.getBean(key);
}
return super.get(key);
}
}
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.pvm.internal.wire.descriptor;
import java.sql.Connection;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.internal.SessionImpl;
import org.jbpm.internal.log.Log;
import org.jbpm.pvm.internal.env.EnvironmentImpl;
import org.jbpm.pvm.internal.tx.HibernateSessionResource;
import org.jbpm.pvm.internal.tx.StandardTransaction;
import org.jbpm.pvm.internal.wire.WireContext;
import org.jbpm.pvm.internal.wire.WireDefinition;
import org.jbpm.pvm.internal.wire.WireException;
/**
* @author Tom Baeyens
*/
public class HibernateSessionDescriptor extends AbstractDescriptor {
private static final long serialVersionUID = 1L;
private static final Log log = Log.getLog(HibernateSessionDescriptor.class.getName());
protected String factoryName;
protected boolean useCurrent = false;
protected boolean tx = true;
protected boolean close = true;
protected String standardTransactionName;
protected String connectionName;
public Object construct(WireContext wireContext) {
EnvironmentImpl environment = EnvironmentImpl.getCurrent();
if (environment == null) {
throw new WireException("no environment");
}
// get the hibernate-session-factory
SessionFactory sessionFactory = null;
if (factoryName != null) {
sessionFactory = (SessionFactory) wireContext.get(factoryName);
} else {
sessionFactory = environment.get(SessionFactory.class);
}
if (sessionFactory == null) {
throw new WireException("couldn't find hibernate-session-factory " + (factoryName != null ? "'" + factoryName + "'" : "by type ") + "to open a hibernate-session");
}
// open the hibernate-session
Session session = null;
if (useCurrent) {
if (log.isTraceEnabled())
log.trace("getting current hibernate session");
session = sessionFactory.getCurrentSession();
} else if (connectionName != null) {
Connection connection = (Connection) wireContext.get(connectionName);
if (log.isTraceEnabled())
log.trace("creating hibernate session with connection " + connection);
session = (Session) sessionFactory.openStatelessSession(connection);
} else {
if (log.isTraceEnabled())
log.trace("creating hibernate session");
session = sessionFactory.openSession();
}
StandardTransaction standardTransaction = environment.get(StandardTransaction.class);
if (standardTransaction != null) {
HibernateSessionResource hibernateSessionResource = new HibernateSessionResource(session);
standardTransaction.enlistResource(hibernateSessionResource);
}
return session;
}
public Class<?> getType(WireDefinition wireDefinition) {
return SessionImpl.class;
}
public void setFactoryName(String factoryName) {
this.factoryName = factoryName;
}
public void setTx(boolean tx) {
this.tx = tx;
}
public void setStandardTransactionName(String standardTransactionName) {
this.standardTransactionName = standardTransactionName;
}
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
public void setUseCurrent(boolean useCurrent) {
this.useCurrent = useCurrent;
}
public void setClose(boolean close) {
this.close = close;
}
}
另外,再釋出到伺服器上的時候,由于我這裡代碼的位置是在作為依賴引入到web項目的位置,導緻此路徑在伺服器上的時候,會出現讀取不到的情況,導緻依然會讀取jbpm jar包裡的類。解決辦法是可以将類加在web主項目中,或者将這三個類打到jbpm jar包裡。由于web主項目主要是一些業務代碼,是以我是将這三個類打到了jar包裡。具體做法是首先将這三個類編譯成class檔案,然後将釋出的war包解壓開,将class檔案替換掉war包中的相應的類,然後再将war包壓縮,釋出到伺服器,再次測試就沒問題了。
2、Hibernate3之後的版本廢除了Session類的connection()方法
解決辦法:按照官網推薦的方式,采用了Session類的doWork()方法,替換了connection()方法,如下:
public void deleteAllByIds(final String ids){
final String sql = "delete from SCM_MST_DOCUMENT_TBL where id in(?)";
// 4.x後請使用以下方式
this.getSessionFactory().getCurrentSession().doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException{
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = connection.prepareStatement(sql);
String[] id = ids.split(",");
for(String value:id){
ps.setString(1, value);
rs = ps.executeQuery();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
ps.close();
rs.close();
}
}
});
}
3、Hibernate.createBlob()方法禁用
解決辦法:采用SerialBlob類的執行個體,如下:
public File saveFile(Document document, byte[] b) throws Exception {
// 儲存檔案
File model = new File();
model.setDocumentId(document.getId());
SerialBlob blob = new SerialBlob(b);
model.setFileBlob(blob);
model.setLoginUser(document.getLoginUser());
saveOrUpdate(model);
return model;
}
4、struts2更新後頁面action傳回的jsp報錯404
解決辦法:此問題原因是新版本struts2的配置檔案struts.xml中,通配符“*”無效了。解決辦法是在配置檔案中添加屬性strict-method-invocation="false",如下:
另外,原來項目中對結果的處理如圖,是在配置檔案裡未對結果進行轉發到相應的jsp頁面,而是在各自的代碼中直接傳回相應的頁面。這種方式我看網上用的很少,是以沒用到的請忽略這裡。換新版本後,結果顯示也出現了問題。解決辦法是在配置檔案開始出,重新定義了result-type,并且重寫了StrutsResultSupport類,繼承了新版本struts2的StrutsResultSupport類。如下:
代碼如下:
5、jsp頁面中的struts标簽中的屬性有變更
解決辦法:escape屬性改為escapeHtml;<s:iterator >、<s:bean>的id屬性改為var屬性;<s:set>标簽的name屬性換成了var屬性。
6、新版本Spring去除了TimeTask的定時器
解決辦法:采用了quartz的定時器,修改了配置檔案,如下:
<!-- 配置觸發器 -->
<!-- 配置JOB類 -->
<bean id="schedulers" class="com.lengsun.scm.io.service.TimerTask"></bean>
<!-- 配置JobDetail -->
<bean id="jobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="schedulers"></property>
<property name="targetMethod" value="execute"></property>
</bean>
<!-- 配置trigger -->
<bean id="cronTrigger"
class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"
autowire="no">
<!-- 排程程式 -->
<property name="jobDetail" ref="jobDetail" />
<!-- 表達式(重點) -->
<property name="cronExpression" value="10/60 * * * * ? *" />
</bean>
<!-- 配置排程中心 -->
<bean id="scheduler"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean"
autowire="no">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>
7、其他問題
其他問題包括配置檔案中類替換、代碼中的類替換、各别方法替換等,就不一一說明了。
五、總結
說實話,這個任務即使已經完成了,我也覺得挺沒必要的,因為與其說把老舊的技術更新一個新的軟體版本,不如直接把架構換成最新的技術,這樣才是從根本上解決問題,效率才更高。可能上司結合目前的業務需要和成本把控,有自己的想法吧,我就不多操心了。。。