📌 들어가며
오늘의 학습 목표:
Spring Boot의 핵심 개념들을 깊이 있게 이해하고, 실무에서 바로 활용할 수 있는 수준으로 정리하기
안녕하세요! 오늘은 Spring Boot 개발의 핵심이 되는 6가지 주제를 집중적으로 학습했습니다. Bean Scope부터 Spring Security까지, 실무에서 정말 자주 마주치는 개념들이라 더욱 집중해서 공부했어요. 제가 이해한 내용을 최대한 쉽게 풀어서 설명해보겠습니다! 💪
📌 Today I Learned
✅ Bean Scope - 빈의 생명주기 이해하기
✅ Spring MVC Framework - 웹 애플리케이션의 핵심
✅ SpringApplication - Spring Boot의 시작점
✅ Test Code - 안정적인 코드 작성하기
✅ Dirty Checking - JPA의 마법 같은 기능
✅ Spring Security - 인증과 인가
1️⃣ Bean Scope - 빈의 생명주기 완벽 이해하기

🎯 Bean Scope란?
Spring Container가 관리하는 Bean의 생명주기와 범위를 결정하는 개념입니다. 쉽게 말해 "이 객체를 언제, 어떻게, 얼마나 오래 살려둘 것인가?"를 정하는 것이죠.
📊 주요 Scope 종류
1) Singleton (기본값)
@Component
public class UserService {
// Spring Container당 하나의 인스턴스만 생성
}
특징:
- Spring Container가 시작될 때 단 하나의 인스턴스만 생성
- 모든 요청이 같은 객체를 공유
- 메모리 효율적이지만 상태를 가지면 안 됨 (Stateless)
2) Prototype
@Component
@Scope("prototype")
public class OrderForm {
// 요청할 때마다 새로운 인스턴스 생성
}
특징:
- 빈을 요청할 때마다 새로운 인스턴스 생성
- Spring Container는 생성 후 관리하지 않음
- 상태를 가져야 하는 객체에 사용
3) Request (웹 환경)
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class LoginUser {
// HTTP 요청당 하나의 인스턴스
}
특징:
- HTTP 요청마다 새로운 빈 생성
- 요청이 끝나면 소멸
- 사용자별 임시 데이터 저장에 유용
4) Session (웹 환경)
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
// HTTP 세션당 하나의 인스턴스
}
특징:
- HTTP 세션마다 하나의 빈 생성
- 세션이 유지되는 동안 같은 빈 사용
- 장바구니, 사용자 설정 등에 활용

💡 오늘의 깨달음
처음에는 "왜 Singleton이 기본값일까?"가 궁금했는데, 실습하면서 이해했어요. 대부분의 비즈니스 로직은 상태를 가지지 않고(Stateless), 여러 사용자가 동시에 사용해도 문제없기 때문이더라고요. 하지만 사용자별로 다른 데이터를 가져야 한다면 Request나 Session Scope를 사용해야 한다는 것도 배웠습니다!
2️⃣ Spring MVC Framework - 웹 개발의 핵심 구조
🏗️ MVC 패턴이란?


Model-View-Controller의 약자로, 애플리케이션을 세 가지 역할로 분리하는 디자인 패턴입니다.
- Model: 데이터와 비즈니스 로직
- View: 사용자 인터페이스
- Controller: Model과 View를 연결
🔄 Spring MVC의 요청 처리 흐름

