기본적인 데이터 전달 방식

1. GET + Query Parameter(=Query String)

일부 서버에서 GET 방식의 데이터 요청은 Body값을 처리하지 않기 때문에 쿼리 파라미터로 전달해야 한다.

response.getWriter().write() : 응답의 Body 값에 접근할 수 있어서 @ResponseBody를 대체할 수 있다.

http://localhost:8080/request-params?key1=value1&key2=value2
@Slf4j
@Controller
public class RequestParamController {

    @GetMapping("/request-params")
    public void params(
    	HttpServletRequest request,
        HttpServletResponse response
    ) throws IOException {
 
        String key1Value = request.getParameter("key1");
        String key2Value = request.getParameter("key2");
        
        log.info("key1Value={}, key2Value={}", key1Value, key2Value);
        response.getWriter().write("success");
    }
}

 

 

 

 

2. POST + HTML Form(x-www-form-urlencoded)

HTTP Request Body에 쿼리 파라미터 형태로 전달하는 방법으로 폼 데이터를 서버에 전송할 때 사용하는 방식이다.

Controller 코드는 GET method와 동일하다.

POST /form-data
content-type: application/x-www-form-urlencoded

key1=value1&key2=value2

 

 

3. POST + HTTP Request Body

데이터(JSON, TEXT, XML 등)를 직접 HTTP Message Body에 담아서 전달한다.

주로 @RestController에서 사용하며, 대부분 JSON 형식으로 데이터를 전달한다.

@Slf4j
@Controller
public class RequestBodyController {

    // JSON을 객체로 변환해주는 Jackson 라이브러리
    private ObjectMapper objectMapper = new ObjectMapper();

    @PostMapping("/request-body")
    public void requestBody(
            HttpServletRequest request,
            HttpServletResponse response
    ) throws IOException {

        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        log.info("messageBody={}", messageBody);

        Board board = objectMapper.readValue(messageBody, Board.class);

        log.info("board.getTitle()={}, board.getContent()={}", board.getTitle(), board.getContent());

        response.getWriter().write("success");

    }
    
}

 

 


 

어노테이션 기반 전달 방식

1. @RequestParam

  • 쿼리 파라미터 or 폼 데이터를 통해 데이터를 전달하는 경우, 요청 파라미터의 각 속성값을 필드에 담아 처리하는 방식이다.
  • @RequestParam : 파라미터 key값과 변수 이름이 동일하면 자동 바인딩
  • @RequestParam("key") 해당 속성값을 변수에 저장 
  • @RequestParam 어노테이션을 생략해도 동작하지만, 요청 파라미터의 값을 바인딩하는 코드임을 이해하기 어렵기 때문에 권장 X
@Slf4j
@Controller
public class RequestParamControllerV2 {

	@ResponseBody
	@GetMapping("/v1/request-param")
	public String requestParamV1 (
					@RequestParam("name") String userName,
					@RequestParam("age") int userAge													
	) {
		// logic
		log.info("name={}", userName);
    log.info("age={}", userAge);
		return "success";
	}

}

 

  • required 속성
    • default = true
    • int 타입에는 null이 올 수 없으므로, 속성값이 false여도 age가 파라미터에서 생략되면 500 error가 발생한다.
    • int가 아닌 Integer를 사용해야 (required=false)가 의도대로 적용된다.
@ResponseBody
@GetMapping("/v4/request-param")
public String requestParam (
                @RequestParam(required = true) String name, // 필수
                @RequestParam(required = false) int age,  // 필수가 아님 -> 에러		
                @RequestParam(required = false) Integer age2 // 필수가 아님		
) {
	// logic
	log.info("name={}", name);
  log.info("age={}", age);
	return "success";
}

 

  • defaultValue 속성
    • 해당 파라미터가 전달되지 않으면 설정한 기본값 적용
    • `http://localhost:8080/v5/request-param?name&age` 처럼 value값만 지정하지 않아도 defaultValue 적용됨
@ResponseBody
@GetMapping("/v5/request-param")
public String requestParam (
    @RequestParam(required = true, defaultValue = "sparta") String name,
    @RequestParam(required = false, defaultValue = "1") int age
) {
    // logic
    log.info("name={}", name);
    log.info("age={}", age);
    return "success"
}

 

  • Map 사용
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam Map<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}

 

  • MultiValueMap 사용
    • key=[value1, value2]
    • 잘 사용하진 않음
    • ex : `http://localhost:8080/v6/request-param?name=sparta&name=wonuk&name=tutor&age=100`
@ResponseBody
@GetMapping("/v6/request-param")
public String requestParamV6(
        @RequestParam MultiValueMap<String, String> map
) {
    // logic
    log.info("name={}", map.get("name"));
    log.info("age={}", map.get("age"));

    return "success";
}

 

 

2. @ModelAttribute

  • 쿼리 파라미터 or 폼 데이터를 통해 데이터를 전달하는 경우, 전달된 데이터를 객체로 처리할 때 사용하는 방식이다.
  • 자동으로 해당 객체를 생성한다.
  • 요청 파라미터의 key값으로 객체 필드의 setter를 호출해서 바인딩한다. (필드 이름과 파라미터 이름이 같아야 함)
  • 요청 파라미터 속성값의 데이터 타입과 필드의 타입이 일치하지 않으면 BindException 발생한다.
  • @ModelAttribute 어노테이션을 생략 가능하지만 @RequestParam과 마찬가지로 권장 X
@ResponseBody
@PostMapping("/v2/tutor")
public String modelAttributeV2(
            @ModelAttribute Tutor tutor													
    ) {
    String name = tutor.getName();
    int age = tutor.getAge();
    
    return "tutor name = " + name + " age = " + age;
}

 

 

만약 @RequestParam과 @MoelAttribute 둘 다 생략한 경우,
파라미터가 String, int, Integer와 같은 기본 타입은 @RequestParam과 바인딩하고,
파라미터가 클래스(객체)인 경우는 @ModelAttribute와 바인딩한다.

 

 

3. POST + @RequestBody, @ResponseBody

  • Body에서 데이터를 전달할 때 사용
  • HttpMessageConverter가 Body 데이터를 String이나 Object로 매핑해서 동작
  • @RequestBody : 요청 메세지 Body 데이터를 조회
  • @RequestHeader : 요청 메세지 헤더 정보 조회
  • @ResponseBody : 응답 메세지 Body에 값을 담아 전달
  • @RequestBody를 생략하면 @ModelAttribute로 인식하므로 생략 불가
// text 타입 데이터 전달 시 코드

@Controller // @RestController = @Controller + @ResponseBody
public class RequestBodyStringController {
	
  @ResponseBody
  @PostMapping("/v5/request-body-text")
  public String requestBodyTextV5(
          @RequestBody String body,  
          @RequestHeader HttpHeaders headers
  ) {					
      // HttpMessageConverter가 동작해서 아래 코드가 동작하게됨
      String bodyMessage = body;

      return "request header = " + headers + " response body = " + bodyMessage;
  }
}
// JSON 타입 데이터 전달 시 사용

@RestController
public class JsonController {
	
	@PostMapping("/v3/request-body-json")
	public String requestBodyJsonV3(@RequestBody Tutor tutor) {
		
		Tutor requestBodyTutor = tutor;

		return "tutor = " + requestBodyTutor;
	}
}

 

 


HttpMessageConverter 역할

