spring2.5.6升级到spring4+java8

本文记录了一个基于spring2.5.6+java6的老系统升级到spring4.2.3+java8的过程。解决了循环依赖、类名变更、jar包冲突等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

华宇做为全国领先的法律科技公司,在十几年的过程中,开发了数百款软件产品。有些产品开发时间较早,使用的技术栈还是spring2.5.6+java6。这个技术栈存在几个问题

  • java8相对java6做了很多性能优化,这些年陆陆续续遇到一些java6相关的性能问题,比如java的ClassLoader用的是方法级的synchonized,而java7就已经在ClassLoader中提供了一类一锁。
  • java8的语法相对java6来说更便捷,生产效率更高。
  • 国外主流开源项目很多都只提供java8以上的SDK,一旦出现非升级不可的情况,将是灾难性后果。像今年出现的多次fastjson的安全漏洞,如果不是温少照顾中国国情,也只提供java8的sdk,很多项目组将会付出惨痛代价

目前成研负责的数字法院系统(以下简称NP)是全国最多法院在用的核心办案系统,为了让团队成员效率更高,法官们用的更爽,同时避免后续出现无法升级开源项目SDK的窘境,NP将尝试将spring和java进行升级。

升级方案

基于spring2.5.6源码进行改造

网上提到的方案都是定制一下spring的JDKVersion这个类,用这种方案只是说你的程序能在jdk8运行起来,但是无法用到java8的新语法,因为spring2.5.6是不支持lambda表达式的,spring在启动时会报错。
在这里插入图片描述

也有人给spring官方提了相应问题,希望spring官方能让spring2.5.6支持java8,官方的答复是哪凉快哪呆着去。既然官方不干,那我可以试着代官方行事,修改一个支持java8的spring2.5.6版本。

结合上述报错信息,我就把org.springframework.asm的代码拿出来进行修改,但是经过两个小时的努力,发现这个工作量不小。先舍弃这种方案。

直接将spring2.5.6升级到spring4.XX

spring4.XX是官方支持java8的最小版本。跨两个大版本升级,心里还是有些没底的。但是经过三个小时的努力,终于成功的将NP升级到spring4.2.3。在升级过程中,陆续出现了十多个错误,但基本上没有改代码,只是各种升级jar包和修改配置。以下是升级中出现的错误和相应的解决方案。

bouncycastle循环依赖
Caused by: java.lang.IllegalStateException: 
Unable to complete the scan for annotations for web application [/spxt] due to a StackOverflowError. 
Possible root causes include a too low setting for -Xss and illegal cyclic inheritance dependencies.
The class hierarchy being processed was [org.bouncycastle.asn1.ASN1EncodableVector
->org.bouncycastle.asn1.DEREncodableVector
->org.bouncycastle.asn1.ASN1EncodableVector]

这个是bcprov出现了jar包冲突,只是以前spring2.5.6的时候没报错,spring4.XX更加严格了。删除重复的jar包,我只保留了bcprov-jdk16-1.46,也可以考虑升级到更新的版本

各种ClassNotFoundException

Caused by: java.lang.ClassNotFoundException: org.springframework.scheduling.quartz.JobDetailBean

这个类改名了,改成JobDetailFactoryBean

ClassNotFoundException: org.springframework.scheduling.quartz.CronTriggerBean
这个类改名了,改成CronTriggerFactoryBean

NoClassDefFoundError: org/apache/neethi/PolicyBuilder

升级neethi到3.1.1.jar

Requested bean is currently in creation: Is there an unresolvable circular reference?

spring的bean出现了循环依赖,以前有循环依赖的话,spring2.5.6会想办法兼容解决,但是其实也遗留了很多隐患,NP深受其害。个中原由略去不讲,大家在程序设计时,要好好设计,不要出现bean的循环依赖。

如果不重构代码的话,可以在bean的申明时加一个@Lazy的注解,这样A依赖B,B依赖A的话,把B这个bean设为懒加载。

ClassFormatException Invalid byte tag in constant pool: 18
Caused by: org.aspectj.apache.bcel.classfile.ClassFormatException: File: 'com.thunisoft.XXXService': Invalid byte tag in constant pool: 18
	at org.aspectj.apache.bcel.classfile.ClassParser.readConstantPool(ClassParser.java:192)
	at org.aspectj.apache.bcel.classfile.ClassParser.parse(ClassParser.java:131)
	at org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository.loadJavaClass(NonCachingClassLoaderRepository.java:262)
	at org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository.loadClass(NonCachingClassLoaderRepository.java:242)
	at org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository.loadClass(NonCachingClassLoaderRepository.java:249)

升级aspectJ到1.8解决

hibernate相关问题