클라이언트 요청
↓
DispatcherServlet (프론트 컨트롤러)
↓
HandlerMapping (어떤 컨트롤러로 갈지 결정)
↓
Controller (비즈니스 로직 처리)
↓
ViewResolver (어떤 View를 보여줄지 결정)
↓
View (최종 응답 생성)
↓
클라이언트에게 응답
📝 실제 코드 예시
@Controller
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
// GET 요청 처리
@GetMapping("/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail"; // View 이름
}
// POST 요청 처리
@PostMapping
public String createUser(@ModelAttribute UserForm form) {
userService.create(form);
return "redirect:/users";
}
// REST API (@ResponseBody 사용)
@GetMapping("/api/{id}")
@ResponseBody
public User getUserApi(@PathVariable Long id) {
return userService.findById(id);
}
}
🎨 주요 어노테이션 정리
어노테이션 용도
| @Controller | View를 반환하는 컨트롤러 |
| @RestController | JSON/XML 등 데이터를 반환하는 컨트롤러 |
| @RequestMapping | URL 매핑 (공통) |
| @GetMapping | GET 요청 처리 |
| @PostMapping | POST 요청 처리 |
| @PathVariable | URL 경로의 변수 추출 |
| @RequestParam | 쿼리 파라미터 추출 |
| @ModelAttribute | 폼 데이터를 객체로 바인딩 |
| @ResponseBody | 메서드의 리턴값을 HTTP 응답 본문으로 전송 |
🔍 오늘 겪었던 문제와 해결
문제: @Controller와 @RestController의 차이를 명확히 이해하지 못해서 JSON 응답이 제대로 안 나왔어요.
해결: @Controller는 기본적으로 View 이름을 반환하고, JSON을 반환하려면 메서드마다 @ResponseBody를 붙여야 합니다. 반면 @RestController는 클래스 레벨에서 모든 메서드에 @ResponseBody가 적용되어 있어 JSON 응답에 특화되어 있다는 것을 배웠습니다!
3️⃣ SpringApplication - Spring Boot의 시작점
🚀 SpringApplication이란?
Spring Boot 애플리케이션의 진입점이자, 애플리케이션 컨텍스트를 생성하고 설정하는 핵심 클래스입니다.

📌 기본 구조
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
⚙️ SpringApplication 커스터마이징
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
// 배너 끄기
app.setBannerMode(Banner.Mode.OFF);
// 추가 프로파일 설정
app.setAdditionalProfiles("dev");
// 기본 프로퍼티 설정
Properties properties = new Properties();
properties.setProperty("server.port", "8081");
app.setDefaultProperties(properties);
// 애플리케이션 실행
app.run(args);
}
}
🎯 @SpringBootApplication 어노테이션 분석
@SpringBootApplication
= @SpringBootConfiguration // Spring Boot 설정 클래스임을 나타냄
+ @EnableAutoConfiguration // 자동 설정 활성화
+ @ComponentScan // 컴포넌트 스캔 활성화
동작 원리:
- @ComponentScan: 현재 패키지부터 하위 패키지까지 @Component, @Service, @Repository, @Controller 등을 찾아 Bean으로 등록
- @EnableAutoConfiguration: spring.factories 파일에 정의된 자동 설정 클래스들을 조건에 맞게 자동으로 Bean 등록
- @SpringBootConfiguration: 설정 클래스임을 표시 (내부적으로 @Configuration 포함)
💻 실무 활용 팁
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
// 애플리케이션 시작 후 초기화 작업
@Bean
public CommandLineRunner initData(UserRepository userRepository) {
return args -> {
// 초기 데이터 세팅
userRepository.save(new User("admin", "admin@example.com"));
System.out.println("초기 데이터 세팅 완료!");
};
}
}
📚 오늘 배운 것
Spring Boot의 자동 설정이 얼마나 편리한지 실감했어요! 예전에는 XML 설정 파일을 일일이 작성해야 했다는데, 지금은 의존성만 추가하면 대부분 자동으로 설정된다는 게 놀라웠습니다. 특히 @SpringBootApplication 하나에 세 가지 어노테이션이 합쳐져 있다는 것을 알고 나니, Spring Boot의 철학이 "개발자가 비즈니스 로직에 집중할 수 있도록 반복적인 설정을 최소화한다"는 것을 이해하게 되었습니다.
4️⃣ Test Code - 안정적인 애플리케이션 만들기
✅ 테스트 코드를 작성하는 이유
- 버그 조기 발견: 개발 단계에서 문제를 미리 잡을 수 있음
- 리팩토링 자신감: 코드 변경 후에도 기존 기능이 정상 작동하는지 확인 가능
- 문서화 역할: 테스트 코드가 곧 사용 예시
- 협업 효율성: 다른 개발자가 코드를 수정해도 테스트로 검증 가능
🔧 Spring Boot 테스트 환경 설정
@SpringBootTest // 전체 애플리케이션 컨텍스트 로드
@AutoConfigureMockMvc // MockMvc 자동 설정
class UserServiceTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserService userService;
@MockBean // Mock 객체로 대체
private UserRepository userRepository;
// 각 테스트 전에 실행
@BeforeEach
void setUp() {
// 테스트 데이터 준비
}
// 각 테스트 후에 실행
@AfterEach
void tearDown() {
// 정리 작업
}
}
📝 다양한 테스트 작성 예시