@RequestBody 동작 시, 

  1. 요청한 데이터와 요청 헤더의 Content-Type 을 참고해서 어떤 Converter를 적용할지 결정
    • ex. Content-Type: application/json -> MappingJackson2HttpMessageConverter
  2. Converter가 정해진 타입으로 데이터를 변환
    • MappingJackson2HttpMessageConverter : Object로 변환
    • HttpMessageConverter : String으로 변환

 

@ResponseBody 동작 시, 

  1. 응답할 데이터와 요청 헤더의 Accept 를 참고해서 어떤 Converter를 적용할지 결정
    • ex. Accept: application/json -> MappingJackson2HttpMessageConverter
  2. Converter가 정해진 타입으로 데이터를 변환

 

Client에서 Server로 Data를 전달하는 방법 정리

1. GET

쿼리 파라미터(쿼리 스트링)으로 데이터 전달

@RequestParam, @ModelAttribute

 

2. POST - HTML Form

@RequestParam, @ModelAttribute

 

3. POST - HTTP Request Body

@RequestBody

 

 

'언어, 프레임워크 > Spring' 카테고리의 다른 글

Spring Boot의 예외처리 (유효성 검사)  (0) 2025.03.20
응답(Response) 데이터 전달 방식  (0) 2025.03.20
Spring Annotation (+Request Mapping)  (0) 2025.03.19
Spring MVC  (0) 2025.03.19
WAS / Servlet / SSR, CSR  (0) 2025.03.19

@Slf4j

  • 로그를 남기기 위한 Logger 객체를 자동 생성하는 어노테이션
  • Slf4j는 인터페이스이고, 구현체로 Logback같은 라이브러리 선택
  • 실제 개발에서는 Spring Boot가 기본 제공하는 Logback을 대부분 사용

Logging : 스레드 정보, 클래스명 같은 부가 정보를 함께 확인 가능하다

Log Level : TRACE > DEBUG > INFO > WARN > ERROR 

설정한 레벨 이하의 로그만 출력

log.info("문자 info={}", sparta);  // 문자 info=sparta
// 중괄호가 sparta로 치환되어 로그 출력
// 이 형식의 로그 출력 권장

log.info("문자 info " + sparta);  // 문자 info sparta
// 해당 레벨의 로그를 출력하지 않는 상황에서도 문자열의 '+' 연산은 수행됨
// -> 불필요한 작업 발생

 

 

@Indexed

  • 해당 클래스가 컴포넌트 스캔 대상으로 Spring Bean에 더 빠르게 등록되도록 돕는 어노테이션

 

@Target

  • 어노테이션이 적용될 수 있는 대상(위치)을 지정하는 메타 어노테이션
  • 특정 어노테이션을 특정 위치에서만 사용하도록 제한할 때 @Target 사용 -> 사용자 지정 어노테이션에 주로 사용
  • ElementType Enum의 속성을 인자로 둠

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)  // 클래스, 인터페이스, enum에만 적용 가능
public @interface ClassOnlyAnnotation {
}


@ClassOnlyAnnotation
public class MyClass { }

// ❌ 오류: 필드에는 적용 불가능
@ClassOnlyAnnotation
private String name;

 

 

@Retention

  • 얼마나 오래 유지되는지를 결정하는 메타 어노테이션
  • RetentionPolicy Enum의 속성을 인자로 둠

CLASS는 주석과 같은 기능

 

@Documented

  • Javadoc 등의 문서화 도구에 의해 문서화되어야 함을 나타내는 메타 어노테이션

 


 

@PathVariable

  • @PathVariable로 설정된 경로 변수(`user/{id}`)는 반드시 값을 가져야 하며, 값이 없으면 응답 상태 코드 404가 발생
@RequestMapping("/posts")
@RestController
public class PathVariableController {
	
	// postId로 된 post 단건 조회
	@GetMapping("/{postId}")
	public String pathVariableV1(@PathVariable("postId") Long data) {
		// logic
		String result = "PathvariableV1 결과입니다 : " + data;
		return result;
	}
}
@RequestMapping("/posts")
@RestController
public class PathVariableController {
	
	// 변수명과 같다면 @PathVariable의 매개변수 생략가능
	@GetMapping("/{postId}")
	public String pathVariableV2(@PathVariable Long postId) {
		// logic
		String result = "PathvariableV2 결과입니다 : " + postId;
		return result;
	}
	
}
@RestController
public class PathVariableController {

    @GetMapping("/{postId}/comments/{commentId}")
    public String pathVariableV3(
    			@PathVariable Long postId,
        		@PathVariable Long commentId) {
    // logic
    String result = "PathvariableV3 결과입니다 postId : " + postId + "commentsId : " + commentId;
    return result;
    }
}

 


 

특정 파라미터 매핑

package com.example.springbasicannotation.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ParameterController {

    // parms 속성값 추가
    @GetMapping(value = "/users", params = "gender=man")
    public String params() {
        // logic
        String result = "params API가 호출 되었습니다.";
        return result;
    }

}

http://localhost:8080/users?gender=man <- 쿼리 스트링이 params대로 작성되어야 호출 가능

 

params에 저장된 값의 형태에 따라 호출 가능한 상황이 달라진다.

  • 속성 작성 규칙
    1. params = "gender"
      • params의 key값은 커스텀이 가능하다
      • value는 없어도 된다.
    2. params = "!gender"
      • gender 파라미터가 없어야 한다.
    3. params = "gender=man"
      • gender=man 이어야 한다.
    4. params = "gender!=man"
      • params의 value값이 man가 아니여야 한다.
    5. params = {"gender=man", "gender=woman"}
      • 배열로 속성 값을 여러 개 설정이 가능하다.

 

특정 헤더 매핑

  • MediaType Enum의 속성값 활용 가능
    • `produces = “application.json"`→ `produces = MediaType.APPLICATION_JSON_VALUE`
  • headers = "Content-Type=application/json"과 consumes = "application/json"는 표현 방식의 차이
    • 클라이언트 요청의 헤더 정보가 특정 조건인 경우에만 서버가 요청을 처리하고 응답
    • consume(수용)
  • produces = "text/plain"
    • 서버가 클라이언트에 보내는 데이터의 형식을 지정
    • produce(제공)
@RestController
public class ParameterController {
	
	// headers 속성값 추가
  @PostMapping(value = "/users", headers = "Content-Type=application/json")
  public String headers() {
      // logic
      String result = "headers API가 호출 되었습니다.";
      return result;
  }
	
}
@RestController
public class ParameterController {
	
	// consumes 속성값 추가
  @PostMapping(value = "/users", consumes = "application/json") // MediaType.APPLICATION_JSON_VALUE
  public String consumes() {
      // logic
      String result = "consumes API가 호출 되었습니다.";
      return result;
  }
	
}
@RestController
public class ParameterController {
	
	// produces 속성값 추가
  @GetMapping(value = "/users", produces = "text/plain")
  public String produces() {
      // logic
      String result = "text/plain 데이터 응답";
      return result;
  }
	
}

 

더보기

MediaType Enum의 상수값 정리

_VALUE를 붙여서 사용해줘야 객체가 아닌 문자열로 사용 가능하다

'언어, 프레임워크 > Spring' 카테고리의 다른 글

응답(Response) 데이터 전달 방식  (0) 2025.03.20
요청(Request) 데이터 전달 방식  (1) 2025.03.20
Spring MVC  (0) 2025.03.19
WAS / Servlet / SSR, CSR  (0) 2025.03.19
스프링 기초 개념  (9) 2025.03.18

