ruoyi整合netty,部署到weblogic

前言

笔者第一次用netty开发功能,也是第一次把服务部署到weblogic上,之前没用过weblogic,算是小白一个,说得不对的地方,请各路大神不吝赐教。

项目背景

笔者使用若依框架整合netty,搭建了一个http代理服务,最后要把服务部署到weblogic上。

涉及到的技术或框架

ruoyi 3.8.1 oracle版本
springboot 2.5.8
netty 4.1.72.Final
weblogic 12c

问题整理

在本地IDEA跑服务,一点儿问题没有,已部署安装到weblogic就开始出现问题,现整理如下:

问题1

把项目打成war包,在weblogic控制台安装应用时,提示tomcat-embed-websocket-9.0.56.jar和tomcat-embed-el-9.0.56.jar两个jar包里的/META-INF/web-fragment.xml有问题。报错信息如下:
cvc-enumeration-valid string value '4.0' is not a valid enumeration value for web-app-versionType in namespace http://xmlns.jcp.org/xml/ns/javaee

解决办法是编辑这两个jar包里面/META-INF/web-fragment.xml文件,把

<web-fragment xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-fragment_4_0.xsd"
              version="4.0"
              metadata-complete="true">

改成

<web-fragment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd"
              version="3.0">

改完终于能把应用安装上了。

问题2

在本地开发时,我把netty服务写到了应用的main方法中,在IDEA中直接run就能把netty服务也启动起来。但是部署到weblogic上,应用启动之后发现netty没有起来,其他接口是正常的,可以正常使用若依相关服务。原main方法如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RuoYiApplication {

    public static void main(String[] args) throws Exception {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  ");

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new GatewayServerInitializer());
            ChannelFuture future = serverBootstrap.bind(9000).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

从日志来看,应该是没有执行main方法,所以netty服务没起来。
因此,把netty服务单独提取出来,作为一个线程类(implements Runnable),在Override的run方法中启动netty服务。

import com.ruoyi.GatewayServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class NettyServer implements Runnable {
    
    @Override
    public void run() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new GatewayServerInitializer());
            ChannelFuture future = serverBootstrap.bind(9000).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

并增加一个容器事件监听,在SpringBoot应用刷新的时候新建线程并执行。

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

/**
 * 容器事件监听
 */
@Component
public class NettyServerListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        NettyServer nettyServer = new NettyServer();
        new Thread(nettyServer).start();
    }

}

问题3

在修复问题2之前,启动的时候,除了netty服务没起来,还在日志里面发现了一个错误提示,如下:
java.lang.ClassCastException: org.apache.tomcat.websocket.server.WsServerContainer cannot be cast to org.glassfish.tyrus.server.TyrusServerContainer

经查,大神们说springboot部署到weblogic上还有3个要注意的地方:

1、要把tomcat相关jar的使用范围改为provided,打war包时不包含进去,即在pom.xml中添加以下依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

注:这个依赖的作用是将SpringBoot内部自带的tomcat排除掉,要使用外部的web容器。

2、新增/src/main/webapp/WEB-INF/weblogic.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<wls:weblogic-web-app
        xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd
        http://xmlns.oracle.com/weblogic/weblogic-web-app
        https://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
    <wls:container-descriptor>
        <wls:prefer-application-packages>
            <wls:package-name>org.slf4j</wls:package-name>
            <wls:package-name>com.fasterxml.jackson.*</wls:package-name>
        </wls:prefer-application-packages>
    </wls:container-descriptor>
</wls:weblogic-web-app>

3、修改main方法,改成:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.web.WebApplicationInitializer;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class RuoYiApplication extends SpringBootServletInitializer implements WebApplicationInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(RuoYiApplication.class);
    }

    public static void main(String[] args) throws Exception {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  ");

    }
}

网上还有人说还要再添加/src/main/webapp/WEB-INF/web.xml,这个和servlet的版本有关,若依3.8.1版本使用的springBoot是2.5.8,使用了servlet 3.0,因此不需要新增web.xml,具体是否需要新增,可以参考springBoot官网文档,例如2.5.8的文档地址如下:

https://docs.spring.io/spring-boot/docs/2.5.8/reference/htmlsingle/

问题4

修复问题2后,服务正常能用了,可把我乐坏了,以前不是没用过weblogic么?看看怎么重启服务呗,免得后面服务挂了,怎么重启都不知道。
于是在weblogic管理控制台上面停止服务,再重新起来服务,坏了!日志报了一个错误:
java.net.BindException: Address already is use

