Springboot整合Shiro分離資源打包運行出現 java.lang.ClassNotFoundException

在開發過程中,Springboot整合了Shiro安全框架,並且採用shiro-redis來進行session的緩存操作

<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>2.8.24</version>
</dependency>

開發完成後,在本地IDEA中運行完全沒問題,一切和預期相同,然後執行打包部署,問題就出現了,我的打包方式爲分離出依賴jar包和配置文件等,其他接口一切正常,登錄也沒問題,但是後面的登錄狀態校驗就怎麼也不成功,也就是不能通過token(sessionId)獲取到已登錄的賬號信息,折騰了差不多一天,終於找到了問題原因並解決。

首先貼出我出現錯誤的打包方式的代碼:

<build>
    <plugins>
        <!--打包jar-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <!--不打包資源文件-->
                <excludes>
                    <exclude>static/**</exclude>
                    <exclude>*.properties</exclude>
                    <exclude>*.xml</exclude>
                </excludes>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <!--MANIFEST.MF 中 Class-Path 加入前綴-->
                        <classpathPrefix>lib/</classpathPrefix>
                        <!--jar包不包含唯一版本標識-->
                        <useUniqueVersions>false</useUniqueVersions>
                        <!--指定入口類-->
                        <mainClass>com.lvyabar.tutor.TutorApplication</mainClass>
                    </manifest>
                    <manifestEntries>
                        <!--MANIFEST.MF 中 Class-Path 加入資源文件目錄-->
                        <Class-Path>./resources/</Class-Path>
                    </manifestEntries>
                </archive>
                <outputDirectory>${project.build.directory}</outputDirectory>
            </configuration>
        </plugin>

        <!--拷貝依賴 copy-dependencies-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>
                            ${project.build.directory}/lib/
                        </outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!--拷貝資源文件 copy-resources-->
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-resources</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                            </resource>
                        </resources>
                        <outputDirectory>${project.build.directory}/resources</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!--spring boot repackage,依賴 maven-jar-plugin 打包的jar包 重新打包成 spring boot 的jar包-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <!--重寫包含依賴,包含不存在的依賴,jar裏沒有pom裏的依賴-->
                <includes>
                    <include>
                        <groupId>null</groupId>
                        <artifactId>null</artifactId>
                    </include>
                </includes>
                <layout>ZIP</layout>
                <!--使用外部配置文件,jar包裏沒有資源文件-->
                <addResources>true</addResources>
                <outputDirectory>${project.build.directory}</outputDirectory>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build>

通過上述打包方式,會生成文件夾lib(項目依賴jar包)、resources(原項目中resources文件夾下的文件)以及一個jar(項目啓動文件)文件,使用如下命令啓動項目,一切都很順利

java -jar xxx.jar

但是當登錄後,用token進行權限校驗以及獲取session信息後,出錯了,報錯的日誌是在下面這個方法中輸出的

org.crazycake.shiro.RedisSessionDAO#doReadSession

@Override
protected Session doReadSession(Serializable sessionId) {
	if (sessionId == null) {
		logger.warn("session id is null");
		return null;
	}
	Session s = getSessionFromThreadLocal(sessionId);

	if (s != null) {
		return s;
	}

	logger.debug("read session from redis");
	try {
		s = (Session) valueSerializer.deserialize(redisManager.get(keySerializer.serialize(getRedisSessionKey(sessionId))));
		setSessionToThreadLocal(sessionId, s);
	} catch (SerializationException e) {
		logger.error("read session error. settionId=" + sessionId);
	}
    return s;
}

正是logger.error("read session error. settionId=" + sessionId);這行代碼輸出了error的日誌信息,但是,因爲這是依賴的shiro-redis的代碼,我無法查看到詳細的報錯信息,於是我複製了這個類的所有代碼,在輸出錯誤日誌之前,打印了錯誤信息棧,找到了根本原因是redis中的session反序列化失敗,導致反序列化失敗的原因是因爲session裏存入的管理員信息類找不到,具體錯誤信息如下:

2019-12-25 11:38:34.905 ERROR 35048 --- [nio-8080-exec-7] c.lvyabar.tutor.mapper.RedisSessionDAO   : org.crazycake.shiro.SerializationException: deserialize error
        at org.crazycake.shiro.ObjectSerializer.deserialize(ObjectSerializer.java:52)
        at com.lvyabar.tutor.mapper.RedisSessionDAO.doReadSession(RedisSessionDAO.java:142)
        at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:168)
        at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236)
        at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222)
        at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118)
        at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:148)
        at org.apache.shiro.session.mgt.AbstractNativeSessionManager.getSession(AbstractNativeSessionManager.java:140)
        at org.apache.shiro.mgt.SessionsSecurityManager.getSession(SessionsSecurityManager.java:156)
        at org.apache.shiro.mgt.DefaultSecurityManager.resolveContextSession(DefaultSecurityManager.java:461)
        at org.apache.shiro.mgt.DefaultSecurityManager.resolveSession(DefaultSecurityManager.java:447)
        at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:343)
        at org.apache.shiro.subject.Subject$Builder.buildSubject(Subject.java:845)
        at org.apache.shiro.web.subject.WebSubject$Builder.buildWebSubject(WebSubject.java:148)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.createSubject(AbstractShiroFilter.java:292)
        at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:359)
        at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.lvyabar.tutor.vo.admin.user.AdminVO
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at java.io.ObjectInputStream.resolveClass(Unknown Source)
        at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
        at java.io.ObjectInputStream.readClassDesc(Unknown Source)
        at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
        at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)
        at java.util.HashMap.readObject(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
        at java.io.ObjectInputStream.readSerialData(Unknown Source)
        at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
        at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)
        at org.apache.shiro.session.mgt.SimpleSession.readObject(SimpleSession.java:500)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at java.io.ObjectStreamClass.invokeReadObject(Unknown Source)
        at java.io.ObjectInputStream.readSerialData(Unknown Source)
        at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
        at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)
        at org.crazycake.shiro.ObjectSerializer.deserialize(ObjectSerializer.java:48)
        ... 46 more

沒錯,究其根本,就是這個錯誤導致的:Caused by: java.lang.ClassNotFoundException: com.lvyabar.tutor.vo.admin.user.AdminVO,反序列化的時候找不到這個類,接下來就開始分析,爲什麼會找不到這個類呢,在本地不打包運行的時候完全沒問題,並且我也看了打包後的文件中是能找到這個類的,於是各種猜測、網上搜索原因,都沒解決我的問題,直到我看到一篇文章,關於項目打包後別的模塊無法import的原因,懷着試一試的態度,居然解決了我的問題,原因正是打包的問題,通過上面說到的打包方式,生成的jar包結構是這樣的

項目的類路徑是在BOOT-INF這個文件夾下,所以大概原因就知道了,因爲類路徑多了個BOOT-INF,所以導致反序列化的時候找不到類,於是,只要打包的時候去除BOOT-INF文件夾就好了,新的打包方式只需添加一行代碼<skip>true</skip>,完整的打包方式代碼如下:

<build>
    <plugins>
        <!--打包jar-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <!--不打包資源文件-->
                <excludes>
                    <exclude>static/**</exclude>
                    <exclude>*.properties</exclude>
                    <exclude>*.xml</exclude>
                </excludes>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <!--MANIFEST.MF 中 Class-Path 加入前綴-->
                        <classpathPrefix>lib/</classpathPrefix>
                        <!--jar包不包含唯一版本標識-->
                        <useUniqueVersions>false</useUniqueVersions>
                        <!--指定入口類-->
                        <mainClass>com.lvyabar.tutor.TutorApplication</mainClass>
                    </manifest>
                    <manifestEntries>
                        <!--MANIFEST.MF 中 Class-Path 加入資源文件目錄-->
                        <Class-Path>./resources/</Class-Path>
                    </manifestEntries>
                </archive>
                <outputDirectory>${project.build.directory}</outputDirectory>
            </configuration>
        </plugin>

        <!--拷貝依賴 copy-dependencies-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>
                            ${project.build.directory}/lib/
                        </outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!--拷貝資源文件 copy-resources-->
        <plugin>
            <artifactId>maven-resources-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-resources</id>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-resources</goal>
                    </goals>
                    <configuration>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                            </resource>
                        </resources>
                        <outputDirectory>${project.build.directory}/resources</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!--spring boot repackage,依賴 maven-jar-plugin 打包的jar包 重新打包成 spring boot 的jar包-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <!--重寫包含依賴,包含不存在的依賴,jar裏沒有pom裏的依賴-->
                <includes>
                    <include>
                        <groupId>null</groupId>
                        <artifactId>null</artifactId>
                    </include>
                </includes>
                <layout>ZIP</layout>
                <!--使用外部配置文件,jar包裏沒有資源文件-->
                <addResources>true</addResources>
                <outputDirectory>${project.build.directory}</outputDirectory>
                <skip>true</skip>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build>

於是打包後的jar文件結構如下

再次運行項目,發現終於正常了,能正常解析出redis中的session數據了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章