Spring MVC 사용 배경

  1. Servlet 사용
    • 비지니스 로직을 처리하는 코드와 화면을 그리는 View 코드가 함께 존재하는 문제
  2. JSP 사용
    • View 에 해당하는 코드를 분리하였지만, 여전히 비지니스 로직을 JSP에 포함하는 문제
  3. MVC 패턴 사용
    • 공통 로직을 처리하는것에 코드가 중복되는 문제
  4. 프론트 컨트롤러 패턴 사용
    • 공통 로직을 하나의 입구에서 처리하기 위해서 프론트 컨트롤러 패턴 적용
    • 각각의 핸들러 호출 후 응답을 프론트 컨트롤러에 맞게 변형시켜야 하는 문제
  5. Spring MVC 사용
    • 프론트 컨트롤러 패턴, 어댑터 패턴이 모두 적용된 현재
    • 우리가 사용하는 Spring을 이용한 Web Application 개발 방식에 사용됨
    • Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용되어 있다.

 

MVC 프레임워크는 model, view, controller 아키텍처를 제공한다.

  • Model : POJO(애플리케이션 데이터) 관리
  • View : 데이터를 렌더링해서 output 생성 (JSP, HTML, XML, Velocity template 등)
  • Controller : 요청을 처리해서 결과를 Model에 저장

어댑터 적용된 버전


동작 순서 (tomcat 위에서 동작)

  1. Dispatcher Servlet이 Handler Mapping에 클라이언트로부터 전달받은 URL에 적합한 Controller를 요청 -> @RequestMapping으로 개발자가 URL-Controller 매핑 정보를 관리
  2. 해당 Controller로 필요한 작업을 요청하면 Controller가 Service 레이어 호출해서 DAO 객체를 활용
  3. 작업으로 반환된 데이터를 Model에 저장
  4. Model에 저장한 데이터 객체와, view 이름을 함께 Dispatcher Servlet에 전달
  5. Dispatcher Servlet이 View Resolver 객체에게 view 이름을 전달하고, 해당하는 view object를 받아옴
  6. 해당 View에게 데이터 객체를 보내서 output(ex.HTML)을 받아옴

계층 구조

 

  • 필요에 따라 여러 개의 Dispatcher Servlet을 생성해서 관리할 수 있다
  • MVC의 경우, Dispatcher Servlet이 스프링 컨테이너 생성한다
  • 생성된 스프링 컨테이너들의 공유 자원은 또다른 스프링 컨테이너에서 관리하고, 이는 ContextLoaderListener가 생성하고 관리한다
  • DispatcherServlet의 컨텍스트(컨테이너의 구현체)는 루트 컨텍스트를 상속하기 때문에 루트 컨텍스트에 등록된 빈(공유 자원)에 접근할 수 있다
  • 루트 컨텍스트에서는 자식 컨텍스트에 접근할 수 없다

Layer 정리

이미지1

 

이미지2

1) 프레젠테이션 레이어 (Presentation Layer)

  • DispatcherServlet
    • 프론트 컨트롤러 역할
    • 핸들러 매핑을 이용해서 적절한 컨트롤러 탐색
  • Controller (@Controller, @RestController)
    • @RequestMapping, @GetMapping, @PostMapping 등의 어노테이션을 통해 요청을 매핑
    • Model 객체를 이용해 데이터를 view로 전달하거나 JSON 형식으로 변환
  • viewResolver
    • view 이름을 실제 view(화면)으로 변환

2) 비즈니스 레이어 (Business Layer)

  • Service (@Service)
    • 지즈니스 로직 처리
    • 여러 컨트롤러에서 공통적으로 사용하는 로직을 모아두는 역할
    • 레포지토리 계층과 상호작용하며 데이터를 가공 후 컨트롤러로 전달

3) 데이터 액세스 레이어 (Data Access Layer)

  • Repository (DAO, @Repository)
    • 데이터베이스와 직접적인 상호작용
    • JPA, MyBatis 등의 ORM 프레임워크와 함께 사용
    • CRUD 역할 수행

Model

model에 저장되는 데이터 형식

 

Spring MVC에서 모델을 전달하는 방식

컨트롤러에 모델을 인자로 설정하면, 스프링이 자동으로 모델 객체를 생성해서 전달해준다.

 

1. java.util.Map 구현체 

  • 키-값 쌍으로 데이터를 전달
  • 데이터가 간단하고, 동적으로 변할 때 유용
  • 항상 데이터의 이름을 지정해줘야 한다
@Controller
public class MyController {
    @RequestMapping("/show")
    public String showData(Map<String, Object> model) {
        model.put("message", "Hello, Spring!");  // key, value
        return "viewName";  // 반환되는 뷰 이름
    }
}

 

2. Model 인터페이스 (스프링 제공)

  • addAttribute 메서드를 통해 데이터를 모델에 추가
  • Map과 동일한 기능을 하지만, 자동으로 데이터의 이름을 지정해주기 때문에 key 생략 가능
@Controller
public class MyController {
    @RequestMapping("/show")
    public String showData(Model model) {
        model.addAttribute("message", "Hello, Spring!");
        model.addAttribute("Hello!"); // key(name) 생략 가능
        return "viewName";  // 반환되는 뷰 이름
    }
}

 

3. ModelMap 객체

  • Map과 유사하게 키-값으로 데이터를 전달
  • addAttribute 메서드를 통해 데이터를 모델에 추가
  • chained call(여러 데이터를 연결해서 모델에 추가)이 가능하다
@Controller
public class MyController {
    @RequestMapping("/show")
    public String showData(ModelMap model) {
        model.addAttribute("message1", "Hello, Spring!")
        	 .addAttribute("message2", "Hello!");
        return "viewName";  // 반환되는 뷰 이름
    }
}

 

 


Controller

@Component를 사용하면 빈 등록이 가능했다.

이를 구체적으로 명시해서 @Controller, @Service, @Repository로 사용하면 스프링에게 각 빈의 역할 정보를 줄 수 있다.

@Controller
public class MyController {
    @RequestMapping("/show") //show URL 요청이 들어오면 실행할 Controller(showData 메서드) 매핑
    public String showData(ModelMap model) {
        model.addAttribute("message1", "Hello, Spring!")
        	 .addAttribute("message2", "Hello!");
        return "viewName";  // 반환되는 뷰 이름
    }
}

 

컨트롤러 매핑은 클래스 매핑, 메서드 매핑 모두 가능하다.

@Controller
public class EmployeeController 
{
    @RequestMapping("/employee-management/employees") // 메서드 매핑
    public String getAllEmployees(Model model)
    {
        //application code
        return "employeesList";
    }
    
    @RequestMapping("/employee-management/employees/add")//employee-management/employees 경로 중복 
    public String addEmployee(EmployeeVO employee)
    {
        //application code
        return "employeesDetail";
    }
}
@Controller
@RequestMapping("/appointments") // 중복되는 경로는 클래스 매핑 -> 가독성 향상
public class AppointmentsController {

    // an HTTP POST for /appointments
    @RequestMapping(method = RequestMethod.POST)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }
     
    // an HTTP GET for /appointments/new
    @RequestMapping(value="/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }
}

 

/login/... URL이 요청됐을 때, 매핑에 의해 doLogin 메서드가 실행된다.

인자로 @RequestParam을 사용하면 URL의 쿼리 스트링에서 각 파라미터의 value값이 인자로 전달된다.

@RequestMapping(value = "/login", method = RequestMethod.GET)

public String doLogin(@RequestParam String username, 
                             @RequestParam String password) { // scott, tiger 전달
 	...
	return success;
}
☝️
http://localhost:8080/spring/login?username=scott&password=tiger

 

