1. 程式人生 > 程式設計 >解決SpringBoot使用devtools導致的型別轉換異常問題

解決SpringBoot使用devtools導致的型別轉換異常問題

問題:

最近在使用新框架SpringBoot + shiro + spring-data-jpa時,為了體驗下spring自帶的熱部署工具的便捷,於是引入了

<dependency> 

   <groupId>org.springframework.boot</groupId> 
   <artifactId>spring-boot-devtools</artifactId> 
   <!-- optional=true,依賴不會傳遞,該專案依賴devtools;之後依賴myboot專案的專案如果想要使用devtools,需要重新引入 --> 

   <optional>true</optional>
 </dependency>

在起初並沒遇到什麼問題,當使用shiro的session管理,而且用的sessionDao是redis實現的,然後再使用Session存取屬性時,發現存進去的屬性,再取出來後,就會出現型別轉換異常ClassCastException

分析:

然後自己寫了一大推單元測試模擬就是沒問題,後來突然意識到會不會是因為ClassLoader不同導致的型別轉換異常呢,然後注意了下專案啟動時載入專案中的類使用的載入器都是

org.springframework.boot.devtools.restart.classloader.RestartClassLoader

而從shiro session 取出來的物件(從redis中取出經過反序列化)的類載入器都是

sun.misc.Launcher.AppClassLoader

很明顯會導致型別轉換異常,原來Spring的dev-tools為了實現重新裝載class自己實現了一個類載入器,來載入專案中會改變的類,方便重啟時將新改動的內容更新進來,其實其中官方文件中是有做說明的:

By default,any open project in your IDE will be loaded using the “restart” classloader,and any regular .jar file will be loaded using the “base” classloader. If you work on a multi-module project,and not each module is imported into your IDE,you may need to customize things. To do this you can create a META-INF/spring-devtools.properties file. The spring-devtools.properties file can contain restart.exclude. and restart.include. prefixed properties. The include elements are items that should be pulled up into the “restart” classloader,and the exclude elements are items that should be pushed down into the “base” classloader. The value of the property is a regex pattern that will be applied to the classpath.

解決:

方案一、解決方案就是在resources目錄下面建立META-INF資料夾,然後建立spring-devtools.properties檔案,檔案加上類似下面的配置:

restart.exclude.companycommonlibs=/mycorp-common-[\w-]+.jar restart.include.projectcommon=/mycorp-myproj-[\w-]+.jar

All property keys must be unique. As long as a property starts with restart.include. or restart.exclude. it will be considered. All META-INF/spring-devtools.properties from the classpath will be loaded. You can package files inside your project,or in the libraries that the project consumes.

方案二、不使用spring-boot-devtools

針對方案一作一個詳細的案例進行分析說明,以及解決問題

首先準備一個jar包,裡面包含序列化以及反序列化的功能。

並打包,在springboot專案中引入

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- 這個包是我自己建立的序列化以及反序列化工具包 -->
<dependency>
  <groupId>com.example</groupId>
  <artifactId>devtools-serialization</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

簡單的配置下springboot專案,並模擬使用jar中的序列化工具類進行處理物件如下

@SpringBootApplication
public class PortalApplication {
  public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext context = SpringApplication.run(PortalApplication.class,args);
    DemoBean demoBean = new DemoBean();
    SerializationUtils.serialize(demoBean);
    Object deserialize = SerializationUtils.deserialize();
    System.out.println(PortalApplication.class.getClassLoader());
    //這裡物件引用是Object型別
    System.out.println(deserialize);
    System.out.println(deserialize.getClass().getClassLoader());
    context.getBeanFactory().destroySingletons();
  }
}

如上,是不會報錯的,因為Object是bootstrap引導類載入器載入的,因此不會產生任何問題,

但是如果改成下面這樣

//...
 public static void main(String[] args) throws Exception {
    ConfigurableApplicationContext context = SpringApplication.run(PortalApplication.class,args);
    DemoBean demoBean = new DemoBean();
    SerializationUtils.serialize(demoBean);
    Object deserialize = SerializationUtils.deserialize();
    System.out.println(PortalApplication.class.getClassLoader());
    //注意這裡進行了一次型別強轉
    System.out.println((DemoBean)deserialize);
    System.out.println(deserialize.getClass().getClassLoader());
    context.getBeanFactory().destroySingletons();
  }
  //...

結果是會丟擲:

Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) Caused by: java.lang.ClassCastException: com.sample.serial.DemoBean cannot be cast to com.sample.serial.DemoBean at com.sample.PortalApplication.main(PortalApplication.java:27) ... 5 more

而觀察上面輸出的ClassLoader資訊會發現分別為

org.springframework.boot.devtools.restart.classloader.RestartClassLoader@63059d5a sun.misc.Launcher$AppClassLoader@18b4aac2

這就是為什麼會明明沒問題,卻仍然拋了個ClassCastException的根源所在。

那麼如何解決這個問題呢?

將輸出的ClassLoader資訊保持一致即可,要麼都是RestartClassLoader要麼都是

AppClassLoader

這裡參考spring官方文件給出的配置方法進行處理。

在resources下建立META-INF/spring-devtools.properties

如圖:

解決SpringBoot使用devtools導致的型別轉換異常問題

下一步在spring-devtools.properties新增配置

restart.include.projectcommon=/devtools-serialization-[\\w.-]+.jar

注意這裡我需要包含的jar包名稱為devtools-serialization-1.0-SNAPSHOT.jar

配置的key以restart.include.開頭即可

restart.include.*

value 為一個正則表示式

下面再次執行程式檢視效果:

沒有異常產生

控制檯輸出classLoader資訊為

org.springframework.boot.devtools.restart.classloader.RestartClassLoader@1d9fbdd4 DemoBean{age=null,name='null'} org.springframework.boot.devtools.restart.classloader.RestartClassLoader@1d9fbdd4

問題完美解決。

補充知識:Springboot+devtools配置熱部署

Spring Boot提供了spring-boot-devtools這個模組來使應用支援熱部署,可以提高開發者的開發效率,無需手動重啟Spring Boot應用就能實現自動載入,之前寫了一篇可以自動載入springboot靜態檔案的,這次的只需要在原來的基礎上再加一些配置即可實現springboot工程的熱部署,步驟如下:

1、pom檔案增加依賴:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>
 
<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
        <fork>true</fork> <!--重要-->
      </configuration>
    </plugin>
  </plugins>
</build>

2、yml檔案中新增配置使其生效:

# devtools
debug: true
spring:
 devtools:
  restart:
   enabled: true #設定開啟熱部署
 freemarker:
  cache: false  #頁面不載入快取,修改即時生效

3、快捷鍵:Ctrl+Alt+S

解決SpringBoot使用devtools導致的型別轉換異常問題

4、快捷鍵:Ctrl+Shift+A,輸入Registry,點選進入勾選:

解決SpringBoot使用devtools導致的型別轉換異常問題

以上這篇解決SpringBoot使用devtools導致的型別轉換異常問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。