SpringBoot升級到2.7.18后不兼容的地方及解決
背景
最近為了給kafka加性能指標(biāo)采集功能,調(diào)研后發(fā)現(xiàn)spring-kafka在2.3版本之后就自帶了Micrometer指標(biāo)采集功能。
但是當(dāng)前項目的spring-boot版本是2.0.2.RELEASE,對應(yīng)的spring-kafka版本是2.1.6.RELEASE,所以準(zhǔn)備將spring-boot版本升級到2.7.18,這是2.x系列的最高版本,對應(yīng)的spring-kafka版本是2.8.11。
版本升級
module | 升級前version | 升級后version |
---|---|---|
spring-boot | 2.0.2.RELEASE | 2.7.18 |
spring-webmvc | 5.0.6.RELEASE | 5.3.31 |
spring-kafka | 2.1.6.RELEASE | 2.8.11 |
不兼容的地方
Spring boot
2.6版本開始默認禁用Bean的循環(huán)依賴
項目啟動會檢測是否存在循環(huán)依賴,存在就報如下錯誤。
***************************
APPLICATION FAILED TO START
***************************Description:
The dependencies of some of the beans in the application context form a cycle:
collectionController (field private com.biz.manager.CollectionManager com.web.controller.CollectionController.collectionManager)
┌─────┐
| collectionManagerImpl (field private com.biz.manager.FunnyManager com.biz.manager.impl.CollectionManagerImpl.funnyManager)
↑ ↓
| funnyManagerImpl (field private com.biz.manager.CollectionManager com.biz.manager.impl.FunnyManagerImpl.collectionManager)
└─────┘Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
2.6版本在org.springframework.boot.SpringApplication類里增加了allowCircularReferences屬性來控制循環(huán)依賴是否允許,默認值是false。
private boolean allowCircularReferences; /** * Sets whether to allow circular references between beans and automatically try to * resolve them. Defaults to {@code false}. * @param allowCircularReferences if circular references are allowed * @since 2.6.0 * @see AbstractAutowireCapableBeanFactory#setAllowCircularReferences(boolean) */ public void setAllowCircularReferences(boolean allowCircularReferences) { this.allowCircularReferences = allowCircularReferences; }
所以,要保持和2.6版本之前行為一樣的話,就把allowCircularReferences屬性設(shè)置為true。
設(shè)置可以添加配置spring.main.allow-circular-references=true,或通過SpringApplication
或 SpringApplicationBuilder
對象直接設(shè)置屬性。
2.1版本禁用Bean覆蓋
當(dāng)出現(xiàn)同名bean時,會判斷是否允許覆蓋beanDefinition,不允許則拋出BeanDefinitionOverrideException異常。
實現(xiàn)邏輯如下:
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName); if (existingDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition); } // ... this.beanDefinitionMap.put(beanName, beanDefinition); }
2.1版本在org.springframework.boot.SpringApplication類里增加了allowBeanDefinitionOverriding屬性來控制是否允許bean覆蓋,默認值是false。
private boolean allowBeanDefinitionOverriding; /** * Sets if bean definition overriding, by registering a definition with the same name * as an existing definition, should be allowed. Defaults to {@code false}. * @param allowBeanDefinitionOverriding if overriding is allowed * @since 2.1.0 * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding(boolean) */ public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; }
所以,要和老版本兼容的話,就把allowBeanDefinitionOverriding屬性設(shè)置為true。
設(shè)置可以添加配置spring.main.allow-bean-definition-overriding=true,或通過SpringApplication
對象直接設(shè)置屬性。
默認的路徑匹配策略改成了PATH_PATTERN_PARSER
2.6版本之前默認策略是ANT_PATH_MATCHER,改成PATH_PATTERN_PARSER會遇到IllegalArgumentException錯誤。
java.lang.IllegalArgumentException: Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".
解決方案是將策略回滾到ANT_PATH_MATCHER:
**spring.mvc.pathmatch.matching-strategy=***ANT_PATH_MATCHER*
Spring webmvc
Cors不允許將allowedOrigins設(shè)置為*
原來為了方便,會將跨域的請求來源設(shè)置為代表允許來自所有host的請求。
5.3開始增加了allowedOrigins值的校驗,不允許為,否則拋出IllegalArgumentException異常。
/** * Validate that when {@link #setAllowCredentials allowCredentials} is {@code true}, * {@link #setAllowedOrigins allowedOrigins} does not contain the special * value {@code "*"} since in that case the "Access-Control-Allow-Origin" * cannot be set to {@code "*"}. * @throws IllegalArgumentException if the validation fails * @since 5.3 */ public void validateAllowCredentials() { if (this.allowCredentials == Boolean.TRUE && this.allowedOrigins != null && this.allowedOrigins.contains(ALL)) { throw new IllegalArgumentException( "When allowCredentials is true, allowedOrigins cannot contain the special value \\"*\\" " + "since that cannot be set on the \\"Access-Control-Allow-Origin\\" response header. " + "To allow credentials to a set of origins, list them explicitly " + "or consider using \\"allowedOriginPatterns\\" instead."); } }
另外,5.3增加了allowedOriginPatterns屬性來代替allowedOrigins的功能。所以,要允許所有host的跨域請求的話,把allowedOriginPatterns設(shè)置為*。
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 允許跨域訪問的路徑 .allowedOriginPatterns("*") // 允許跨域訪問的源 } } /** * Alternative to {@link #setAllowedOrigins} that supports more flexible * origins patterns with "*" anywhere in the host name in addition to port * lists. Examples: * <ul> * <li>{@literal https://*.domain1.com} -- domains ending with domain1.com * <li>{@literal https://*.domain1.com:[8080,8081]} -- domains ending with * domain1.com on port 8080 or port 8081 * <li>{@literal https://*.domain1.com:[*]} -- domains ending with * domain1.com on any port, including the default port * </ul> * <p>In contrast to {@link #setAllowedOrigins(List) allowedOrigins} which * only supports "*" and cannot be used with {@code allowCredentials}, when * an allowedOriginPattern is matched, the {@code Access-Control-Allow-Origin} * response header is set to the matched origin and not to {@code "*"} nor * to the pattern. Therefore allowedOriginPatterns can be used in combination * with {@link #setAllowCredentials} set to {@code true}. * <p>By default this is not set. * @since 5.3 */ public CorsConfiguration setAllowedOriginPatterns(@Nullable List<String> allowedOriginPatterns) { if (allowedOriginPatterns == null) { this.allowedOriginPatterns = null; } else { this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size()); for (String patternValue : allowedOriginPatterns) { addAllowedOriginPattern(patternValue); } } return this; }
靜態(tài)文件是否存在的判斷方式變了
5.3版本開始,ClassPathResource類型的資源文件,判斷是否可讀的isReadable()方法的邏輯改成了文件存在且內(nèi)容不為空。當(dāng)我們訪問一個內(nèi)容為空的資源文件時,spring返回404。
例如,訪問http://localhost:8080/hello.html,spring會在/META-INF/resource、resources、static、public這幾個目錄下查找hello.html。如果文件放在static文件夾下,實際查找的是/static/hello.html文件。如果是jar包里,則完整的路徑是這樣jar:file:/opt/apps/demo.jar!/BOOT-INF/classes!/static/hello.html。
然后我們看看5.3前后版本代碼,對這個文件是否可讀判斷的差異。
5.3版本之前,jar開頭的文件直接返回true。
// 5.3之前 @Override public boolean isReadable() { try { URL url = getURL(); // file/vfsfile/vfs開頭的url if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution File file = getFile(); return (file.canRead() && !file.isDirectory()); } else { return true; } } catch (IOException ex) { return false; } }
5.3版本開始,jar開頭的文件會通過con.getContentLengthLong()獲取文件長度,如果是0的話就返回false。
@Override public boolean isReadable() { URL url = resolveURL(); return (url != null && checkReadable(url)); } boolean checkReadable(URL url) { try { // file/vfsfile/vfs開頭的url if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution File file = getFile(); return (file.canRead() && !file.isDirectory()); } else { // Try InputStream resolution for jar resources URLConnection con = url.openConnection(); customizeConnection(con); if (con instanceof HttpURLConnection) { HttpURLConnection httpCon = (HttpURLConnection) con; httpCon.setRequestMethod("HEAD"); int code = httpCon.getResponseCode(); if (code != HttpURLConnection.HTTP_OK) { httpCon.disconnect(); return false; } } long contentLength = con.getContentLengthLong(); if (contentLength > 0) { return true; } else if (contentLength == 0) { // Empty file or directory -> not considered readable... return false; } else { // Fall back to stream existence: can we open the stream? getInputStream().close(); return true; } } } catch (IOException ex) { return false; } }
所以,5.3開始,靜態(tài)文件不能是空文件,否則會返回404。
RequestMappingInfo#getPatternsCondition()返回null
5.3開始新增了pathPatternsCondition屬性,它和patternsCondition是互斥的,所以getPatternsCondition()可能會返回null了。
可以通過getActivePatternsCondition()方法獲取RequestCondition對象:
/** * Returns either {@link #getPathPatternsCondition()} or * {@link #getPatternsCondition()} depending on which is not null. * @since 5.3 */ @SuppressWarnings("unchecked") public <T> RequestCondition<T> getActivePatternsCondition() { if (this.pathPatternsCondition != null) { return (RequestCondition<T>) this.pathPatternsCondition; } else if (this.patternsCondition != null) { return (RequestCondition<T>) this.patternsCondition; } else { // Already checked in the constructor... throw new IllegalStateException(); } }
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 內(nèi)置接口 Serializable示例詳解
這篇文章主要為大家介紹了Java 內(nèi)置接口 Serializable示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11利用Spring MVC+Mybatis實現(xiàn)Mysql分頁數(shù)據(jù)查詢的過程詳解
這篇文章主要給大家介紹了關(guān)于利用Spring MVC+Mybatis實現(xiàn)Mysql分頁數(shù)據(jù)查詢的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08使用SpringCloudApiGateway之支持Cors跨域請求
這篇文章主要介紹了使用SpringCloudApiGateway之支持Cors跨域請求的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07設(shè)計模式之責(zé)任鏈模式_動力節(jié)點Java學(xué)院整理
這篇文章主要為大家詳細介紹了設(shè)計模式之責(zé)任鏈模式的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08PowerJob的GridFsManager工作流程源碼解讀
這篇文章主要為大家介紹了PowerJob的GridFsManager工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01SpringBoot+Hutool+thymeleaf完成導(dǎo)出Excel的實現(xiàn)方法
這篇文章主要介紹了SpringBoot+Hutool+thymeleaf完成導(dǎo)出Excel,本篇示例當(dāng)中不僅僅有后端,而且還提供了前端html,html當(dāng)中利用js將后端 輸出流直接下載為文件,需要的朋友可以參考下2022-03-03