@ResponseBody 선언 시, ViewResolver를 거치지 않고 HTTP 응답 본문에 데이터를 직접 반환 (view 반환 X)

-> DispatcherServlet이 반환된 값을 받으면, 이를 HttpMessageConverter를 통해 JSON, XML 등의 형식으로 변환

@Controller
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/message")
    @ResponseBody
    public String getMessage() {
        return "Hello, Spring MVC!";
    }
}
@RestController  // @Controller + @ResponseBody
@RequestMapping("/api")
public class UserApiController {

    @GetMapping("/users")
    public List<String> getUsers() {
        return Arrays.asList("User1", "User2", "User3");
        // ["User1", "User2", "User3"] <- JSON 형식으로 반환
    }
}

 


 

View

'언어, 프레임워크 > Spring' 카테고리의 다른 글

요청(Request) 데이터 전달 방식  (1) 2025.03.20
Spring Annotation (+Request Mapping)  (0) 2025.03.19
WAS / Servlet / SSR, CSR  (0) 2025.03.19
스프링 기초 개념  (9) 2025.03.18
HTTP, RESTful API 알아보기  (0) 2025.03.17

빌드 자동화 도구

빌드, 라이브러리 관리, 테스트, 배포 등을 자동화하여 수행

 

Gradle

  • Java와 유사한 문법 구조를 가진 Groovy 기반의 스크립트 언어 사용
  • 다양한 소프트웨어를 빌드할 수 있는 유연한 빌드 자동화 도구
  • 특징
    1. 유연성
      • 복잡한 빌드 시나리오 처리
      • 빌드 스크립트를 통해 다양한 빌드 작업 정의, 필요한 경우 커스터마이징도 가능
    2. 성능
      • Build Cache
        • 빌드 결과물을 캐싱하여 재사용
        • 라이브러리 의존성을 캐싱하여 재사용
      • 점진적 빌드
        • 마지막 빌드 호출 이후 변경된 부분만 빌드
        • 변경되지 않은 부분은 캐시 결과를 검색해 재사용
      • 데몬 프로세스
        • 다음 빌드 작업을 위해 백그라운드에서 대기하는 프로세스
        • 초기 빌드 이후부터는 빌드 실행 시 초기화 작업 거치지 않음
    3. 멀티 프로젝트 빌드 지원
      • 공통으로 사용하는 클래스를 모듈로 만들어 독립적인 각 프로젝트에서 사용 가능
    4. 설정 주입 방식
      • 필요한 설정을 직접 프로젝트에 주입하는 방식
      • 공통되는 정보는 묶어서 한 번에 주입 가능
      • 프로젝트별로 설정을 다르게 주입 가능
더보기

빌드 스크립트 build.gradle

 

플러그인(plugins) : 특정 작업을 위해 모아 놓은 task들의 모음집

 

의존성 관리(dependencies) : 프로젝트에서 사용하는 라이브러리, 패키지 관리

 

의존성 설정 : 라이브러리를 추가하는 시점 설정

  • Implementation
    • 컴파일, 런타임 시점 모두에서 사용한다.
  • compileOnly
    • 컴파일할 때만 사용되고 런타임 때에는 사용하지 않는다.
  • runtimeOnly
    • 런타임 때만 사용한다.
  • testImplementation
    • 테스트할 때만 사용한다.

레포지토리(repositories) : 라이브러리가 저장된 위치 정의, 저장소에서 라이브러리를 가져옴

 


 

Maven

  • XML 기반의 빌드 설정 사용
  • 설정 파일을 통해 프로젝트 빌드 및 의존성 관리
  • 특징
    1. 표준화된 빌드 프로세스 
      • 프로젝트마다 일관된 방식으로 빌드 수행
      • 디렉터리 구조가 정해져 있음
    2. 의존성 관리
      • 의존성 관리를 자동화하여, 프로젝트에 필요한 라이브러리를 자동으로 다운로드하고 관리
      • 중앙 저장소를 통해 필요한 라이브러리를 쉽게 가져옴
    3. 빌드 라이프사이클 관리
      • 빌드 라이프사이클을 정의하여, 빌드 단계마다 어떤 작업이 수행되어야 할지를 자동으로 처리
      • clean(빌드 전, 이전의 빌드 결과물 삭제) -> validate(프로젝트 유효성 검사) -> compile -> test -> package -> install(로컬 저장소에 빌드된 파일 설치) -> deploy(원격 저장소에 배포)
    4. 플러그인 기반 확장성
      • 다양한 플러그인으로 기능 확장
      • `maven-compiler-plugin`은 컴파일 처리
      • `maven-surefire-plugin`은 테스트 처리
더보기

빌드 스크립트 pom.xml (Project Object Model)

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>my-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.8</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <repositories>
    	<repository>
        	<id>central</id>
        	<url>https://repo.maven.apache.org/maven2</url>
    	</repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

 

레포지토리 (Repository) : 라이브러리 의존성 관리

 

  • 중앙 리포지토리 (Central Repository): Maven의 기본 저장소
  • 로컬 리포지토리 (Local Repository): 사용자의 로컬 머신에 저장되는 리포지토리. Maven이 다운로드한 라이브러리 저장
  • 원격 리포지토리 (Remote Repository): 사설 리포지토리나 다른 중앙 저장소 지정 가능

 

 


 

Web Server

  • HTTP를 기반으로 동작하며 정적 리소스(HTML, CSS, JS, 이미지 등)를 제공
  • ex. NGINX, Apache

 

WAS (Web Application Server)

  • HTTP를 기반으로 동작하며 Application 로직을 수행하고, DB와 상호작용하여 동적 컨텐츠를 생성
  • 자바에서는 Servlet Container 기능 수행
  • 멀티 스레드 지원 (공유 변수 사용 유의)
  • ex. Apache Tomcat(Spring Boot에 내장), Jetty, Undertow


 

Servlet

  • HTTP 프로토콜 기반 요청 및 응답을 처리하는데 사용 -> 개발자가 더 쉽게 요청, 응답을 다룰 수 있게 됨
  • 자바에서 Servlet은 HttpServlet 클래스를 상속받아 구현되며, 웹앱 개발의 핵심 기술

 

Servlet 동작 순서

  1. WAS는 HTTP 요청 메세지를 기반으로 새로운 Request, Response 객체 생성
  2. WAS는 만들어진 Request, Response 객체를 서블릿 컨테이너에 넘겨주며 ExampleServlet 객체 호출
  3. ExampleServlet에서 비즈니스 로직 처리
  4. Response 객체에 응답 정보 저장
  5. WAS는 Response 객체로 HTTP 응답 메세지 생성

-> 개발자는 Request 객체에 담긴 HTTP 요청 정보로 비즈니스 로직 수행, Response 객체에 HTTP 응답 정보 입력

 

 

Servlet Container

  • 서블릿을 초기화, 생성, 관리, 호출, 종료하는 역할
  • 서블릿 객체를 싱글톤으로 관리
  • 동시 요청에 대한 처리를 위해 Multi Thread 지원

 

Thread

단일 스레드

  • 여러 개의 요청이 있을 때, (대기 -> 작업 수행 -> 스레드 반환) 작업을 반복
  • 요청의 작업이 지연되거나 error가 발생하는 경우, 이후의 요청이 모두 Time out

 