1) 단위 테스트 (Unit Test)
@Test
@DisplayName("사용자 생성 테스트")
void createUser() {
// Given (준비)
UserDto userDto = new UserDto("홍길동", "hong@example.com");
User user = new User(1L, "홍길동", "hong@example.com");
when(userRepository.save(any(User.class))).thenReturn(user);
// When (실행)
User savedUser = userService.create(userDto);
// Then (검증)
assertNotNull(savedUser);
assertEquals("홍길동", savedUser.getName());
assertEquals("hong@example.com", savedUser.getEmail());
// Mock 호출 검증
verify(userRepository, times(1)).save(any(User.class));
}
2) 통합 테스트 (Integration Test)
@Test
@DisplayName("사용자 조회 API 테스트")
void getUserApi() throws Exception {
// Given
User user = new User(1L, "홍길동", "hong@example.com");
when(userService.findById(1L)).thenReturn(user);
// When & Then
mockMvc.perform(get("/users/{id}", 1L))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("홍길동"))
.andExpect(jsonPath("$.email").value("hong@example.com"))
.andDo(print()); // 요청/응답 출력
}
3) Repository 테스트
@DataJpaTest // JPA 관련 설정만 로드
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
@DisplayName("이메일로 사용자 찾기")
void findByEmail() {
// Given
User user = new User("홍길동", "hong@example.com");
userRepository.save(user);
// When
Optional<User> found = userRepository.findByEmail("hong@example.com");
// Then
assertTrue(found.isPresent());
assertEquals("홍길동", found.get().getName());
}
}
4) 예외 처리 테스트
@Test
@DisplayName("존재하지 않는 사용자 조회 시 예외 발생")
void getUserNotFound() {
// Given
when(userRepository.findById(999L))
.thenReturn(Optional.empty());
// When & Then
assertThrows(UserNotFoundException.class, () -> {
userService.findById(999L);
});
}
🎯 테스트 작성 Best Practice
- 테스트 메서드 이름은 명확하게: test1() ❌ → createUser_Success() ✅
- Given-When-Then 패턴 사용: 준비-실행-검증 단계를 명확히
- 하나의 테스트는 하나의 기능만: 여러 기능을 한 번에 테스트하지 말 것
- 테스트는 독립적이어야 함: 다른 테스트에 의존하지 않도록
- 경계값 테스트: 0, 음수, null, 빈 문자열 등 예외 케이스도 테스트
🚨 오늘 겪었던 어려움과 해결
문제: 처음에 테스트 코드를 작성할 때, @SpringBootTest를 사용하니 테스트 실행 시간이 너무 오래 걸렸어요.
해결: 전체 컨텍스트를 로드하는 @SpringBootTest 대신, 필요한 부분만 테스트하는 어노테이션을 사용하기로 했습니다.
- Repository만 테스트: @DataJpaTest
- Controller만 테스트: @WebMvcTest
- Service만 테스트: @MockBean으로 의존성 주입
이렇게 하니 테스트 속도가 훨씬 빨라졌고, 테스트의 목적도 더 명확해졌어요!
5️⃣ Spring Data JPA의 Dirty Checking - JPA의 마법
🎩 Dirty Checking이란?
JPA의 변경 감지 기능으로, 엔티티의 변경사항을 자동으로 감지하여 데이터베이스에 반영하는 메커니즘입니다. 개발자가 직접 update() 메서드를 호출하지 않아도 된다는 게 놀라웠어요!
🔍 동작 원리
1. 트랜잭션 시작
↓
2. 엔티티 조회 (영속성 컨텍스트에 저장)
↓
3. 엔티티 수정 (Setter 호출 등)
↓
4. 트랜잭션 커밋 시점
↓
5. JPA가 스냅샷과 비교하여 변경 감지
↓
6. 자동으로 UPDATE 쿼리 실행
💻 실제 코드 예시
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
// Dirty Checking 활용
public void updateUserEmail(Long userId, String newEmail) {
// 1. 엔티티 조회 (영속 상태)
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("사용자를 찾을 수 없습니다."));
// 2. 엔티티 수정 (변경 감지 대상)
user.changeEmail(newEmail);
// 3. save() 호출 불필요! 트랜잭션 커밋 시 자동 UPDATE
// userRepository.save(user); ← 불필요!
}
}
실행되는 SQL:

-- 조회 쿼리
SELECT * FROM users WHERE id = 1;
-- 트랜잭션 커밋 시 자동 실행되는 UPDATE 쿼리
UPDATE users SET email = 'new@example.com' WHERE id = 1;
🔐 Dirty Checking이 작동하는 조건
- 영속 상태의 엔티티: EntityManager가 관리하는 엔티티여야 함
- 트랜잭션 내부: @Transactional 안에서만 작동
- 변경 감지 시점: 트랜잭션 커밋 시점에 실행
⚠️ 주의사항과 최적화
@Service
@Transactional
public class UserService {
// ❌ 잘못된 예시: 영속 상태가 아님
public void updateUserWrong(Long userId, String newEmail) {
User user = new User(userId, "홍길동", "old@example.com");
user.changeEmail(newEmail); // 변경되지 않음! (준영속 상태)
}
// ✅ 올바른 예시: 영속 상태
public void updateUserCorrect(Long userId, String newEmail) {
User user = userRepository.findById(userId)
.orElseThrow();
user.changeEmail(newEmail); // 자동으로 UPDATE 쿼리 실행
}
// 🎯 최적화: 필요한 필드만 수정
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
public void updateEmailOnly(@Param("id") Long id, @Param("email") String email);
}
📊 Dirty Checking vs 직접 UPDATE
구분 Dirty Checking 직접 UPDATE
| 장점 | 코드 간결, 자동화 | 성능 최적화 가능 |
| 단점 | 모든 필드 UPDATE | 쿼리 직접 작성 필요 |
| 사용 시기 | 일반적인 수정 | 대량 데이터, 특정 필드만 |
💡 오늘의 성취
처음에는 "왜 save()를 호출하지 않아도 저장되지?"라는 의문이 들었는데, Dirty Checking의 원리를 이해하고 나니 JPA가 얼마나 개발자 친화적인지 깨달았어요. 특히 영속성 컨텍스트와 1차 캐시, 스냅샷 비교 메커니즘을 학습하면서 JPA의 내부 동작을 이해하게 되었습니다!
하지만 무분별하게 사용하면 성능 문제가 발생할 수 있다는 점도 배웠어요. 대량의 데이터를 수정할 때는 @Modifying과 @Query를 활용한 벌크 연산이 더 효율적이라는 것도 알게 되었습니다.
6️⃣ Spring Security - 인증과 인가 완벽 가이드
🔐 Authentication vs Authorization
- Authentication (인증): "당신이 누구인가?" - 사용자의 신원 확인
- Authorization (인가): "무엇을 할 수 있는가?" - 권한 확인