hibernate升级到3.6.10.FINAL,这个版本相对项目中用到的3.2.6改动很小,如果升级到hibernate4或者5的话,改动量会很大。升级hibernate时遇到了一些小问题,比如项目中重写了LocalSessionFactoryBeanconfigurationClass,hibernate3.6.10改了这个类,所以如果用到了的话需要改一行代码。

Caused by: java.sql.SQLException: Incorrect syntax near ‘cross’.

我们用的是sybase数据库,改一下hibernate方言,用SybaseASE157Dialect这个方言。

cxf升级

项目中用到了cxf2.XX,和spring4不兼容,统一升级到cxf3.0.1版本解决。中间也遇到了一些小的配置问题。比如<import resource="classpath:META-INF/ cxf/ cxf-extension-soap.xml" />这个配置要删掉,高版本没有这个文件了。

还有Caused by: java.lang.NoSuchFieldError: QUALIFIED,这个是升级xmlschema-core到2.1.0.jar

然后项目就跑起来啦,当他跑

后记

还有一些项目特有的问题就不一一描述了,每次遇到一个错就解决一个错,再重启验证,就这么来回十几次,当我最后一次发现控制台没有错误信息的时候,这种喜悦只有写代码的人才能感受到。我小心翼翼的打开浏览器,输入网址,运行,页面加载的那三秒钟是今年最长的三秒钟,但是一切还好,老天没有耍我。

十年前我刚入职时,按照架构师写的文档把项目框架搭建起来,十年过去了,我又来改NP代码了。本来这事轮不到我的,找了好几个人,都说不好升级,工作量大。以后大家要慎重点哦。