멀티 스레드 - 요청마다 새로운 스레드 생성

  • 작업 완료 시 스레드 종료
  • 장점
    • 동시 요청 처리 가능
    • 하나의 스레드에 문제 발생 시, 다른 스레드는 정상 동작
  • 단점
    • 스레드 생성에 제한이 없어 수많은 동시 요청 발생시 리소스(메모리, CPU 등) 부족으로 서버 다운 가능성
    • 스레드 생성 비용이 높다
    • context switching 비용 발생 (CPU가 처리할 스레드를 교체하는 것)

멀티 스레드 - Thread Pool

  • 이미 생성된 여러 개의 스레드가 thread pool에 존재
  • 요청이 들어오면 thread pool에서 스레드를 받아 사용
  • 완료 시 스레드를 thread pool에 반납
  • thread pool의 모든 스레드가 모두 사용 중이라면, 대기하거나 요청을 거절
  • 장점
    • 스레드가 미리 생성되어 있어, 생성과 종료의 비용이 절약되고 응답이 빠르다
    • 생성 가능한 스레드의 최대치가 정해져 있어서 많은 요청이 들어와도 안전하게 처리 가능
  • 단점
    • 최대 스레드 수를 낮게 설정하면 응답 지연
    • 최대 스레드 수가 너무 높으면 리소스 부족으로 서버 다운

 


SSR (Server Side Rendering)

  • 서버에서 동적으로 HTML을 만들어 클라이언트에게 제공하는 기술
  • ex. JSP, Thymeleaf
  • 장점
    • 초기 페이지 로드 시 완전히 렌더링된 HTML을 반환하므로, 첫 페이지 로딩이 빠르다
    • 검색 엔진 크롤러가 완전한 HTML을 즉시 수집할 수 있어 SEO에 유리
      • SEO(Search Engine Optimization) : 검색 엔진에서 상위에 노출될 수 있도록 최적화하는 과정
  • 단점
    • 모든 요청에 대해 서버가 페이지를 렌더링하면, 높은 트래픽 상황에서 서버 부하 크게 증가
    • 초기 페이지를 제외한 페이지들은 렌더링 후 반환되므로 속도가 느리다

 

CSR (Client Side Rendering)

  • 자바스크립트를 사용해 동적으로 HTML을 생성해서 적용하는 기술
  • 웹을 모바일 앱처럼 부분마다 변경할 수 있게 함
  • ex. React, Vue
  • 동작 흐름

  1. 비어있는 HTML(기본 뼈대) 요청
  2. 서버로부터 HTML, JS 번들 응답
  3. JS를 실행하면서, 내부적으로 필요한 데이터를 API 요청 -> 가져온 데이터로 인해 완성된 화면 렌더링
  4. 페이지 이동을 할 때마다 서버 요청을 보내는 대신, JS가 필요한 데이터만 가져와 화면을 갱신

 

  •  장점
    • 클라이언트 측에서 렌더링하므로 사용자 인터랙션에 빠른 반응
    • 초기 로딩 후에는 서버와의 통신없이 빠르게 페이지 간 전환 가능
    • ex. 구글맵, sns
  • 단점
    • 초기 로딩 시간이 길다
    • 검색 엔진 크롤러가 자바스크립트를 제대로 실행하지 못하면 SEO에 불리

'언어, 프레임워크 > Spring' 카테고리의 다른 글

요청(Request) 데이터 전달 방식  (1) 2025.03.20
Spring Annotation (+Request Mapping)  (0) 2025.03.19
Spring MVC  (0) 2025.03.19
스프링 기초 개념  (9) 2025.03.18
HTTP, RESTful API 알아보기  (0) 2025.03.17

Spring Framework

  • 엔터프라이즈 애플리케이션 개발에 주로 사용
  • 모듈화되어 있어 필요에 따라 특정 기능만 선택적으로 사용 가능
  • 객체 지향 언어의 특징을 살려낸 프레임워크 (캡슐화, 상속, 추상화, 다형성)

 

Spring Boot

  • 스프링 프레임워크를 기반으로, 간편하고 신속하게 애플리케이션을 개발할 수 있도록 돕는 도구
  • 자동 구성(Auto-configuration) 기능을 제공하여, 초기 설정 작업 자동화
  • 내장 WAS(Tomcat)을 제공하여, 애플리케이션을 별도의 서버 설정 없이 바로 실행 가능
  • `spring-boot-starter-web`를 빌드 관리 도구에 추가하면 웹 애플리케이션에 필요한 모든 종속성과 설정이 자동으로 구성

스프링 특징

1. POJO (Plain Old Java Object, =bean) 사용

  • 인터페이스를 구현하거나 특정 클래스를 상속할 필요가 없는 아주 간단한 자바 클래스
  • bean으로서 스프링 컨테이너에 의해 관리

2. DI (Dependency Injection, 의존성 주입)

  • `@Autowired` 은 스프링이 자동으로 해당 클래스에 다른 객체를 주입(연결)하기 위해 사용
  • 단위 테스트를 진행할 때 Mocked Service를 주입하도록 변경해서 테스트하기 용이
  • 생성자에 주입하는 경우에는 생성자가 하나라면 어노테이션 생략 가능하지만, 생성자가 2개 이상이라면 스프링이 어떤 생성자에 객체를 주입할지 지정할 수 있도록 어노테이션 필수
  • 생성자에 객체를 주입하지 않고, 필드 주입이나 세터 주입도 가능은 함(권장 X)
더보기

생성자 주입✅

final 키워드 사용 가능 -> 안전한 코드 작성 가능

@Component
public class PersonService {
    private final Person person;

    @Autowired  // 생성자가 하나면 생략 가능 (Spring 4.3 이상)
    public PersonService(Person person) {  
        this.person = person;
    }
}

 

필드 주입⚠️

final 키워드 사용 불가 -> 불변성 보장 X

주입된 객체에 접근시 NullPointerException 발생 가능

@Component
public class PersonService {
    @Autowired  // 직접 필드에 주입
    private Person person;
}

 

세터 주입

final 키워드 사용 불가 -> 불변성 보장 X

세터가 실행되지 않으면 주입된 객체는 null값을 가짐

@Component
public class PersonService {
    private Person person;

    @Autowired
    public void setPerson(Person person) {
        this.person = person;
    }
}

3. AOP (Aspect-Oriented Programming, 관점 지향 프로그래밍)

  • 핵심 로직과 부가 기능 분리
  • 여러 곳에서 반복되는 부가 기능을 하나의 애스펙트로 처리함으로써 중복 코드를 제거할 수 있음
  • `@Aspect`
    • 핵심 로직에 부가적인 기능 추가
    • ex. 로깅, 트랜잭션 관리, 보안 등을 분리해서 관리
  •  Join Point
    • Aspect가 적용될 수 있는 코드의 지점 (메서드 호출, 예외 발생, 객체 생성 지점 등)
  • Advice
    • 실제 실행할 코드
    • `@Before` : 조인 포인트에서 메서드 실행 전에 실행
    • `@After` : 조인 포인트에서 메서드 실행 후에 실행
    • `@Around` : 메서드 실행 전후로 원하는 대로 제어 가능
더보기
public class UserService {
    public void addUser(User user) {
        // Add the user to the database
     }
}
@Aspect
public class LoggingAspect {
    @Before("execution(* kr.ac.UserService.addUser(..)) && args(user)")
    public void logBefore(JoinPoint joinPoint, User user) {
        System.out.println("Adding user: " + user.getUsername());
    }
}

 

@Before() 내부의 코드가 실행되기 전에, 아래 logBefore 메서드 실행

 

4. PSA (Portable Service Abstraction)

  • 서비스의 인터페이스를 추상화해서 각 서비스의 변경이 발생해도 Application Code는 수정할 필요없음

 