🏗️ Spring Security 아키텍처
클라이언트 요청
↓
Security Filter Chain
↓
UsernamePasswordAuthenticationFilter (로그인 처리)
↓
AuthenticationManager (인증 관리자)
↓
AuthenticationProvider (실제 인증 로직)
↓
UserDetailsService (사용자 정보 조회)
↓
SecurityContext에 인증 정보 저장
↓
Authorization Filter (권한 확인)
↓
Controller로 요청 전달
⚙️ Spring Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF 보호 (운영 환경에서는 활성화 권장)
.csrf(csrf -> csrf.disable())
// 권한 설정
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/register").permitAll() // 누구나 접근 가능
.requestMatchers("/admin/**").hasRole("ADMIN") // ADMIN만 접근
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 또는 ADMIN
.anyRequest().authenticated() // 나머지는 인증 필요
)
// 로그인 설정
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error=true")
.permitAll()
)
// 로그아웃 설정
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
)
// 세션 관리
.sessionManagement(session -> session
.maximumSessions(1) // 동시 세션 1개만 허용
.maxSessionsPreventsLogin(true) // 추가 로그인 차단
);
return http.build();
}
// 비밀번호 암호화
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
👤 UserDetailsService 구현
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 데이터베이스에서 사용자 조회
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + username));
// UserDetails 객체로 변환하여 반환
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 암호화된 비밀번호
.roles(user.getRole()) // "USER", "ADMIN" 등
.build();
}
}
🔑 JWT 기반 인증 (REST API용)

