开发者

SpringBoot升级到2.7.18后不兼容的地方及解决

开发者 https://www.devze.com 2024-08-16 10:23 出处:网络 作者: 李昂的数字之旅
目录背景版本升级不兼容的地方Spring bootSpring webmvc总结背景 最近为了给kafka加性能指标采集功能,调研后发现spring-kafka在2.3版本之后就自带了Micrometer指标采集功能。
目录
  • 背景
  • 版本升级
  • 不兼容的地方
    • Spring boot
    • Spring webmvc
  • 总结

    背景

    最近为了给kafka加性能指标采集功能,调研后发现spring-kafka在2.3版本之后就自带了Micrometer指标采集功能。

    但是当前项目的spring-boot版本是2.0.2.RELEASE,对应的spring-kafka版本是2.1.6.RELEASE,所以准备将spring-boot版本升级到2.7.18,这是2.x系列的最高版本,对应的spring-kafka版本是2.8.11。

    版本升级

    module升级前version升级后version
    spring-boot2.0.2.RELEASE2.7.18
    spring-webmvc5.0.6.RELEASE5.3.31
    spring-kafka2.1.6.RELEASE2.8.11

    不兼容的地方

    Spring boot

    2.6版本开始默认禁用Bean的循环依赖

    项目启动会检测是否存在循环依赖,存在就报如下错误。

    ***************************

    APPLICATION FAILED TO START

    ***************************

    Description:

    The depe编程ndencies 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 priv编程客栈ate 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属性来控制循环依赖是否允许,默认值是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属性设置为true。

    设置可以添加配置spring.main.allow-circular-references=true,或通过SpringApplicationSpringApplicationBuilder 对象直接设置属性。

    2.1版本禁用Bean覆盖

    当出现同名bean时,会判断是否允许覆盖beanDefinition,不允许则抛出BeanDefinitionOverrideException异常。

    实现逻辑如下:

    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    ifhttp://www.devze.com (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属性设置为true。

    设置可以添加配置spring.main.allow-bean-definition-overriding=true,或通过SpringApplication 对象直接设置属性。

    默认的路径匹配策略改成了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设置为*

    原来为了方便,会将跨域的请求来源设置为代表允许来自所有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 validateAllowCredeUknrynoIwFntials() {
    		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设置为*。

    @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;
    }

    静态文件是否存在的判断方式变了

    5.3版本开始,ClassPathResource类型的资源文件,判断是否可读的isReadable()方法的逻辑改成了文件存在且内容不为空。当我们访问一个内容为空的资源文件时,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 boolepythonan 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开始,静态文件不能是空文件,否则会返回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();
    	}
    }

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号