从日志来看,怀疑是没有执行到finally块的shutdownGracefully方法,于是修改netty服务和容器事件监听类,把两个事件循环线程组改成静态全局变量,在容器即将关闭时调用shutdownGracefully方法。

import com.ruoyi.GatewayServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer implements Runnable {

    public static EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    public static EventLoopGroup workerGroup = new NioEventLoopGroup();

    @Override
    public void run() {
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new GatewayServerInitializer());
            serverBootstrap.bind(9000).sync();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.stereotype.Component;

/**
 * 容器事件监听
 */
@Component
public class NettyServerListener implements ApplicationListener {

    private static final Logger log = LoggerFactory.getLogger(NettyServerListener.class);

    /**
     * springBoot容器事件
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 在这里可以监听到Spring Boot的生命周期
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            log.info("SpringBoot初始化环境变量");
        } else if (event instanceof ApplicationPreparedEvent) {
            log.info("SpringBoot初始化完成");
        } else if (event instanceof ContextRefreshedEvent) {
            log.info("SpringBoot应用刷新");
            NettyServer nettyServer = new NettyServer();
            new Thread(nettyServer).start();
        } else if (event instanceof ApplicationReadyEvent) {
            log.info("SpringBoot应用已启动完成");
        } else if (event instanceof ContextStartedEvent) {
            log.info("SpringBoot应用启动,需要在代码动态添加监听器才可捕获 ");
        } else if (event instanceof ContextStoppedEvent) {
            NettyServer.bossGroup.shutdownGracefully();
            NettyServer.workerGroup.shutdownGracefully();
            log.info("SpringBoot应用停止");
        } else if (event instanceof ContextClosedEvent) {
            NettyServer.bossGroup.shutdownGracefully();
            NettyServer.workerGroup.shutdownGracefully();
            log.info("SpringBoot应用关闭");
        } else {

        }
    }

}

改成这种写法之后,重新安装war包,启动服务,接着停止服务,再启动服务,又报了另一个错误:
Force-closing a channel whose registration task was not accepted by an event loop: [id:0xda3d9df0] java.util.concurrent.RejectedExecutionException: event executor terminated

到这里,我就懵逼了,感觉自己应该是对netty的shutdownGracefully方法理解错了。
上网查了半天资料,大神说shutdownGracefully方法是netty的资源优雅释放,不但断开所有连接,而且将其中的内存清理掉。除非是整个进程优雅退出,一般情况下不会调用EventLoopGroup和EventLoop的shutdownGracefully方法,更多的是链路channel的关闭和资源释放。
那么也就是说,当你在本地IDEA或者apache tomcat上面跑的时候,实际上启动了一个进程,shutdown的时候进程也就退出了,那么在finally或者容器停止的时候调用shutdownGracefully去释放资源,一点儿问题也没有。
但是在weblogic上面启动服务的时候,服务和weblogic的进程是同一个,这个时候调用shutdownGracefully把资源释放了,再启动的时候就会有问题,因此只需要关闭链路channel就好。
于是再次修改netty服务和容器事件监听类,把ChannelFuture改成静态全局变量,在容器即将关闭时调用关闭链路channel的close方法。

import com.ruoyi.GatewayServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer implements Runnable {

    public static ChannelFuture future = null;

    @Override
    public void run() {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new GatewayServerInitializer());
            future = serverBootstrap.bind(9000).sync();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.stereotype.Component;

/**
 * 容器事件监听
 */
@Component
public class NettyServerListener implements ApplicationListener {

    private static final Logger log = LoggerFactory.getLogger(NettyServerListener.class);

    /**
     * springBoot容器事件
     * @param event
     */
	@Override
    public void onApplicationEvent(ApplicationEvent event) {
        // 在这里可以监听到Spring Boot的生命周期
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            log.info("SpringBoot初始化环境变量");
        } else if (event instanceof ApplicationPreparedEvent) {
            log.info("SpringBoot初始化完成");
        } else if (event instanceof ContextRefreshedEvent) {
            log.info("SpringBoot应用刷新");
            NettyServer nettyServer = new NettyServer();
            new Thread(nettyServer).start();
        } else if (event instanceof ApplicationReadyEvent) {
            log.info("SpringBoot应用已启动完成");
        } else if (event instanceof ContextStartedEvent) {
            log.info("SpringBoot应用启动,需要在代码动态添加监听器才可捕获 ");
        } else if (event instanceof ContextStoppedEvent) {
            if (NettyServer.future != null) {
                NettyServer.future.channel().close();
                log.info("channel已关闭");
            }
            log.info("SpringBoot应用停止");
        } else if (event instanceof ContextClosedEvent) {
            if (NettyServer.future != null) {
                NettyServer.future.channel().close();
                log.info("channel已关闭");
            }
            log.info("SpringBoot应用关闭");
        } else {

        }
    }
}

问题5

此时应用重启不报错了,再登录一下管理后台,又报了个错误,提示线程池已停止。
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask@6134f059 rejected from com.ruoyi.framework.config.ThreadPoolConfig$1@3ed5be2f[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 2]

基于问题4的思路,首先怀疑是不是连接池没有正确的shutdown,调试之后发现weblogic停止应用的时候已经自动shutdown了。
那会不会是线程池只初始化了一次,导致第二次重启还是使用了被shutdown的线程池呢?调试结果是weblogic应用重启还是有重新初始化线程池的。
最后发现是线程执行时的问题,它居然一直使用的是第一次初始化的线程池。。。
于是在使用之前加个判断:

import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.ruoyi.common.utils.Threads;
import com.ruoyi.common.utils.spring.SpringUtils;

/**
 * 异步任务管理器
 * 
 * @author ruoyi
 */
public class AsyncManager
{
    /**
     * 操作延迟10毫秒
     */
    private final int OPERATE_DELAY_TIME = 10;

    /**
     * 异步操作任务调度线程池
     */
    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

    /**
     * 单例模式
     */
    private AsyncManager(){
    }

    private static AsyncManager me = new AsyncManager();

    public static AsyncManager me() {
        return me;
    }

    /**
     * 执行任务
     * 
     * @param task 任务
     */
    public void execute(TimerTask task)
    {
        if (executor == null || executor.isShutdown() || executor.isTerminated()) {
            executor = SpringUtils.getBean("scheduledExecutorService");
        }

        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }

    /**
     * 停止任务线程池
     */
    public void shutdown()
    {
        Threads.shutdownAndAwaitTermination(executor);
    }
}

问题6

因为在调试、测试上面几个问题的时候会时不时重启应用,然后就发现了这个问题:当应用第一次成功启动之后,马上停止服务,接着再次启动服务,完了去登陆管理后台,会出现如下错误:
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class com.ruoyi.framework.manager.AsyncManager

如果第一次成功启动之后有正常使用一下系统,就不会报这个错误。这个问题研究了好久就挺无解的,先记录一下,回头有空再研究研究。

2022年5月16日继续更新一下新发现的问题

问题6弄着弄着好像自己消失了。。。。。。要不是我回来看笔记,估计都忘了这回事了,算了,随它去吧。。。

问题7:

客户希望数据库源使用weblogic的jndi,而不是写死在配置文件中,于是百度了一下,已经有大神给了修改方法,参考:

https://copyfuture.com/blogs-details/20201230225025438v

在配置好weblogic的jndi后,项目需要修改两个地方:
1、配置文件:主库从库各新增一个配置项jndiName

spring:
    # 数据源配置
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: oracle.jdbc.driver.OracleDriver
        druid:
            # 主库数据源
            master:
                url: 
                username: 
                password: 
                jndiName: proxy_jndi
            # 从库数据源
            slave:
                # 从数据源开关/默认关闭
                enabled: false
                url:
                username:
                password:
                jndiName: proxy_jndi

2、修改com.ruoyi.framework.config.DruidConfig文件的masterDataSource和slaveDataSource方法,如下:

    @Value("${spring.datasource.druid.master.jndiName}")
    private String masterjndiName;
    
    @Value("${spring.datasource.druid.slave.jndiName}")
    private String slavejndiName;

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) throws NamingException {
        if (StringUtils.isBlank(masterjndiName)) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        } else {
            JndiDataSourceLookup lookup = new JndiDataSourceLookup();
            lookup.setResourceRef(true);
            return lookup.getDataSource(masterjndiName);
        }
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        if (StringUtils.isBlank(slavejndiName)) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        } else {
            JndiDataSourceLookup lookup = new JndiDataSourceLookup();
            lookup.setResourceRef(true);
            return lookup.getDataSource(slavejndiName);
        }
    }

