제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 @Slf4j 정리입니다.

 

로그가 뭐야?

로그(Log)는 애플리케이션이 실행되면서 발생하는 이벤트, 오류, 상태 정보 등을 기록하는 중요한 데이터입니다.
프로그램의 실행 흐름을 추적하고, 문제 발생 시 원인을 분석하는 데 필수적인 요소입니다.

로그가 중요한 이유

  1. 디버깅 & 문제 해결
    • 애플리케이션에서 오류가 발생했을 때, 로그를 통해 원인을 분석하고 해결할 수 있습니다.
    • 예를 들어, 사용자가 API 요청을 보냈을 때 응답이 늦거나 실패하는 경우 로그를 확인하면 어느 단계에서 문제가 발생했는지 파악할 수 있습니다.
  2. 운영 모니터링 & 시스템 분석
    • 운영 중인 서비스의 성능을 추적하고, 장애 발생 가능성을 미리 감지할 수 있습니다.
    • 로그 데이터는 시스템의 상태를 분석하고 최적화하는 데 중요한 역할을 합니다.
  3. 보안 & 감사(Audit Logging)
    • 사용자 활동 기록(로그인, 결제, 데이터 변경 등)을 남겨 보안 감사(Audit) 및 문제 발생 시 추적할 수 있습니다.
    • 보안 로그는 악의적인 접근을 감지하고, 사이버 공격을 방어하는 데 사용됩니다.

로그를 남기는 방법

  • Java에서는 System.out.println()을 사용하여 로그를 출력할 수 있지만, 이는 성능 저하 및 유지보수 어려움 등의 단점이 있습니다.
  • 대신, Slf4j와 같은 로깅 프레임워크를 활용하면 효율적이고 확장성 있는 로깅 시스템을 구축할 수 있습니다.

Slf4j란?

Slf4j(Simple Logging Facade for Java)는 Java 애플리케이션에서 로깅을 위한 추상화 인터페이스입니다.
쉽게 말해, 특정 로깅 프레임워크(Logback, Log4j 등)에 종속되지 않고 Slf4j를 통해 원하는 로깅 시스템을 선택하여 사용할 수 있도록 도와주는 역할을 합니다.

💡 Slf4j는 로깅을 위한 "추상화 인터페이스"이며, 실제 로깅은 Logback, Log4j와 같은 구현체가 수행합니다.

Slf4j와 Logback 관계

Slf4j 로깅 추상화 인터페이스 (로깅 방식만 제공, 직접 로그 출력 X)
Logback Slf4j의 기본 로깅 구현체 (Spring Boot 기본 제공)
Log4j 별도의 로깅 프레임워크 (Slf4j와 함께 사용할 수 있음)

 


Slf4j를 사용하는 이유

  • 특정 로깅 프레임워크에 종속되지 않음
    1. Slf4j를 사용하면 코드 수정 없이 Logback, Log4j 등 원하는 로깅 프레임워크를 쉽게 교체할 수 있습니다
  • System.out.println()보다 성능 우수
    1. System.out.println()은 단순한 콘솔 출력이지만, Slf4j는 다양한 출력 대상(파일, DB, 원격 서버 등)에 기록할 수 있습니다
    2. System.out.println()은 실행 속도가 느리지만, Slf4j는 비동기 로깅레벨 제어가 가능하여 성능 최적화에 유리합니다.
  • 로그레벨 지원
    1. Slf4j는 로그의 중요도에 따라 TRACE, DEBUG, INFO, WARN, ERROR 등의 로그 레벨을 제공하여 필요한 정보만 기록할 수 있도록 합니다.

로그 레벨설명

로그 레벨 설명
TRACE 가장 상세한 디버깅 로그
DEBUG 개발 시 필요한 디버깅 정보
INFO 운영 시 중요한 정보 로그
WARN 경고 로그 (문제 가능성 존재)
ERROR 심각한 오류 발생 로그

Slf4j 사용법

Lombok에서는 @Slf4j를 지원하여 간편하게 사용할 수 있습니다.
@Slf4j는 AOP(Aspect-Oriented Programming) 기능을 활용하여 자동으로 Logger 객체를 생성해줍니다.

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j // Logger 객체 자동 생성
@RestController
public class LogController {

    @GetMapping("/log")
    public String logExample(@RequestParam String message) {
        log.info("INFO 로그: {}", message);
        log.warn("WARN 로그: {}", message);
        log.error("ERROR 로그: {}", message);
        return "로그 출력 완료!";
    }
}

 