객체 생성 방법

1. XML 형식 객체 생성

주로 싱글톤으로 관리되며, 이 경우에는 id 값을 지정하지 않아도 된다.

// XML 설정 방식
<bean id="person"
	class="kr.ac.hansung.Person"
    scope="singleton"> 
    <constructor-arg ref="developer" />  
    <constructor-arg value="John Doe" />  
    <constructor-arg value="30" />		
    <property name="myName" value="John Re" />  
</bean>
  • scope
    • singleton : 단 하나의 객체를 공유 (default)
    • prototype : 해당 빈이 주입될 때마다 새로운 객체 생성
  • <constructor-arg> : 생성자에 주입할 값
  • <property> : 세터에 주입할 값
  • value : String 값
  • ref : 다른 객체(bean)의 id 값

 

2. 어노테이션으로 객체 생성

@Component
public class PersonService {
    private Person person;

    @Autowired
    public PersonService(Person person) {
        this.person = person;
    }

    public void printPerson() {
        System.out.println("Name: " + person.getName() + 
		", Age: " + person.getAge());
    }
}
  • @Component : 빈(객체) 생성
  • @Autowired : 다른 객체를 주입할 생성자 명시
만약 new 키워드로 객체를 생성할 경우, 해당 객체는 스프링 컨테이너를 통해 관리할 수 없다.

 


스프링 컨테이너 생성 방법

1. XML 설정을 사용하는 경우

1.1 XML 설정 파일 (applicationContext.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean id="person" class="com.example.Person">
        <constructor-arg value="John Doe" />
        <constructor-arg value="30" />
    </bean>

    <bean id="personService" class="com.example.PersonService">
        <constructor-arg ref="person" />
    </bean>
</beans>

 

1.2 애플리케이션에서 컨테이너 생성 (Main.java)

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        // XML 설정 파일을 사용하여 컨테이너를 생성
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 빈(bean) 가져오기
        PersonService personService = (PersonService) context.getBean("personService");

        // 빈 사용
        personService.printPerson();
    }
}

 

ApplicationContext 인터페이스의 ClassPathXmlApplicationContext를 사용하여 XML 설정 파일(applicationContext.xml)을 읽고, 해당 설정에 정의된 빈들을 스프링 컨테이너에 등록

 

 

2. 자바 기반 설정을 사용하는 경우

2.1 자바 설정 클래스 (AppConfig.java)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example") // 해당 패키지 내 @Component가 붙은 클래스 스캔하여 빈 등록
public class AppConfig {

    @Bean // 메서드 단위로 빈 등록 가능
    public Person person() {
        return new Person("John Doe", 30);
    }

    @Bean
    public PersonService personService() {
        return new PersonService(person());
    }
}

 

2.2 애플리케이션에서 컨테이너 생성 (Main.java)

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 자바 설정 클래스를 사용하여 컨테이너를 생성
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 빈(bean) 가져오기
        PersonService personService = context.getBean(PersonService.class);

        // 빈 사용
        personService.printPerson();
    }
}

 

ApplicationContext 인터페이스의 AnnotationConfigApplicationContext를 사용하여 자바 설정 클래스(AppConfig.class)를 읽고, 그 안에 정의된 빈들을 스프링 컨테이너에 등록

 


Lombok

  • 보일러 플레이트 코드(getter/setter, 생성자 등 반복적으로 작성되는 코드)를 컴파일 시점에 자동으로 생성하여 코드의 가독성과 유지보수성 향상
  • `@getter`, `@setter`
  • `@ToString`
  • `@EqualsAndHashCode` : equals(), hashCode() 메서드를 자동 생성
  • `@NoArgsConstructor`, `@AllArgsConstructor`, `@RequireArgsConstructor`
    • `@NoArgsConstructor` : 기본 생성자 생성
    • `@AllArgsConstructor` : 모든 필드를 매개변수로 하는 생성자 생성
    • `@RequireArgsConstructor` : final 필드만 매개변수로 하는 생성자 생성
  • `@Data`
    • `@getter`, `@setter`, `@ToString`, `@EqualsAndHashCode`, `@RequireArgsConstructor`를 모두 적용
    • 테스트 용도로 주로 사용
  • `@Builder` : 빌더 패턴을 적용해 객체를 생성할 수 있다
@Builder
public class User {
    private String name;
    private int age;
}

// 사용 예시
User user = User.builder()
                .name("John")
                .age(30)
                .build();
  • `@Slf4j` : 로그를 남기기 위한 Logger 객체 자동 생성
@Slf4j
public class UserService {
    public void logMessage() {
        log.info("This is a log message");
    }
}

 


어노테이션 

  • 사용자 정의 어노테이션
    • 개발자가 필요에 따라 어노테이션 정의 가능
  • 내장 어노테이션
    • `@Override`
    • `@Deprecated`
      • 더이상 사용되지 않음을 표시
      • 해당 요소 사용 시, 컴파일 경고 발생
      • 새로운 API 사용 유도로 활용
    • `@SuppressWarnings`
      • 컴파일러 경고 무시
    • `@Require`

 

 

`@Require`

  • setter에 사용하는 어노테이션
  • 해당 세터를 호출할 때, 반드시 `<property>`를 포함하도록 강제
import org.springframework.beans.factory.annotation.Required;

public class Person {
    private String name;
    private int age;

    @Required
    public void setName(String name) {
        this.name = name;
    }