于是我发动CV大法,再在weblogic配置个数据源proxy_jndi,发布运行war包就哦了。但是,直到我更新或者重新安装war包,噩梦就来了,每次一启动老报错:

javax.naming.NameNotFoundException: Unable to resolve 'proxy_jndi'. Resolved ''; remaining name 'proxy_jndi' Unable to resolve 'proxy_jndi'. Resolved ''; remaining name 'proxy_jndi'

每次都要把数据源删了,重新配置一遍才能跑起来,要是生产上面有其他人开发的应用也使用同一个jndi,一删人家不得疯了。。。百度了半天都是说找不到proxy_jndi,要检查是不是写错了,这不科学啊,拷贝还能有错的。。。
直到前几天客户说人家的项目也是使用的jndi,从来没有这样的报错,让我参考一下,于是我解压了人家的war包,用idea一个个打开class文件去找,看人家怎么连的数据源,一比对才发现真的不一样,重新修改com.ruoyi.framework.config.DruidConfig文件的masterDataSource和slaveDataSource方法后问题解决了,如下:

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties) throws NamingException {
        if (StringUtils.isBlank(masterjndiName)) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        } else {
            JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
            bean.setJndiName(masterjndiName);
            bean.setProxyInterface(DataSource.class);
            bean.setLookupOnStartup(false);
            try {
                bean.afterPropertiesSet();
            } catch (NamingException e) {
                throw new RuntimeException(e);
            }
            return (DataSource) bean.getObject();
        }
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties) {
        if (StringUtils.isBlank(slavejndiName)) {
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            return druidProperties.dataSource(dataSource);
        } else {
            JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
            bean.setJndiName(slavejndiName);
            bean.setProxyInterface(DataSource.class);
            bean.setLookupOnStartup(false);
            try {
                bean.afterPropertiesSet();
            } catch (NamingException e) {
                throw new RuntimeException(e);
            }
            return (DataSource) bean.getObject();
        }
    }

