controller
@Controller와 @RestController의 차이
@Controller HTML 뷰 페이지를 반환할 때 사용 (Model 객체로 데이터 전달)
@Controller
public class PageController {
@GetMapping("/hello")
public String helloPage(Model model) {
model.addAttribute("message", "Hello Spring!");
return "hello";
}
}
@RestController JSON 형식의 데이터를 반환할 때 사용 (API 서버에서 사용)
@RestController
public class ApiController {
@GetMapping("/api/hello")
public String helloApi() {
return "Hello API!";
}
}
@RequestMapping, @GetMapping, @PostMapping
@RequestMapping("/users") 공통 URL 경로를 지정
@GetMapping, @PostMapping 각각 GET / POST 요청을 처리할 메서드 지정
- 중복 가능:
@GetMapping({"/", "/list"})
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/signup")
public String showSignupForm() {
return "signup-form";
}
@PostMapping("/signup")
public String handleSignup(User user) {
return "signup-success";
}
@GetMapping({"/", "/list"})
public String userList() {
return "user-list";
}
}
데이터 받는 방식
- Form 방식 (
User user 또는 String name 등으로 자동 매핑)
@PostMapping("/signup")
public String handleSignup(User user) {
System.out.println(user.getUsername());
return "signup-success";
}
- JSON 방식 (
@RequestBody User user → JSON Body를 파싱)
@PostMapping("/api/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(user);
}
PathVariable vs RequestParam
@PathVariable URL 경로 안에서 변수 추출
@RequestParam 쿼리 파라미터 (?id=1) 방식
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
return "User ID: " + id;
}
@GetMapping("/find")
public String findUser(@RequestParam Long id) {
return "User ID: " + id;
}
모델에 데이터 전달
@GetMapping("/profile")
public String userProfile(Model model) {
model.addAttribute("name", "홍길동");
return "profile";
}
리다이렉트
@PostMapping("/delete")
public String deleteUser(Long id) {
return "redirect:/users/";
}
- 브라우저에게 다른 URL로 이동하라고 지시하는 방식
- 주로
POST 요청 후 GET 페이지로 이동할 때 사용
예외 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return ResponseEntity
.internalServerError()
.body("서버 오류가 발생했습니다: " + ex.getMessage());
}
}
- 예외 발생 시 JSON 형태로 메시지 응답 가능
기타 개념
@RequiredArgsConstructor final 필드 기반 생성자 자동 생성 (롬복)
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
→ 생성자 자동 생성
@ResponseBody 문자열이나 객체를 그대로 HTTP 응답 Body로 반환
@GetMapping("/text")
@ResponseBody
public String plainText() {
return "그냥 텍스트로 응답합니다";
}
ResponseEntity 응답의 본문 + HTTP 상태 코드를 함께 보낼 때 사용
@GetMapping("/api/status")
public ResponseEntity<String> apiStatus() {
return ResponseEntity.status(200).body("OK");
}
domain
import lombok.*;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class User {
private Long id;
private String username;
private String password;
private String name;
}
주요 어노테이션 설명
| 어노테이션 | 설명 |
|---|
@Data | Getter, Setter, toString, equals, hashCode, RequiredArgsConstructor 자동 생성 |
@AllArgsConstructor | 모든 필드를 파라미터로 받는 생성자 생성 |
@NoArgsConstructor | 파라미터 없는 기본 생성자 생성 |
@Builder | 빌더 패턴 적용 (메서드 체인 방식으로 객체 생성 가능) |
event, listner
package com.codeit.start.event;
import com.codeit.start.domain.User;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class UserRegisteredEvent extends ApplicationEvent {
private final User user;
public UserRegisteredEvent(Object source, User user) {
super(source);
this.user = user;
}
}
package com.codeit.start.listener;
import com.codeit.start.event.UserRegisteredEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class UserEventListener {
@EventListener
public void onApplicationEvent(UserRegisteredEvent event) {
String username = event.getUser().getUsername();
System.out.println("[메일 전송] " + username + "님이 가입하였습니다." );
System.out.println("[메일 전송 완료]");
}
}
- 사용자가 회원가입에 성공했을 때 발생하는 사용자 정의 이벤트
- Spring에서 제공하는
ApplicationEvent를 상속받아 만들며 이벤트 리스너(@EventListener)를 통해 다른 컴포넌트에 알릴 수 있습니다.
필수 구성요소 설명
| 요소 | 설명 |
|---|
extends ApplicationEvent | Spring 이벤트 객체로 만들기 위한 상속 |
Object source | 이벤트를 발생시킨 주체 (보통 this) |
User user | 이벤트와 함께 전달할 데이터 (가입된 사용자 정보) |
@Getter | Lombok으로 user에 대한 getter 자동 생성 |
사용 예시
이벤트 발행
@Autowired
private ApplicationEventPublisher eventPublisher;
public void register(User user) {
eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
}
이벤트 리스너
@Component
public class WelcomeEmailListener {
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
User user = event.getUser();
System.out.println("📧 환영 이메일 전송: " + user.getUsername());
}
}
정리
ApplicationEvent를 상속하여 커스텀 이벤트 클래스를 만들 수 있다.
- 이벤트 객체는 필요한 데이터를 함께 보낼 수 있다 (
User 등).
ApplicationEventPublisher로 발행하고, @EventListener로 수신 처리한다.
- 관심사 분리(Separation of Concerns)에 매우 유용
service
UserService
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
public User register(User user) {
User saveUser = userRepository.save(user);
eventPublisher.publishEvent(new UserRegisteredEvent(this, saveUser));
return saveUser;
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public void deleteUser(Long id) {
userRepository.delete(id);
}
}
| 기능 | 설명 |
|---|
@Service | Spring이 관리하는 서비스 계층 컴포넌트 |
@Autowired | 필드 기반 의존성 주입 (스프링 5 이후에는 생성자 주입 권장) |
register() | 회원 등록 후 UserRegisteredEvent 이벤트 발행 |
eventPublisher | 이벤트를 리스너로 전달하는 이벤트 중개자 역할 |
이벤트 발행 로직
eventPublisher.publishEvent(new UserRegisteredEvent(this, saveUser));
- 이 코드가 실행되면,
UserRegisteredEvent가 발생하고
- 이를 감지한
@EventListener 클래스에서 후처리(예: 이메일 전송)를 진행할 수 있습니다.
DI 권장 방식 비교
| 방식 | 설명 | 권장 여부 |
|---|
@Autowired 필드 주입 | 코드 간결하지만 테스트 어려움 | ❌ (비권장) |
생성자 주입 (@RequiredArgsConstructor) | 불변성 보장 + 테스트 용이 | ✅ (권장) |
즉, UserService는 아래처럼 바꾸는 게 더 좋습니다:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
...
}
repository
UserRepository – 인메모리 회원 저장소
@Repository
public class UserRepository {
private final List<User> list = new ArrayList<>();
public UserRepository() {
list.add(new User(1L, "test1", "1234", "홍길동"));
list.add(new User(2L, "test2", "1234", "박길동"));
list.add(new User(3L, "test3", "1234", "최길동"));
}
public User save(User user) {
findById(user.getId()).ifPresent(list::remove);
list.add(user);
return user;
}
public Optional<User> findById(Long id) {
return list.stream().filter(x -> x.getId().equals(id)).findFirst();
}
public List<User> findAll() {
return list;
}
public long count() {
return list.size();
}
public void delete(Long id) {
list.removeIf(x -> x.getId().equals(id));
}
public boolean existsById(Long id) {
return list.stream().anyMatch(x -> x.getId().equals(id));
}
}
@Repository Spring에서 Bean으로 등록됨. DAO 계층 의미
List<User> list DB 대신 임시로 사용하는 인메모리 리스트