    @Required
    public void setAge(int age) {
        this.age = age;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!-- Person 빈 정의 -->
    <bean id="person" class="com.example.Person">
        <property name="name" value="John Doe" />   <!-- 필수!! -->
        <property name="age" value="30" />	<!-- 필수!! -->
    </bean>

</beans>

 

 

'언어, 프레임워크 > Spring' 카테고리의 다른 글

요청(Request) 데이터 전달 방식  (1) 2025.03.20
Spring Annotation (+Request Mapping)  (0) 2025.03.19
Spring MVC  (0) 2025.03.19
WAS / Servlet / SSR, CSR  (0) 2025.03.19
HTTP, RESTful API 알아보기  (0) 2025.03.17

HTTP(HyperText Transfer Protocol)

클라이언트-서버 간 데이터를 주고받는 통신 규약

서버-서버 간 통신에도 사용

 


HTTP 특징

1. 무상태(Stateless)

서버가 클라이언트의 상태를 보존하지 않음

  • Scale Out 수평 확장성이 높아, 요청량 증가해도 서버 증설이 쉽다
  • 클라이언트가 데이터를 추가적으로 전송해야 한다
  • 로그인처럼 무상태로 설계 불가능한 경우 존재
    • Cookie, Session, Token 활용해서 해결 가능

2. 비연결(Connectionless)

  • 서버와 클라이언트의 연결을 유지하지 않음
  • 서버 자원을 효율적으로 사용 가능
  • 추가적인 요청 발생 시 연결을 새로 해야 한다
    • 요청에 대한 응답 시간 증가
  •  HTML, CSS, JS, 이미지 등의 정적 자원을 모두 다시 다운로드
    • 캐시, 브라우저 캐싱(임시 저장)으로 해결

 


HTTP 메시지 구조

요청(Request)

GET /index.html HTTP/1.1  <-- 요청 라인 (Start-Line)
Host: example.com         <-- 헤더 (Headers)
User-Agent: Mozilla/5.0  
Content-Type: application/json  
Content-Length: 34  

{ "username": "user1", "password": "1234" }  <-- 본문 (Body, 선택적)

 

1. 요청 라인(Start-Line)

  • HTTP Method
  • path
  • HTTP Version (1.1이 표준)

HTTP Method

 

2. 헤더

요청 정보를 `field-name: OWS field-value OWS (OWS : 띄어쓰기 허용)` 형태로 작성

 

3. 공백 한 줄 필수

 

4. Message Body

실제 전송하는 데이터가 담긴 부분

 


응답(Response)

HTTP/1.1 200 OK          <-- 상태 라인 (Status-Line)
Content-Type: text/html  <-- 헤더 (Headers)
Content-Length: 27  

<html><body>Welcome!</body></html>  <-- 본문 (Body)

 

1. 상태 라인(Status-Line)

  • HTTP Version
  • 상태 코드
  • 상태 메시지

status code

 

2. 헤더

응답 정보를 `field-name: OWS field-value OWS (OWS : 띄어쓰기 허용)` 형태로 작성

 

3. 공백 한 줄 필수

 

4. Message Body

실제 전송하는 데이터가 담긴 부분

 

일부 서버는 GET 요청의 Body를 무시할 수 있으므로, GET 요청에서는 Message Body를 포함하는 것을 권장 X
👉 
서버에 추가적인 데이터 전송이 필요하다면, path에 Query String을 사용
GET /search?q=Spring HTTP/1.1
PUT으로 기존 리소스가 존재하지 않는 데이터를 수정하려고 하면, POST와 같은 기능
PUT으로 기존 리소스가 존재하는 데이터를 수정
할 때, body에 명시된 값만 기존 리소스에 남고 나머진 소실되니 주의
👉 PATCH로 데이터를 수정하면, 명시되지 않은 값도 그대로 남아있음

 


 

RESTful API

REST(Representational State Transfer)

자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 것

URI를 통해 자원을 명시하고, HTTP Method를 통해 해당 자원에 대한 CRUD Operation을 적용하는 것

 

RESTful API

REST를 잘 준수하는 API로, HTTP 프로토콜을 사용하여 클라이언트와 서버 간의 통신을 통해 자원을 관리

요청과 응답은 일반적으로 JSON 또는 XML 형식

 

 

REST API URI Naming Conventions

URI(Uniform Resource Identifier) : 인터넷에서 특정 리소스를 식별하는 문자열

  • 명사 사용
  • 복수 형태 사용 (해결하기 어려운 경우 동사 허용)
  • 슬래시(/)로 자원의 계층 관리
  • 마지막 문자에는 슬래시 X
  • 언더바가 아닌 하이픈(-) 사용
  • 소문자 사용
  • 파일 확장자 포함 X
  • CRUD 함수명 사용 X
  • 정렬, 필터링, 페이징은 신규 API를 만드는 게 아니라 Query Parameter 사용

 

Maturity Model (성숙도 모델)

REST의 제약 조건에 따라 API를 등급화하는 방법

Level 0

  • 웹서비스 제공을 위해 URL만 매핑해둔 상태
더보기
POST /operation
{
    "operation": "createUser",
    "data": {
        "name": "sparta",
        "password": "codingclub"
    }
}

Level 1

  • 리소스를 의미있는 URL로 표현
  • 서비스 형태나 작업 종류에 맞추어 적절한 HTTP 메소드를 지정하지 않음
  • 사용자의 요청을 GET, POST로 대부분 처리하고 에러를 반환
더보기
POST /users
{
    "name": "sparta",
    "password": "codingclub"
}

Level 2

  • 리소스를 적절하게 용도와 상태에 따라서 HTTP Methods에 맞게 설계
  • RESTful API 설계 시 최소한 맞춰야할 등급
더보기
GET /users/123     // 특정 사용자 조회

POST /users        // 사용자 생성
{
    "name": "sparta",
    "password": "codingclub"
}

PUT /users/123     // 사용자 정보 수정
{
    "name": "java",
    "password": "spring"
}

DELETE /users/123  // 사용자 삭제

Level 3

  • 데이터를 가지고 그 다음 작업에서 어떠한 작업을 할 수 있는지 상태 정보를 함께 넘겨준다
  • 엔드포인트만 가지고 있으면 서버가 제공할 수 있는 다음, 그 다음 URI값을 알 수 있다
HATEOAS(Hypermedia As The Engine Of Application State)
클라이언트가 서버의 응답을 통해 추가적인 액션(링크)을 동적으로 발견할 수 있도록 하는 원칙
👉 API 응답 안에 다음에 호출할 수 있는 관련 API 링크를 포함하는 방식
더보기
GET /users/123
{
    "id": 123,
    "name": "sparta",
    "links": {
        "self": "/users/123",
        "update": "/users/123",
        "delete": "/users/123"
    }
}

 


 

'언어, 프레임워크 > Spring' 카테고리의 다른 글

요청(Request) 데이터 전달 방식  (1) 2025.03.20
Spring Annotation (+Request Mapping)  (0) 2025.03.19
Spring MVC  (0) 2025.03.19
WAS / Servlet / SSR, CSR  (0) 2025.03.19
스프링 기초 개념  (9) 2025.03.18

문제 설명

자연수 n을 뒤집어 각 자리 숫자를 원소로 가지는 배열 형태로 리턴해주세요. 예를들어 n이 12345이면 [5,4,3,2,1]을 리턴합니다.

 

제한 조건

n은 10,000,000,000이하인 자연수입니다.

 

입출력 예

n return
12345 [5, 4, 3, 2, 1]

 


 

1차 시도

class Solution {
    public int[] solution(long n) {
        String input = String.valueOf(n);
        
        int[] answer = new int[input.length()];
        
        for(int i = 0; i < input.length(); i++){
            answer[input.length() - (i + 1)] = Integer.valueOf(input.charAt(i) - '0');
        }
        
        return answer;
    }
}

 

1. 입력받은 값의 길이를 구하기 위해 문자열로 변환

2. 변환한 문자열의 각 문자를 answer 배열에 저장

-> 배열에 의도한 값이 아니라 유니코드 값이 저장되어 출력되는 오류 발생

3. '0'의 유니코드 값을 마이너스 연산해서 의도한 값이 출력되도록 함 ('5'의 유니코드(53) - '0'의 유니코드(48) = 5)

 


2차 시도

1차 시도에 작성한 코드도 주어진 문제는 잘 실행되지만, 문자열이 입력되는 상황에서도 잘 동작하는 코드를 만들고 싶어서 2차 시도를 진행하게 되었다.

import java.util.ArrayList;
import java.util.List;

class Solution {
    public int[] solution(long n) {
        List<Long> list = new ArrayList<>();
        
        while(n>0){
            list.add(n%10);
            n/=10;
        }
        
        return list.stream().mapToInt(Long::intValue).toArray();
    }
}

 

1차 시도와는 다르게, 리스트를 사용해서 값을 바로 집어넣으면 for문의 인덱스로 각 위치에 접근할 필요가 없다.

1. `n%10` 연산자로 1의 자리 수부터 리스트에 넣어준다.

2. 저장한 값은 `n/=10`으로 버린다.

3. 값을 다 넣어서 더이상 남은 수가 존재하지 않으면 n은 0이 되고, 반복문 종료

4. mapToInt()로 각 데이터를 IntStream으로 변환

5. Long 클래스 내부의 intValue 메서드를 통해 long이었던 n값을 int로 변환

6. toArray() 메서드로 배열로 반환

 

n%10을 하면 list에 저장될 값은 한 자리의 정수인데, 왜 굳이 List<Long> 타입으로 정의해야 할까?
solution() 메서드에 전달된 n이 long타입이기 때문에 list에 들어갈 값이 한 자리의 정수여도, 그 값은 long 타입이다.

 

 

만약 list의 타입 매개변수를 Integer라고 지정하면 이미지와 같은 에러가 발생하게 된다.

Comparator를 적용해서 람다 실습하기

 

Chat GPT가 내준 문제,,

 

Chat GPT에게 람다 실습을 위한 문제를 달라고 요청했더니 comparator를 적용하는 문제를 줘서 공부하게 됐다.

기존의 Collections.sort()나 stream().sorted()는 자연적인 순서로 정렬해준다면,

Comparator는 내가 어떤 기준으로 정렬할지 직접 설정할 수 있다.

 

public class RamdaPrac {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("banana", "apple", "cherry", "blueberry");

        Comparator comparator = (word1, word2) -> {
            if (word1.toString().length() > word2.toString().length()) {
                return 1;
            }else if(word1.toString().length() < word2.toString().length()){
                return -1;
            }
            else{
                return 0;
            }
        };

        Collections.sort(words, comparator);

        System.out.println(words);
    }
}

 

람다식을 사용해서 Comparator 인터페이스를 구현했다.

두 개의 인자를 받아서, 첫 번째 인자가 큰 경우 1을 반환하고 더 작은 경우는 -1을, 동일한 경우는 0을 반환하도록 했다.

숫자로 리턴하는 이유는 Comparator의 compare() 가 int형을 반환하는 메서드이기 때문이다.

compare() 메서드의 반환값이 음수이면 두 번째 인자가 더 크다고 판단, 양수이면 첫 번째 인자가 더 크다고 판단, 0이면 동일하다고 판단하는 메서드이다.

 

Collections.sort() 메서드는 인자가 하나만 올 때는 인자로 전달된 리스트를 자연 정렬하는 메서드이다.

하지만 인자가 두 개인 경우는 두 번째 인자로 전달된 Comparator 내부의 compare() 메서드가 실행되어 개발자가 정의한 기준대로 리스트를 정렬한다.

위의 리스트를 자연 정렬했다면 알파벳 순서로 정렬해서 출력하지만, 위의 실습 코드는 정의한대로 문자열의 길이를 기준으로 정렬해서 출력하게 된다.

 

 


 

두 번째 Comparator + 람다 실습

 

package study;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) {
        List<Person> personList = new ArrayList<>(
                List.of(
                        new Person("김땡땡", 27),
                        new Person("이땡땡", 25),
                        new Person("홍길동", 30)
                )
        );

        Comparator<Person> comparator = (p1, p2) -> {
            if(p1.age > p2.age){
                return 1;
            }else if(p1.age < p2.age){
                return -1;
            }else{
                return 0;
            }
        };

        Collections.sort(personList, comparator);

        for(Person person : personList){
            System.out.println(person.name);
        }
    }
}

 


 