결과

포스트맨으로 요청
인텔리제이에서의 로그 확인

 

Slf4j는 Spring Boot에서 로깅을 더욱 유연하고 효율적으로 관리할 수 있도록 도와주는 필수적인 라이브러리입니다.
로깅은 디버깅, 모니터링, 보안, 성능 분석 등 여러 측면에서 필수적인 요소이므로, 올바르게 활용하는 것이 중요합니다.

이제 프로젝트에서 Slf4j를 적극 활용하여 가독성 높은 로그 관리를 해보세요!

 

 

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 직렬화, 역직렬화 어노테이션 정리입니다.

 

직렬화? 역직렬화?

직렬화(Serialization)

직렬화객체를 JSON, XML, 바이너리 등 데이터 형식으로 변환하는 과정을 의미합니다.

이 과정을 통해 데이터를 네트워크로 전송하거나 데이터 스트림으로 전송하는데에 사용되빈다.

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User("testUser", "password123");
        ObjectMapper objectMapper = new ObjectMapper();

        // 직렬화 (객체 → JSON)
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);
    }
}

// 출력
{
    "username": "testUser",
    "password": "password123"
}

 

역직렬화(Deserialization)

역직렬화JSON, XML, 바이너리 데이터를 객체로 변환하는 과정을 의미합니다.

이를 통해 비즈니스 로직에서 객체를 활용하여 사용할 수 있습니다.

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"username\":\"testUser\",\"password\":\"password123\"}";
        ObjectMapper objectMapper = new ObjectMapper();

        // 역직렬화 (JSON → 객체)
        User user = objectMapper.readValue(json, User.class);
        System.out.println(user.getUsername());
    }
}

// 출력
testUser

 

@JsonIgonre

@JsonIgonre은 JSON 변환 과정에서 특정 필드를 제외할 때 사용됩니다.

import com.fasterxml.jackson.annotation.JsonIgnore;

public class User {
    private String username;

    @JsonIgnore
    private String password; // JSON 변환 시 제외됨

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
    
    public String getUsername() { return username; }
    public String getPassword() { return password; }
}

// 출력
{
    "username": "testUser"
}

password 필드가 포함되지 않는 이유가 @JsonIgnore로 때문입니다.

보안 뿐만 아니라 무한 루프 방지 등의 용도로도 사용할 수 있습니다.

 

@JsonProperty

@JsonProperty는 직렬화 또는 역직렬화할 때 필드의 JSON 키 이름을 변경하는데 사용할 수 있습니다.

특정 조건에서만 JSON 처리를 제어할 수 있기에 더욱 활용도가 높습니다.

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
    private String username;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // 역직렬화만 허용
    private String password;
}

// 출력
{
    "username": "testUser"
}

password는 API 응답에서는 제외되지만, JSON 응답을 받을 때는 사용이 가능합니다.

이렇게 내부적으로 보안을 위한 경우에도 사용할 수 있습니다. 

 

@JsonInclude

@JsonInclude는 특정 조건을 만족하는 필드만 JSON에 포함 할 수 있습니다. 예를 들어 null을 가진 필드를 제외하려면

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    private String username;
    private String email;
}

// 출력: email이 null일 때
{
    "username": "testUser"
}

와 같이 구현할 수 있습니다.

 

@JsonIgnoreType

@JsonIgnoreType는 특정 타입의 모든 필드를 JSON 변환에서 제외할 수 있습니다.

객체 내부에 사용되는 숨겨야하는 필드에 대해서 JSON 변환을 제외합니다.

import com.fasterxml.jackson.annotation.JsonIgnoreType;

@JsonIgnoreType
public class SecretData {
    public String key = "secret";
}

public class User {
    public String username = "testUser";
    public SecretData secretData = new SecretData();
}

// 출력
{
    "username": "testUser"
}

 

결과

password 필드 직렬화하지않음
@JsonProperty 설정으로 역직렬화만 허용

 

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 @Controller, @Service, @Repository의 역할과 특징 정리입니다.

 

도입

Spring 프레임워크는 계층구조로 코드를 나누어 각 계층마다 역할을 분리하였습니다.(객체 지향적 특징)
이와 같은 계층 구조는 코드의 역할 분리를 명확히 하고 유지보수성을 높이는 데 중요한 역할을 합니다.

  • Presentation Layer: 클라이언트의 요청 처리
  • Business Layer: 비즈니스 로직 처리
  • Persistence Layer: 데이터 접근 처리