@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
private long validityInMilliseconds = 3600000; // 1시간
// 토큰 생성
public String createToken(String username, List<String> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("roles", roles);
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
// 토큰 검증
public boolean validateToken(String token) {
try {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return !claims.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
// 토큰에서 사용자 정보 추출
public String getUsername(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
🎯 컨트롤러에서 인증 정보 사용하기
@RestController
@RequestMapping("/api")
public class UserController {
// 현재 로그인한 사용자 정보 가져오기
@GetMapping("/me")
public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
String username = userDetails.getUsername();
// username으로 사용자 정보 조회
return userService.findByUsername(username);
}
// 권한 확인
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/users/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.ok().build();
}
}
🛡️ 보안 Best Practice
- 비밀번호는 반드시 암호화: BCryptPasswordEncoder 사용
- HTTPS 사용: 운영 환경에서 필수
- SQL Injection 방지: PreparedStatement 사용 (JPA는 기본 제공)
- XSS 방지: 입력값 검증 및 이스케이프 처리
- CSRF 보호: POST, PUT, DELETE 요청에 CSRF 토큰 사용
- 세션 관리: 타임아웃 설정, 동시 세션 제어
🚨 오늘 겪었던 문제와 극복
문제: JWT 토큰 기반 인증을 구현하다가 CORS 에러와 함께 401 Unauthorized 에러가 계속 발생했어요.
해결 과정:
- CORS 설정 추가: WebMvcConfigurer로 허용할 Origin 설정
- Security Filter Chain에서 JWT 필터 순서 조정
- OPTIONS 요청(Preflight)도 허용하도록 설정
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://localhost:3000");
configuration.addAllowedMethod("*");
configuration.addAllowedHeader("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
이 과정에서 브라우저의 Preflight 요청과 서버의 CORS 정책이 어떻게 상호작용하는지 깊이 이해하게 되었어요!
💭 오늘 하루 돌아보기
🎉 오늘의 성취
- Bean Scope의 실전 활용: Singleton이 기본인 이유와 각 Scope의 용도를 완벽히 이해했어요
- MVC 패턴 체화: 단순히 코드를 작성하는 것이 아니라, 요청이 어떻게 흘러가는지 전체 그림을 그릴 수 있게 되었습니다
- 테스트 주도 개발의 가치: 테스트 코드를 먼저 작성하니 버그를 미리 잡을 수 있었어요
- JPA의 매력 발견: Dirty Checking 덕분에 코드가 얼마나 간결해지는지 체감했습니다
- 보안의 중요성: Spring Security로 인증/인가를 구현하면서 보안이 얼마나 복잡하고 중요한지 깨달았어요
🤔 어려웠던 점과 개선 계획
어려웠던 점:
- JWT 토큰 기반 인증 구현 시 필터 체인의 순서와 CORS 설정이 복잡했어요
- Dirty Checking이 작동하지 않는 경우(준영속 상태)를 구분하는 게 헷갈렸습니다
- 테스트 코드 작성 시 어떤 어노테이션을 사용해야 할지 선택이 어려웠어요
개선 계획:
- 내일 계획: JWT 인증 흐름을 시퀀스 다이어그램으로 그려서 완벽히 이해하기
- 복습 방법:
- 오늘 배운 내용을 블로그에 정리하면서 다시 한번 복습
- Dirty Checking 동작 조건을 플로우차트로 만들어서 벽에 붙여두기
- 테스트 어노테이션 비교표 만들어서 언제 무엇을 쓸지 정리
- 실전 적용: 개인 프로젝트에 Spring Security와 JWT 인증을 적용해보기
✨ 좋았던 점
- 강사님이 실무 경험을 바탕으로 "이론상으론 이렇지만 실전에서는 이렇게 쓴다"는 식으로 설명해주셔서 너무 좋았어요
- 팀원들과 함께 Security 설정을 하면서 서로 모르는 부분을 알려주고 배울 수 있었습니다
- 실습 시간이 충분해서 직접 코드를 작성하면서 익힐 수 있었어요
💡 아쉬웠던 점
- Spring Security가 워낙 방대해서 하루에 다 배우기엔 시간이 부족했어요 (OAuth2, 소셜 로그인 등은 다음 시간에)
- 테스트 코드 작성법을 더 다양하게 연습해보고 싶었는데 시간 관계상 기본적인 내용만 다뤘네요
건의사항:
- Security 실습 시간을 좀 더 늘려주시면 좋을 것 같아요
- 실전 프로젝트에서 자주 쓰이는 테스트 패턴들을 몇 가지 더 다뤄주시면 감사하겠습니다
🎯 나만의 학습 팁
📚 효과적인 복습 방법
- 개념 정리 노트:
- Notion에 키워드 중심으로 정리 (검색하기 쉽게)
- 코드 예시는 GitHub Gist에 올려서 링크 걸기
- 머릿속 구조화:
- 복잡한 개념은 마인드맵으로 시각화
- 흐름이 있는 내용은 플로우차트로 그리기
- 실습 중심 학습:
- 단순히 코드를 따라 치는 게 아니라, 직접 변형해보기
- "이렇게 하면 어떻게 될까?" 실험 정신 발휘!
- 협업 학습:
- 스터디원에게 설명해보기 (가르치면서 배우는 게 최고)
- 서로의 코드 리뷰하면서 다른 접근법 배우기
📝 마치며
오늘은 정말 알차게 보낸 하루였습니다! 특히 Spring Security를 구현하면서 "보안"이라는 게 단순히 로그인만 처리하는 게 아니라, 인증/인가/세션 관리/CORS/CSRF 등 정말 많은 것들을 고려해야 한다는 걸 깨달았어요.
처음에는 막막했지만, 하나씩 차근차근 따라가니 어느새 JWT 기반 REST API 인증까지 구현할 수 있게 되었습니다. 물론 아직 부족한 부분도 많지만, 매일 조금씩 성장하고 있다는 게 느껴져서 뿌듯합니다! 💪
내일도 화이팅! 🔥