首先,不得不吐槽一下,现在还在用这些技术,真的稍微有点落后了。无奈自己遇上了这样的事情,面对复杂的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、其他问题
其他问题包括配置文件中类替换、代码中的类替换、各别方法替换等,就不一一说明了。
五、总结
说实话,这个任务即使已经完成了,我也觉得挺没必要的,因为与其说把老旧的技术升级一个新的软件版本,不如直接把框架换成最新的技术,这样才是从根本上解决问题,效率才更高。可能领导结合当前的业务需要和成本把控,有自己的想法吧,我就不多操心了。。。