andThen(), compose() 적용해서 람다 실습하기

 

람다 문제인데 람다는 제공한 코드에 있어서,, 메서드만 실습

import java.util.function.Function;

public class AndThen {
    public static void main(String[] args) {
        Function<String, String> toUpperCase = str -> str.toUpperCase();
        Function<String, String> addExclamation = str -> str + "!";

	// andThen() 실습
        String input1 = "hello";
        Function<String, String> output1 = toUpperCase.andThen(addExclamation);
        System.out.println(output1.apply(input1));

	// compose() 실습
        String input2 = "java";
        Function<String, String> output2 = toUpperCase.compose(addExclamation);
        System.out.println(output2.apply(input2));
    }
}

 

Function<T, R>은 자바에서 제공하는 고차 함수형 인터페이스로, 입력값을 받아 변환하는 함수형 객체를 생성할 수 있다.

여기서 제네릭 T는 입력값에 대한 타입 파라미터이고, R은 출력값에 대한 타입 파라미터다.

이 인터페이스를 활용하면 compose(), andThen() 같이 함수 조합 기능을 사용할 수 있다.

 

compose(), andThen()은 스트림처럼 여러 개의 함수를 연결해서 순차적으로 실행할 수 있도록 해준다.

andThen()은 앞에 있는 함수를 먼저 수행하고 인자에 있는 함수를 이어 수행한다.

compose()는 인자로 있는 함수를 먼저 수행하고 앞에 있는 함수를 수행한다.

 

Function<String, String> toUpperCase = str -> str.toUpperCase();
Function<String, String> addExclamation = str -> str + "!";

Function<String, String> output1 = toUpperCase.andThen(addExclamation);
Function<String, String> output2 = toUpperCase.compose(addExclamation);

 

즉 위의 4줄의 코드는 모두 Function 인터페이스의 apply() 추상 메서드를 구현한 것이지만, 

위의 두 줄은 단순 구현한 것이고, 뒤의 두 줄은 함수와 함수를 연결하는 기능을 구현한 것이다.

'언어, 프레임워크 > Java' 카테고리의 다른 글

QueryDSL  (0) 2025.05.02
JDBC 알아보기 (+SQL Mapper의 한계)  (0) 2025.03.21
BufferedReader / BufferedWriter  (0) 2025.03.11
예외 복구/회피/전환 (예외 전환의 필요성)  (0) 2025.03.10
Assert 알아보기  (0) 2025.03.08

EOF(End of File)는 파일이나 입력 스트림의 끝을 의미한다.

파일을 끝까지 읽었거나, 입력 스트림에 직접 EOF를 입력하면 프로그램은 EOF가 발생한 걸 인식할 수 있다.

 

대표적인 예시는 백준 10951번 문제를 보면 된다.

 


문제

두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다.

각 테스트 케이스는 한 줄로 이루어져 있으며, 각 줄에 A와 B가 주어진다. (0 < A, B < 10)

 


처음 접근한 방법

package baekjoon;

import java.io.*;

public class B10952 {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        String inputString;
        int sum;

        while ((inputString = br.readLine()) != null) {
            String[] inputStringArray = inputString.split(" ");

            sum = 0;

            for (int i = 0; i < inputStringArray.length; i++) {
                sum += Integer.parseInt(inputStringArray[i]);
            }

            bw.write(sum + "\n");
        }

        bw.flush();
        bw.close();
        br.close();
    }
}

 

EOF에 대해 알아보는 과정에서, while문의 조건식에서 바로 입력을 받을 수 있다는 것을 배웠다!

 

 

인텔리제이에서는 이미지처럼 공백을 입력해서 NumberFormatException이 발생한다.

하지만 이 코드를 백준에 제출하면 문제없이 해결된다.

구글링해보니 백준은 입력값 자체를 파일로 넘겨주기 때문에 프로그램에서 EOF를 인식하고 null로 반환하여 제대로 실행되는데,

인텔리제이에서 엔터를 입력해서 EOF를 발생시켰을 때는 EOF가 아닌 잘못된 형식의 입력으로 받아들이는 것 같다.

 

 

개선된 코드

while ((inputString = br.readLine()) != null && !inputString.isEmpty())

 

파일 입력 뿐만 아니라 입력 스트림의 끝도 처리할 수 있도록 코드를 수정했다.

isEmpty()는 컬렉션, 문자열 등이 비어있는지 확인하는 메서드로, 비어있다면 true를 반환한다.

즉, 위의 코드는 EOF가 발생하거나, inputString이 비어있는 경우는 반복문을 실행하지 않고 종료하는 코드이다.

 


 

결과

 

이렇게 해서 공백을 입력하면 반복이 종료되면서 공백 입력 전까지의 연산 결과가 출력되는 것을 볼 수 있다.