rar包内含有spring2.5.6源码,解压即可使用 源代码分析,是一件既痛苦又快乐的事情,看别人写的代码是通过的,但当你能够看明白的时候,相信快乐也会随之而来,为了减少痛苦,更快的带来快乐,在这里希望通过这篇文章对觉得困难的朋友有一个帮助。 本文以spring框架的XmlBeanFactory为入手点进行分析,希望能够以尽量简洁明了的方式给予有需要的朋友一定的帮助。 首先来打开该类的代码,我们将看到如下代码: Java代码 public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } } public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } } 这个类的代码很简单,一个成员对象加两个构造函数,从这里我们可以看出,最重要的地方在于最后一个构造函数: Java代码 super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); 第一句就是将父亲工厂交给父类的构造函数,实际上最后也就是把父工厂保存到类的parentBeanFactory成员对象中,这个对象是在AbstractBeanFactory抽象类中定义的,而这个父工厂也会一直传递到该抽象类进行保存。第二句就是整个类中最重要的地方了,顾名思义,它的目的是通过XmlBeanDefinitionReader这个XML的Reader从资源resource中(也就是你的配置文件)读取bean的定义。接下来我们打开XmlBeanDefinitionReader的loadBeanDefinitions方法,我们可看到在这个方法里代码就一行,调用了一个同名不同参的方法,而参数是EncodedResource的一个实例,这个类实际上是Resource的一个包装类,用来保存资源的Encode的,那接下来我们再看被调用的loadBeanDefinitions方法,这个方法里最主要的部分就是: Java代码 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 这里的目的是将资源包装成一个InputSource,连同Resource作为参数传递到doLoadBeanDefinitions方法 Java代码 DocumentBuilderFactory factory = createDocumentBuilderFactory(); if (logger.isDebugEnabled()) { logger.debug("Using JAXP implementation [" + factory + "]"); } DocumentBuilder builder = createDocumentBuilder(factory); Document doc = builder.parse(inputSource); return registerBeanDefinitions(doc, resource); DocumentBuilderFactory factory = createDocumentBuilderFactory(); if (logger.isDebugEnabled()) { logger.debug("Using JAXP implementation [" + factory + "]"); } DocumentBuilder builder = createDocumentBuilder(factory); Document doc = builder.parse(inputSource); return registerBeanDefinitions(doc, resource); 这个方法的目的一目了然,就是为了将资源解释成为Document对象,然后调用registerBeanDefinitions方法,这里不做详细解释,不了解的话请去看看关于JAXP的介绍。接下来我们打开registerBeanDefinitions方法: Java代码 public int registerBeanDefinitions(Document doc, Resource resource) throws BeansException { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } public int registerBeanDefinitions(Document doc, Resource resource) throws BeansException { XmlBeanDefinitionParser parser = (XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass); return parser.registerBeanDefinitions(this, doc, resource); } 这里创建了一个XmlBeanDefinitionParser接口的实现,这个接口的具体类是DefaultXmlBeanDefinitionParser,这个接口很简单,只有registerBeanDefinitions一个方法,这个方法的作用也很明了,就是用来注册Bean的定义的,所以说类和方法的名字一定要起得有意义,这样可以让人一看就大概了解其作用,减少了很多阅读代码的痛苦。废话不多说,我们打开DefaultXmlBeanDefinitionParser的registerBeanDefinitions方法,这个类就是解释XML配置文件的核心类了,打开registerBeanDefinitions方法后我们看到如下代码: Java代码 public int registerBeanDefinitions(BeanDefinitionReader reader, Document doc, Resource resource) throws BeanDefinitionStoreException { this.beanDefinitionReader = reader; this.resource = resource; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); //初始化根元素 initDefaults(root); if (logger.isDebugEnabled()) { logger.debug("Default lazy init '" + getDefaultLazyInit() + "'"); logger.debug("Default autowire '" + getDefaultAutowire() + "'"); logger.debug("Default dependency check '" + getDefaultDependencyCheck() + "'"); } preProcessXml(root);//一个空方法用于扩展 int beanDefinitionCount = parseBeanDefinitions(root);//解释配置的主要方法 if (logger.isDebugEnabled()) { logger.debug("Found " + beanDefinitionCount + " elements in " + resource); } postProcessXml(root); //一个空方法用于扩展 return beanDefinitionCount; } public int registerBeanDefinitions(BeanDefinitionReader reader, Document doc, Resource resource) throws BeanDefinitionStoreException { this.beanDefinitionReader = reader; this.resource = resource; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); //初始化根元素 initDefaults(root); if (logger.isDebugEnabled()) { logger.debug("Default lazy init '" + getDefaultLazyInit() + "'"); logger.debug("Default autowire '" + getDefaultAutowire() + "'"); logger.debug("Default dependency check '" + getDefaultDependencyCheck() + "'"); } preProcessXml(root);//一个空方法用于扩展 int beanDefinitionCount = parseBeanDefinitions(root);//解释配置的主要方法 if (logger.isDebugEnabled()) { logger.debug("Found " + beanDefinitionCount + " elements in " + resource); } postProcessXml(root); //一个空方法用于扩展 return beanDefinitionCount; } 在这个方法当中,主要用于解释定义的有两个方法,一个是initDefaults,一个是parseBeanDefinitions,第一个方法是用来解释根元素的属性的,例如lazy-init, autowire等,而parseBeanDefinitions就是用来解释具体的bean定义了,方法代码如下: Java代码 protected int parseBeanDefinitions(Element root) throws BeanDefinitionStoreException { NodeList nl = root.getChildNodes(); int beanDefinitionCount = 0; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (IMPORT_ELEMENT.equals(node.getNodeName())) { importBeanDefinitionResource(ele); } else if (ALIAS_ELEMENT.equals(node.getNodeName())) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); this.beanDefinitionReader.getBeanFactory().registerAlias(name, alias); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } } } return beanDefinitionCount; } protected int parseBeanDefinitions(Element root) throws BeanDefinitionStoreException { NodeList nl = root.getChildNodes(); int beanDefinitionCount = 0; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (IMPORT_ELEMENT.equals(node.getNodeName())) { importBeanDefinitionResource(ele); } else if (ALIAS_ELEMENT.equals(node.getNodeName())) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); this.beanDefinitionReader.getBeanFactory().registerAlias(name, alias); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } } } return beanDefinitionCount; } 其他标签具体如何被解释这里就不多说,相信大家也能看得懂,这里主要讲一下解释bean的的处理,我们注意以下代码: Java代码 else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } else if (BEAN_ELEMENT.equals(node.getNodeName())) { beanDefinitionCount++; BeanDefinitionHolder bdHolder = parseBeanDefinitionElement(ele, false); BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.beanDefinitionReader.getBeanFactory()); } 这里是当碰到一个bean标签的时候所进行的处理,也既是对bean的定义进行解释,可以看到parseBeanDefinitionElement方法的第一个参数就是bean则个元素,第二个参数表示该bean是否为内置的bean,从这里进行解释的bean都不可能是内置的,所以这里直接以false为参数,打开parseBeanDefinitionElement方法,就可以看到这个方法里就是对bean的内部的解释,也很简单,也不多讲了,呵呵(下班时间已经到了,所以就写这么多了,基本的流程也就这样,没什么特别难的地方。),对了,最后还有一点就是解释完后,bean的定义将会被保存到beanFactory中,这个beanFactory的实现就是XmlBeanFactory了,该beanFactory是在new的时候被传递到reader中的,就是该类中以下这行代码: Java代码 private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 好了,就这么多了,本文只作为参考,只讲解了如何加载bean定义这块,只作为一个参考,希望对其他朋友能有所帮助吧,因为时间匆忙,有错漏的地方请指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值