클라이언트와의 요청 처리는 @Controller, 비즈니스 로직 처리는 @Service, 데이터베이스와의 통신은 @Repository를 통해 코드를 구성합니다. 

 

이번 포스팅에서는 @Controller, @Service, @Repository의 역할과 특징을 살펴보겠습니다.

 

@Controller

@Controller는 Spring MVC에서 컨트롤러 클래스를 정의할 때 사용되는 어노테이션입니다. 일반적으로 뷰 템플릿을 반환하는 방식으로 클라이언트의 요청을 처리합니다. 이 어노테이션이 있는 클래스는 HTTP 요청을 처리하고, Model 객체를 사용하여 데이터를 뷰에 전달합니다. @Controller는 주로 웹 애플리케이션에서 서버 사이드 렌더링을 위한 컨트롤러로 사용됩니다.

@Controller 구조

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. Dispatcher Servlet은 해당 뷰 네임에 해당하는 뷰를 ViewResolver를 통해서 뷰를 반환합니다.
@Controller
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/users")
    public String getUsers(Model model) {
        model.addAttribute("users", userService.findAll());
        return "userList"; // "userList" 뷰 반환
    }
}

userList.html

 

@Controller + @RequestBody

@RequestBody는 HTTP 요청 본문을 자바 객체로 변환하여 매핑해주는 어노테이션입니다. 이 어노테이션을 @Controller와 함께 사용하면, 클라이언트에서 전달한 JSON, XML 등의 데이터를 객체로 받을 수 있습니다. @Controller는 뷰를 반환하는 역할을 계속하고, @RequestBody는 데이터를 뷰에 매핑하는 역할을 합니다.

@Controller + @RequestBody 구조

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환됩니다. 이는 @ResponseBody 어노테이션으로 개발됩니다.
@Controller
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    /**
     * Handles POST requests to create a new user from JSON data and renders the "userDetail" view.
     *
     * @param user the user data from the request body
     * @param model the model to hold attributes for the view
     * @return the name of the view to be rendered
     */
    @PostMapping("/users")
    public String createUser(@RequestBody User user, Model model) {
        User savedUser = userService.save(user);
        model.addAttribute("user", savedUser);
        return "userDetail"; // Return the name of the view
    }
}

성공적인 response

 

@RestController

@RestController는 @Controller와 @ResponseBody를 결합한 어노테이션입니다. @RestController가 적용된 클래스는 RESTful 웹 서비스를 제공하며, 메서드의 반환 값이 바로 HTTP 응답 본문으로 전송됩니다. 따라서 @RestController는 일반적으로 JSON, XML과 같은 데이터를 직접 반환하는 데 사용됩니다. 뷰를 반환하지 않으며, 주로 API 서버에서 사용됩니다.

@RestController

  1. 클라이언트로부터 받은 요청을 통해 Handler 어댑터를 통해 해당 요청을 처리할 수 있는 controller를 찾습니다.
  2. controller는 service와 repository의 비즈니스 로직을 처리한 후 뷰 이름을 handlerAdapter에게 전달합니다.
  3. 반환되는 객체는 Json으로 Serialize되어 사용자에게 반환됩니다. 이는 @RestController 어노테이션으로 개발됩니다.
@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    /**
     * Handles GET requests to retrieve the list of users as JSON.
     *
     * @return a list of users
     */
    @GetMapping("/json-users")
    public List<User> getUsers() {
        return userService.findAll();
    }
}

 

@Service

@Service 어노테이션은 서비스 계층에 사용됩니다. 서비스 계층은 비즈니스 로직을 처리하는 곳으로, 일반적으로 데이터베이스에서 데이터를 조회하고, 가공하여 컨트롤러로 전달하는 역할을 합니다. @Service는 주로 서비스 클래스를 나타내며, @Component의 특성을 그대로 가지고 있어 Spring 컨테이너에 의해 관리됩니다.

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }
}

 

 

@Repository

@Repository는 데이터 접근 계층에 사용됩니다. 이 어노테이션은 주로 데이터베이스와 상호작용하는 클래스를 나타냅니다. @Repository 어노테이션을 사용하면 Spring이 해당 클래스를 자동으로 데이터 접근 객체(DAO)로 인식하고, 예외 변환 기능을 제공합니다. @Repository는 @Service와 동일하게 @Component의 특성을 그대로 가지고 있어 Spring 컨테이너에 의해 관리되며, 데이터베이스와의 상호작용을 담당합니다.

