一、背景说明
考虑到bean复制性能问题,在SSM框架中使用引入了orkia 实现bean复制。上线一段时间后,发现部分线上机器出现bean复制后属性丢失问题,重启后正常
二、问题详细说明
在线上机器使用orika 进行bean复制时,在bean属性类型、名称相同情况下无法赋值的情况,属性值全为null,其中最特别的仅出现在部分bean上,大部分bean复制完全正常,并且出现问题的bean不固定(每次重启都会变),tomcat容器重启恢复正常。
三、问题尝试解决
1、在问题发生后,tomcat容器重启后恢复正常,首先想到的是在 orkia 进行bean 复制时依赖的jar包冲突导致的(浪费了大量时间),简略说明下,在orkia-core 1.4.5 包下需要依赖的包中如下所示:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.paranamer</groupId>
<artifactId>paranamer</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.concurrentlinkedhashmap</groupId>
<artifactId>concurrentlinkedhashmap-lru</artifactId>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>java-sizeof</artifactId>
</dependency>
其中最容易出现jar包冲突的是javassist 和 slf4j-api 这两个包,通过jar包排除统一版本后,在夜深人静时上线,一切正常。
过一段时间后白天上线又出现同样的问题,中间耗费了大量的时间,经过各种日志排查监控,通过排除法确定,发生问题的机器是在服务器发布上线重启时,接到了用户的请求,由于上线操作员是个急性子刚将线上机器从软负载上摘掉就进行了机器重启,软负载缓存未过期,导致线上用户请求打到重启机器上。
2、结合1步排查结果,继续进行问题定位,组中发现在orkia文档中写到一句话,具体地址内容如下所示:
http://orika-mapper.github.io/orika-docs/faq.html
得到结果在 spring 中使用orkia 时需要将 ConfigurableMapper 定义成单例模式,结合着这个提示对我们代码进行排查,发现我们代码如下所示:
@Component
public class BeanMapper implements InitializingBean {
private MapperFacade mapper;
@Override
public void afterPropertiesSet() throws Exception {
if(mapper == null){
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapper = mapperFactory.getMapperFacade();
}
}
public <S, D> D map(S source, Class<D> destinationClass) {
return mapper.map(source, destinationClass);
}
}
进一步结合orkia 源码进行问题查找,发现MapperFacadeImpl 中会将赋值的bean信息统一存到到 MappingContext 中的typeCache存到内部属性中,其数据结构OpenIntObjectHashMap 类型。
进一步猜测得知由于此时MapperFacade 是懒加载方式,可能是在bean存储到MappingContext 之前,接到到了用户的请求,先一步将需要复制的bean存储到了MappingContext中,在 MappingContext中存储的bean 信息为null,最终导致问题的产生,这里仅是结合和orkia源码进行猜测,是否正确有待考察(在本地tomcat环境无法重现此问题)。
四、解决方案
将MapperFacade 改为预加载方式,具体修改后代码如下所示:
@Component
public class BeanMapper {
private final static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
private final static MapperFacade mapper = mapperFactory.getMapperFacade();
public <S, D> D map(S source, Class<D> destinationClass) {
return mapper.map(source, destinationClass);
}
}
通过以上方式最终解决问题。问题关键点如下所示:
1、MapperFacade 需要配置为单例
2、MapperFacade需要配置为预加载