싱글톤 주의하기

문제

아래의 코드를 우선 읽어보자.

해결 전 심각한 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Controller
public class NoReadRecipientMailboxController {

@Autowired
private NoReadRecipientMailboxMapper recipientMailboxMapper;
private String user_id_session;
private String searchContent;
private List<RecipientEmail> recipientEmail;
private List<RecipientEmail> recipientStarEmail;
private List<RecipientEmail> recipientSearchEmail;

@GetMapping("/noreadreceivemailbox")
public String mailbox(Model model, HttpServletRequest req) {
user_id_session = (String)req.getSession().getAttribute("user_id");
recipientEmail = recipientMailboxMapper.selectList(user_id_session);
model.addAttribute("noreadrecipientEmail", recipientEmail);

recipientStarEmail = recipientMailboxMapper.selectStarList(user_id_session);
model.addAttribute("noreadrecipientStarEmail", recipientStarEmail);

recipientSearchEmail = recipientMailboxMapper.searchMailTitle(searchContent, user_id_session);
model.addAttribute("noreadrecipientSearchEmail", recipientSearchEmail);
this.searchContent = "";

return "mailbox/noreadreceivemailbox";
}
@PutMapping("/noreadreceivemailbox/searchMail/title/{searchContent}")
public ResponseEntity<?> searchMailTitle (@PathVariable String searchContent) {
this.searchContent = "%" + searchContent + "%";

return new ResponseEntity<>(true, HttpStatus.OK);
}
}

문제점 발견

코드를 잘 읽어보면 처음에는 어떤 문제가 있는지 잘 모르는 경우가 많다. 하지만 이 코드는 굉장한 문제점을 가지고 있고, 이제부터 이 문제점을 해결해나가고자 한다.

1
private List<RecipientEmail> recipientEmail;

이러한 방식으로 메소드 외부에 인스턴스 변수를 생성했을 때 스프링은 최초 동작 시에만 새롭게 생성하고, 이후에는 해당 변수를 그대로 반환한다. 그리고 싱글톤은 Thread-Safety하지 않다!
여러 사용자가 동시에 이 서비스를 이용하는 경우에 다른 사용자의 값을 반환받을 경우도 생기는 것이다. 이렇게 되면 다른 사용자의 메일을 볼 수 있을지도 모른다. 아니 실제로 그런 일이 발생한다.

그러므로 아래처럼 메소드 내부에 변수를 선언하는 것이 중요하다.

해결 후 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller
public class NoReadRecipientMailboxController {

@Autowired
private NoReadRecipientMailboxMapper recipientMailboxMapper;

@GetMapping("/noreadreceivemailbox")
public String mailbox(Model model, HttpServletRequest req, String searchContent, String view) {
String user_id_session = (String)req.getSession().getAttribute("user_id");

if(searchContent != null) {
String Content = "%" + searchContent + "%";
List<RecipientEmail> recipientSearchEmail = recipientMailboxMapper.searchMailTitle(Content, user_id_session);
model.addAttribute("noreadrecipientSearchEmail", recipientSearchEmail);
}
else if(view != null && view.equals("star")){
List<RecipientEmail> recipientStarEmail = recipientMailboxMapper.selectStarList(user_id_session);
model.addAttribute("noreadrecipientStarEmail", recipientStarEmail);
}
else {
List<RecipientEmail> recipientEmail = recipientMailboxMapper.selectList(user_id_session);
model.addAttribute("noreadrecipientEmail", recipientEmail);
}

return "mailbox/noreadreceivemailbox";
}
}