我的项目涉及SpringBoot。因此,在Service
中,我创建了一种方法来检索所有用户可用的默认索引:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private IndexRepository indexRepository;
/**
* Read from application.properties
*
* We only store the index ids in the application.properties
*/
@Value("${default.allowed.ids}")
private String defaultAllowedIds;
// I've also tried and removed the static with the same behaviour
private static HashSet<Index> listOfDefaultAllowedIndex = null;
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
public User findByResetToken(String resetToken) {
return userRepository.findByResetToken(resetToken);
}
/**
* Saves the user in the database and encrypts the password with bcrypt.
*
* @param user
*/
public void save(User user) {
// Code to save a new user
}
public void update(User user) {
// COde to update the user
}
/**
* List the default index allowed by default to all users
*
* @return the listOfDefaultAllowedIndex
*/
private HashSet<IndexSetup> getListOfDefaultAllowedIndex() {
System.err.println("TEST : DEFAULT LIST CONTAINS " + (listOfDefaultAllowedIndex != null ?
listOfDefaultAllowedIndex.size() + " index" : "NULL"));
// This is null at the beginning then it grows
System.err.println("TEST : THE DEFAULT STRING IS " + defaultAllowedIndexIds);
// This is always an empty string
if (listOfDefaultAllowedIndex == null) {
// 1) Gets the ids from the property
HashSet<String> defaultIds = new HashSet(Stream.of(
defaultAllowedIndexIds.split(
SEMI_COLON_SEPARATOR)).collect(Collectors.toSet()));
System.err.println("TEST : DEFAULT IDS : " + defaultIds); // THis is empty
// 2) Converts them into list of Index object
listOfDefaultAllowedIndex = new HashSet(StreamSupport
.stream(indexRepository.findAll().spliterator(),
false)
.filter(idx -> defaultIds.contains(Integer.toString(
idx.getId())))
.collect(Collectors.toSet()));
System.err.println("TEST : CONVERTED INDEX : ");
listOfDefaultAllowedIndex.stream().forEach(
idx -> System.err.println(idx.getName() + " (" + idx.getId() + ")")); // THis is empty
}
System.err.println("TEST : READING FROM CACHE (FROM THREAD " + Thread.currentThread().getName() + ") FOR THE DEFAULT IDS : ");
listOfDefaultAllowedIndex.stream().forEach(
idx -> System.err.println(idx.getName() + " (" + idx.getId() + ")"));
// THis should be empty but is growing
return listOfDefaultAllowedIndex;
}
因此,每次getListOfDefaultAllowedIndex
都会增长listOfDefaultAllowedIndex(实际上,如果它是一个List但它是一个Set,它会增长),尽管application.properties中的属性不会更改,而listOfDefaultAllowedIndex
只会更改在该方法中(如果为null,则在重置服务器时)。我已经从IDE中使用Find Usage
或Find in project
函数进行搜索,除了该方法之外,找不到任何结果。
我注意到线程名称不同,但是我无法弄清楚竞争条件如何将值更改为不同于空的值。
请注意:我注意到,如果我删除类变量listOfDefaultAllowedIndex
和if listOfDefaultAllowedIndex == null
条件,并在方法中声明listOfDefaultAllowedIndex
,那么它将按预期工作, listOfDefaultAllowedIndex
保持为空(在这种情况下,该属性为空),但是我无法解释原因。
因此,什么可以使变量更改?
编辑
正如@RalfKleberhoff所指出的,我确实是通过另一种方法这样做泄漏了原始的HashSet
:
HashSet<Index> allowedIndexes = this.getListOfDefaultAllowedIndex();
//...
allowedIndexes.addAll(...);
这个other old question实际上回答了我的问题。
要解决这个问题,我必须通过将Set
包装到Set
中来返回Collections.unmodifiableSet(listOfDefaultAllowedIndex)
的不变版本,如下面的答案所建议。哇,我完全忘记了!
任何帮助表示赞赏
答案 0 :(得分:2)
最可能的原因:
您正在将内部HashSet
泄漏给吸气剂的调用者。
因此,如果调用者修改了从您的获取方法返回的HashSet
作为返回值,则此更改将反映在您的内部字段值中,因为它是相同的HashSet
对象。
要检查:从您的吸气剂中返回Collections.unmodifiableSet(listOfDefaultAllowedIndex)
(并将返回类型调整为Set
而不是HashSet
)。然后,任何尝试更改Set
的调用方都将获得异常,而无需进行任何修改。然后,您可以决定如何继续:例如让此呼叫者进行复制,请务必返回该字段的HashSet的副本