问题8:

系统经过漏洞扫描,需要升级一下jar包,于是Spring Boot升级到2.5.11,Spring升级到5.3.19,poi升级到5.2.1,升级一通之后,重新部署到weblogic,登陆时报错了:
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'javax.validation.constraints.NotBlank' validating type 'java.lang.String'. Check configuration for 'uuid'
查了半天资料,说是weblogic jar包冲突了,于是修改weblogic.xml,在prefer-application-packages处添加以下两行:

<wls:prefer-application-packages>
    ......
    <wls:package-name>org.hibernate.*</wls:package-name>
    <wls:package-name>javax.validation.*</wls:package-name>
</wls:prefer-application-packages>

打包重新部署,以上问题不再出现。

问题9:

完成jar包升级之后,需要测试一下所有服务是否正常,发现下载功能有问题,估计是poi升级导致的,主要是以下三个错误:
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook
……
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook
……

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.ExceptionInInitializerError
……
Caused by: java.lang.ExceptionInInitializerError: null
……
Caused by: org.apache.xmlbeans.XmlRuntimeException: java.lang.ClassCastException: org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl cannot be cast to org.apache.xmlbeans.SchemaTypeLoader
……
Caused by: java.lang.ClassCastException: org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl cannot be cast to org.apache.xmlbeans.SchemaTypeLoader
……

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: org.apache.commons.io.IOUtils.byteArray(I)[B
……
Caused by: java.lang.NoSuchMethodError: org.apache.commons.io.IOUtils.byteArray(I)[B

用必应搜半天,在stackoverflow找到了解决办法,修改weblogic.xml,在prefer-application-packages处添加以下几行,然后再加prefer-application-resources:

<wls:prefer-application-packages>
    ......
    <wls:package-name>org.apache.commons.io.*</wls:package-name>
    <wls:package-name>org.apache.commons.collections4.*</wls:package-name>
    <wls:package-name>org.apache.commons.compress.*</wls:package-name>
    <wls:package-name>org.apache.poi.*</wls:package-name>
    <wls:package-name>org.apache.xmlbeans.*</wls:package-name>
    <wls:package-name>org.openxmlformats.*</wls:package-name>
    <wls:package-name>schemaorg_apache_xmlbeans.*</wls:package-name>
</wls:prefer-application-packages>
<wls:prefer-application-resources>
    <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLCONFIG/TypeSystemHolder.class</wls:resource-name>
    <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLLANG/TypeSystemHolder.class</wls:resource-name>
    <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLSCHEMA/TypeSystemHolder.class</wls:resource-name>
    <wls:resource-name>schemaorg_apache_xmlbeans/system/sXMLTOOLS/TypeSystemHolder.class</wls:resource-name>
</wls:prefer-application-resources>

后语

前前后后折腾了很久,网上相关的资料也少得可怜,或者年代有些久远,把遇到的问题整理整理,当是一个笔记吧。

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值