// In-memory Database
@Repository
public class ItemRepository {

    private static final Map<Long, Item> store = new HashMap<>(); //static
    private static long sequence = 0L; //static

    public Item save(Item item) {
        item.setId(++sequence);
        store.put(item.getId(), item);
        return item;
    }
}


// JPA
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findAll();
}

 

 

코드

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁

스프링 정리를 위한 포스팅입니다.
해당 포스팅은 Lombok 어노테이션 입니다.

 

Lombok이란?

Lombok은 Java 애플리케이션에서 보일러플레이트 코드(반복적인 코드)를 줄여주는 라이브러리입니다.

이를 통해 코드의 가독성을 높이고 유지보수를 쉽게 만들어 줍니다.

왜 Lombok을 사용해야 하는가?

  • Java의 기본 클래스는 자주 사용되는 메서드(생성자, getter, setter, toString 등)수동으로 작성해야 합니다.
  • Lombok은 이러한 반복적인 코드 생성을 자동화하여 개발 생산성을 크게 향상시킵니다.

Lombok 어노테이션 종류

@Getter / @Setter

  • 클래스의 모든 필드에 대해 getter와 setter 메서드를 자동으로 생성합니다.
@Getter @Setter
public class Person {
    private String name;
    private int age;
}

@ToString

  • 객체의 필드를 문자열로 변환하는 toString() 메서드를 자동으로 생성합니다.
@ToString 
public class Person { 
    private String name; 
    private int age; 
}

@NoArgsConstructor / @AllArgsConstructor

  • 기본 생성자 및 모든 필드를 초기화하는 생성자를 자동으로 생성합니다.
@NoArgsConstructor
@AllArgsConstructor
public class Animal {
    int age;
    String name;
}

@Builder

  • 빌더 패턴을 적용하여 객체 생성 시 가독성을 높입니다.
@Builder
public class Animal {
    int age;
    String name;
}

 

@Data

  • @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 모두 포함하는 어노테이션입니다. 주로 DTO 객체에 사용됩니다.
@Data
public class Plant {
    private int height;
    private String origin;

    public Plant(int height, String origin) {
        this.height = height;
        this.origin = origin;
    }
}

Lombok 설치방법

spring에서 lombok을 설치하기 위해서는 maven 또는 gradle을 사용하면 됩니다. 저는 gradle을 사용하겠습니다. 

build.gradle에서 compileOnly 'org.projectlombok:lombok'annotationProcessor 'org.projectlombok:lombok'

를 설정한 후에 gradle 프로젝트를 reload 하면 됩니다.

dependencies {
	// ...
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

예제 확인

public class Test {
    public static void main(String[] args) {
        // @Getter, @Setter
        Person person = new Person(1, 3, 4, 5, 8);
        System.out.println(person.getShoes()); // getShoes() automatically generated
        person.setShoes(84); // setShoes() automatically generated
        System.out.println(person.getShoes());

        // @ToString
        System.out.println("person.toString: " + person.toString()); // toString() automatically generated

        // @NoArgsConstructor / @AllArgsConstructor
        Animal tomy = new Animal(20, "tomy");
        Animal aymee = new Animal(); // Default constructor automatically generated
        System.out.println("tomy = " + tomy); // toString() automatically generated
        System.out.println("aymee = " + aymee); // toString() automatically generated

        // @Builder
        Animal hiro = Animal.builder().age(7).name("hiro").build(); // Using builder pattern
        System.out.println("hiro.toString() = " + hiro); // toString() automatically generated

        // @EqualsAndHashCode, @RequiredArgsConstructor
        Plant plant1 = new Plant(2, "korea");
        Plant plant2 = new Plant(2, "korea");

        // @Data
        System.out.println("plant1 equals plant2: " + plant1.equals(plant2)); // true, since fields are the same
        System.out.println("plant1 hashCode: " + plant1.hashCode()); // Same hashCode for equal objects
        System.out.println("plant2 hashCode: " + plant2.hashCode()); // Same hashCode for equal objects

        // @RequiredArgsConstructor automatically generates a constructor with final fields
        Plant plant3 = new Plant(3, "japan"); // Constructor with height and origin fields
        System.out.println("plant3: " + plant3); // toString() automatically generated
    }
}

결과 확인

다음과 같이 모든 어노테이션에 대해 결과를 확인할 수 있습니다.

 

코드

+ Recent posts