SpringBoot 的使用的中的一个奇怪问题 - Jackson ObjectMapper的注入冲突

SpringBoot 的使用的中的一个奇怪问题:

Jackson中Mapper的注入冲突


0x1 问题

在工作中,使用的框架是SpringBoot,为了把一些对象转换为web使用的json格式的数据,就常常需要一些框架来完成,关于Object转换Json,常用的框架并不多,主要是这几个:

  • Jackson: 这个框架基本成为了Spring的标配。
  • FastJson : 这个是国内的一个框架,出自阿里,在某些时候据说效率会比较高。
  • Gson : 没怎么用过。

我现在使用的框架就是Jackson。

SpringBoot开发的系统,有一个application.properties文件,他可以配置很多关于Spring方方面面的基础设置,其中就有这Jackson。由于某种特定的需求,我需要在原有的系统中添加一个新的功能,而这个功能的实现,需要一个自己的ObjectMapper – 哦,这个就是Jackson转换ObjectJson数据的东西。

但是,一个奇怪的问题就出现了,当这个新的ObjectMapper作为Bean出现之后,原本SpingBoot中对于ObjectMapper的配置全都失去了效果,后来向前辈请教,得知应该是新的Mapper覆盖了原有的Mapper。

项目有一个WebConfig类,其中一个方法是这样的:

    @Bean
    public JsonHandler jsonHandler(ObjectMapper objectMapper) {
        JsonHandler jh = new JsonHandler();
        jh.setMapper(objectMapper);
        return jh;
    }

这个项目在一个WEBConfig类中配置了一个用于转换Object到Json的类JsonHandler,它就需要一个ObjectMapper,在这个配置方法中打印出这个ObjectMapper的class,正是新配置的Mapper。

而且各个配置中都没有看到对ObjectMapper的注入,那么这个方法要求的ObjectMapper,在定义这个新的Mapper之前,他又是从哪里来的呢?

0x2 新注解

我发现,在Spring的STS(Spring官方提供的一个Eclipse版本)中,按住Ctrl的时候,可以点开application.properties配置,看到对应的源码,而Jackson的配置,是在spring-boot-autoconfigure这个包里面的,名字是JacksonProperties,而同一个包下,就有JacksonAutoConfiguration

首先我注意到的就是这个:

@Configuration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration 

很明显,这是两个注解,不过在Spring的使用中好像还没有见过他们。
而在这后面不远,就可以看到:

@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
	return builder.createXmlMapper(false).build();
}

看起来我找到那个ObjectMapper的Bean了。

0x3 这些注解的含义和使用
注解名称 注解参数 含义 作用
ConditionalOnBean type,value bean的class对象,Bean的名字 仅当Bean存在的时候,此注解修饰的内容才会起到作用
ConditionalOnMissingBean type,value bean的class对象,Bean的名字 仅当Bean不存在的时候,此注解修饰的内容才会起作用
ConditionalOnClass name,value 类名,类的class对象 仅当class存在的时候,此注解修饰内容才会起效
ConditionalOnMissingClass value 类名 仅当class不存在的时候,此注解修饰的内容才会起效
ConditionalOnJava range,value 描述java版本的枚举,定义在此注解中。 仅当java版本符合的时候才会起效
Conditional value Condition接口的实现类 根据指定类判别是否让修饰的内容起效
注解名称 作用
Primary 优先提供此注解修饰的Bean
0x4 那么,问题就解决了

从上面的JacksonAutoConfiguration中就可以知道,默认的ObjectMapper,是要在没有其他ObjectMapper的Bean出现的时候才会出现的,所以要解决这个问题,做法也很简单,将新的ObjectMapper添加@ConditionalOnBean这个注解,让他在默认的ObjectMapper出现之后才能被注册,这样默认的那个ObjectMapper就不会自动消失了,而且由于默认的ObjectMapper带有@Primary,所以会优先使用,到此为止,奇怪的Bean冲突问题就不存在了。

0x5 还是有一个后续

通过上面的操作后,可以发现虽然默认的ObjectMapper不会消失,但是CrudMapper却消失了,真是让人头大,,,

接下来我取消了@ConditionalOnBean注解,改用@DependsOn来控制Bean的初始化顺序,让他能够在jsonHandler加载之后在进行初始化,因为这个时候默认的ObjectMapper应该已经载入了,但是呢,想法是美好的,现实是残酷的:

Description:
There is a circular dependency between 6 beans in the application context:

好吧,出现了循环引用,应该是由于新的ObjectMapper是ObjectMapper的子类,而默认的ObjectMapper一旦发现有其他ObjectMapper的Bean,就不会注册,因此就导致了其他需要默认ObjectMapper的Bean对新的ObjectMapper出现了引用,这就出来了循环引用的问题。

我最终是这样做的,使用泛型作为返回类型,抹除本来的类型签名,这样就会导致config中不存在ObjectMapper的Bean定义,从而使得Jackson默认的那个可以得到加载,然后让他DependOn那个jsonHandler,确保它是在默认的ObjectMapepr之后才会出现。

注解名字 参数 含义
DependOn value(String[] Bean名字) 此Bean依赖于另外的Bean,必须在那些Bean之后进行初始化

另外,Java的Annotation配置中,Bean默认的id就是注解了@Bean的方法的名字,而@Autowired注解默认装配是ByType而@Resource是ByName,这里抹去了类型就是为了避免这种ByType的装配,防止由此导致的循环依赖,不过抹除了类型就无法使用ByType装配了,这里我使用的时@Resource来注入这个新的ObjectMapepr。

猜你喜欢

转载自blog.csdn.net/YunJian01/article/details/83030408