Spring MVC 구조

📌 Spring은 MVC 패턴에 프론트 컨트롤러 패턴, 어댑터 패턴이 적용된 구조를 가지고 있다.

  • MVC는 소프트웨어 설계 패턴으로 구축 개념에 가깝다. 당연히 구축 방식은 때에 따라 달라왔고 이를 Spring에서는 통합하여 하나의 템플릿으로 제공한다.

 

MVC 패턴 구조

  1. 요청이 오면 Controller에서 파라미터 정보 확인하여 비지니스 로직을 실행한다.
  2. 비지니스 로직의 결과 Data 를 Model에 담아서 View에 전달해준다.
  3. View는 모델의 Data를 참조하여 화면을 그려준다.

Spring MVC 구조

  • DispatcherServlet : Spring의 프론트(프론트엔드 아님, HTTP 요청의 최전선을 프론트라인이라 함) 컨트롤러
  • View : 인터페이스로 구성되어 있다, 확장성을 가지고 있다.

 

실행순서

  1. Client로 부터 HTTP 요청(Request)을 받는다.
  2. Handler 조회
    • Handler Mapping을 통해 요청 URL에 Mapping된 Handler(Controller)를 조회
  3. Handler를 처리할 Adapter 조회
    • Handler를 처리할 수 있는 Handler Adapter를 조회
  4. Handler Adapter 실행(handle)
    • 알맞은 ****어댑터가 존재한다면 ****Handler Adapter에게 요청을 위임한다.
  5. Handler 실행(호출)
    • Handler Adapter가 실제 Handler(Controller)를 호출하여 실행 및 결과 반환
  6. Model And View 반환(return)
    • Handler Adapter는 Handler가 반환 하는 정보를 ModelAndView 객체로 변환하여 반환
  7. viewResolver 호출(알맞은 View 요청)
    • View Resolver를 찾고 실행
  8. View 반환
    • View Resolver는 View의 논리 이름을 물리 이름으로 전환하는 역할을 수행하고 Rendering 역할을 담당하는 View 객체를 반환
  9. View Rendering
    • View를 통해서 View를 Rendering
더보기

1. Client로부터 HTTP 요청(Request)을 받는다

  • 고객(클라이언트)이 배달 앱에서 음식을 주문합니다. (예: "치킨 1마리 배달 요청")
  • 배달 앱이 이 주문을 **중앙 주문 처리 서버(SPRING MVC)**로 보냅니다.

2. Handler 조회 (Handler Mapping)

  • 서버는 고객이 요청한 **주문 정보(예: 치킨 배달)**를 확인하고, 이 요청을 처리할 적절한 음식점(Handler)을 찾아야 합니다.
  • 여기서 Handler Mapping 주문에 맞는 음식점을 조회하는 과정입니다.
    • 예: 고객이 "치킨"을 주문했으니, 근처의 치킨집을 선택합니다.

3. Handler를 처리할 Adapter 조회

  • 음식점마다 주문을 처리하는 방식(프로세스)이 다를 수 있습니다.
    • 어떤 음식점은 앱을 통해 바로 확인하고,
    • 어떤 음식점은 전화로 주문을 확인해야 합니다.
  • 이 단계에서는 요청을 처리할 수 있는 적절한 **Handler Adapter(음식점과 서버를 연결하는 도구)**를 찾습니다.
    • 예: 치킨집은 앱으로 주문을 확인하는 시스템이 있으니, 앱 기반 주문 어댑터를 선택.

4. Handler Adapter 실행(handle)

  • 적절한 **Handler Adapter(주문 어댑터)**가 음식점에 주문을 전달합니다.
    • 예: 앱이 "치킨 한 마리 주문 요청"을 음식점의 주문 시스템에 보냅니다.

5. Handler 실행(호출)

  • 음식점(Handler)이 실제로 주문을 준비하기 시작합니다.
    • 예: 주방에서 치킨을 튀기고 포장합니다.

6. Model And View 반환(return)

  • 음식점은 준비가 끝난 뒤, "주문이 완료되었습니다!"라는 메시지와 함께 준비된 치킨 데이터를 서버에 보냅니다.
    • 예: "치킨 1마리, 포장 완료!"
    • 여기서 ModelAndView는 이 데이터("치킨 데이터")와 응답 메시지를 함께 묶어서 반환하는 역할을 합니다.

7. View Resolver 호출 (알맞은 View 요청)

  • 서버는 고객에게 응답을 보내기 전에, 결과 데이터를 어떻게 보여줄지 결정합니다.
    • 예: 고객이 앱을 통해 결과를 볼 수 있어야 하므로, 앱 화면(View)을 설정해야 합니다.
  • View Resolver는 "치킨 준비 완료"라는 정보를 앱의 알맞은 화면(예: 주문 완료 페이지)으로 연결합니다.

8. View 반환

  • View Resolver는 "주문 완료 화면"을 앱에 전달합니다.
    • 예: 고객의 앱에 "주문 완료! 배달 중!"이라는 화면이 나타남.

9. View Rendering

  • 고객의 앱에서 실제로 화면이 표시됩니다.
    • 예: 고객은 "주문이 완료되었고, 곧 배달됩니다!"라는 메시지를 화면에서 확인합니다.

결론: 배달 서비스로 비유한 실행 흐름

  1. 고객이 배달 앱에 주문 요청을 보냄 (HTTP 요청)
  2. 서버가 요청을 처리할 음식점을 찾음 (Handler Mapping)
  3. 주문을 전달하는 적절한 방식을 선택 (Handler Adapter 조회)
  4. 음식점이 주문을 준비함 (Handler 실행)
  5. 음식점이 준비 상태를 서버에 반환 (ModelAndView 반환)
  6. 서버가 결과를 앱 화면에 맞게 변환 (View Resolver 호출)
  7. 결과가 앱 화면에 표시됨 (View Rendering)

 

요약

  • DispatcherServlet ( 요청과 응답의 중앙 처리국 )
    1. 클라이언트 HTTP Request를 알맞게 파싱하고 클라이언트에게 알맞은 응답을 반환
    2. 핸들러 목록 정보를 알고있다.
    3. 핸들러 어댑터 목록 정보를 알고있다.
  • HandlerAdapter (부서 담당 연결원)
    1. 자신이 처리할 수 있는 Handler인지 확인할 수 있는 기능(Method)이 필요하다.
    2. 프론트 컨트롤러에서 요청을 위임받았을 때 핸들러에게 요청을 지시하는 기능이 필요하다.
    3. return 시 Handler로부터 전달받은 결과를 알맞은 응답으로 변환한다.
  • Handler (처리자)
    1. 요청에 대한 로직을 수행하는 기능이 필요하다.

 

 

Dispatcher Servlet

📌 모든 HTTP 요청의 진입점으로 작동하는 프론트 컨트롤러(Front Controller)로, 요청 처리의 중앙 허브(총괄), 핸들러 매핑(배정), 핸들러 어댑터 실행(인력 파견), 응답 반환(우편전달)을 다 한다.

  • Spring MVC의 프론트 컨트롤러는 Dispatcher Servlet(Servlet의 한 종류)이다.

  1. Dispatcher Servlet은 HttpServlet을 상속 받아서 사용하고 Servlet의 한 종류이다.
  2. Spring Boot는 Dispatcher Servlet을 서블릿으로 자동으로 등록(내장 Tomcat WAS를 실행하면서 등록한다)하고 모든 URL 경로에 대해서 Dispatcher Servlet을 Mapping 한다. → (urlPatterns=”/”) = 요청이 들어오는 모든 url에 총괄 관리자를 붙인다.
  3. 더 자세한 URL 경로가 높은 우선순위를 가진다. = 요구사항이 자세한 손님부터 처리해 주겠다는 것 = 효율적이라서
    • 개발자가 만들 Servlet이 항상 우선순위가 높아서 실행된다.

 

DispatcherServlet의 service()

  1. Servlet이 호출되면 HttpServlet이 제공하는 service()가 호출된다.
  2. Spring MVC는 DispatcherServlet의 부모인 FrameworkServlet에서 service()를 Override 해두었다.
  3. FrameworkServlet.service()를 시작으로 여러 메서드가 호출됨과 동시에 가장 중요한DispatcherServlet.doDispatch()가 호출된다.

protected void doDispatch() {
...
// 1. 핸들러 조회
	mappedHandler = getHandler(processedRequest); 
	if (mappedHandler == null) {
		noHandlerFound(processedRequest, response); // NotFound 404
	}

// 2. 핸들러 어댑터 조회 : 핸들러를 처리할 수 있는 어댑터
	HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 3. 핸들러 어댑터 실행
// 4. 핸들러 어댑터를 통해 핸들러 실행 
// 5. ModelAndView 반환 
	mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 여기 안에서 render   
	processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);
	...
}


// processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		if (mv != null && !mv.wasCleared()) {
			// View Render 호출
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
}

// render()
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
	View view;
	String viewName = mv.getViewName();

// 6. ViewResolver를 통해 View 조회
// 7. View 반환
	view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 8. View Rendering
	view.render(mv.getModelInternal(), request, response);
}

 

 

 

Spring MVC의 주요 Interface

📌 Spring MVC는 DispatcherServlet 코드의 변경 없이 기능변경 및 확장이 가능하다. 기능들이 대부분 Interface로 만들어져 있기 때문이다.

  • 이쯤에서 한번 더 짚고 넘어가면 왜 대부분이 클래스가 아니라 인터페이스로 구현되어 있냐는 질문엔, 다형을 위해서가 정답이다.
  • org.springframework.web.servlet
    1. HandlerMapping
    2. HandlerAdapter
    3. ViewResolver
    4. View
  • 당연히 이 모든 인터페이스를 알 필요는 없다. 하지만 자주 사용하는 인터페이스에 대해서는 잘 알아야 확장이나 구현, 문제 해결에 있어 용이하다.

 

Controller Interface

📌Spring 2.5 이전에 모든 컨트롤러가 구현해야 했던 표준 인터페이스입니다. 이 인터페이스는 클라이언트의 요청을 처리하고, 결과를 View로 전달하는 기본적인 방법을 정의합니다.

  • Controller Interface를 implements 하여 구현하게되면 개발자가 원하는 Controller(Handler)를 사용할 수 있게됩니다.
  • 추후 강의에 등장할 현대에 사용하는 Annotation 기반 Spring의 @Controller와는 역할이 비슷하지만 연관은 없습니다.
package com.example.springbasicmvc.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

// Spring Bean 이름을 URL로 설정
@Component("/example-controller")
public class ExampleController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("example-controller가 호출 되었습니다.");
        return null;
    }
}
  • http://localhost:8080/example-controller 로 HTTP 요청을 하게 되면 응답결과가 반환된다.

  • 출력 결과

 

  • @Component
    • Spring Bean에 등록하는 역할을 수행한다.
      • Spring Bean은 애플리케이션의 구성 요소를 정의하는 객체이다.
      • 마치 Servlet이 Servlet Container에 등록되는 것과 같다.

 

 

  1. Handler Mapping
    1. 핸들러 매핑에서 ExampleController( Spring 애플리케이션에서 특정 요청을 처리하는 컨트롤러 클래스)를 찾을 수 있어야 한다.
    → Spring Bean의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
  2. Handler Adapter
    1. Handler Mapping을 통해 찾은 핸들러를 실행할 수 있는 Handler Adapter가 필요
    → Controller Interface를 실행할 수 있는 Handler Adapter를 찾고 실행한다.
  • 놀랍게도 Handler Mapping은 찾기만 하고 Handler Adapter가 Handler에게 배치해줘야 한다.
  • 유지보수와 확장성에 용이하다...

 

Spring Boot의 Handler Mapping, Handler Adapter

📌 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 HandlerMapping과 HandlerAdapter들이 있다.

  • HandlerMapping, HandlerAdapter 모두 우선순위대로 조회한다.

 

  • HandlerMapping
    • 우선순위 순서
    1. RequestMappingHandlerMapping
      • 우선순위가 가장 높다
      • Annotation 기반 Controller의 @RequestMapping에 사용
    2. BeanNameUrlHandlerMapping(위 예시코드에 사용)
      • Spring Bean Name으로 HandlerMapping
  • HandlerAdapter
    • 우선순위 순서
    1. RequestMappingHandlerAdapter
      • Annotation 기반 Controller의 @RequestMapping에서 사용
    2. HttpRequestHandlerAdapter
      • HttpRequestHandler 처리
    3. SimpleControllerHandlerAdapter(위 예시코드에 사용)
      • Controller Interface 처리

 

@RequestMapping 은 가장 높은 우선순위의 HandlerMapping인 RequestMappingHandlerMapping 과 가장 높은 우선순위의 HandlerAdapter인 RequestMappingHandlerAdapter 두가지를 사용하며 현대에 사용하는 Annotation 기반의 컨트롤러를 지원한다.

 

HttpRequestHandler로 알아보는 Spring MVC 동작 순서

// 인터페이스
public interface HttpRequestHandler {
	void handleRequest(HttpServletRequest request, HttpServletResponse response)
					throws ServletException, IOException;

}

 

// 구현체
@Component("/request-handler")
public class ExampleRequestHandler implements HttpRequestHandler {

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		System.out.println("request-handler Controller 호출");
		// 구현 로직
	}

}

Postman

출력결과

 

 

실행순서

  1. HandlerMapping 으로 핸들러 조회
    1. BeanName으로 Handler 조회(BeanNameUrlHandlerMapping 실행)
    2. ExampleRequestHandler 반환
  2. HandlerAdapter 조회
    1. HandleAdapter의 supports()를 우선순위 순서대로 호출
    2. HttpRequestHandlerAdapter가 HttpRequestHandler Interface를 지원한다
    • HttpRequestHandlerAdapter.supports()

  1. HandlerAdapter 실행
    1. DispatcherServlet이 조회한 HttpRequestHandlerAdapter를 실행하며 Handler 정보도 넘긴다
    2. HttpRequestHandlerAdapter 는 ExampleRequestHandler를 내부에서 실행 후 결과를 반환
    • HttpRequestHandlerAdapter.handle() → 단순히 handleRequest를 호출한다 = 오버라이딩된 handleRequest() 호출

 

DispatcherServlet에서 호출 → ha.handle()

 

 

 

View Resolver

📌 컨트롤러가 반환한 뷰 이름(View Name)을 실제 뷰 파일의 경로(View Path)로 변환하고, 클라이언트에게 응답할 화면(View)을 렌더링할 수 있도록 도와주는 컴포넌트입니다.

  • 반환된 ModelAndView 객체를 알맞은 View로 전달하기 위해 DispatcherServlet에서 ViewResolver를 호출하여 View 정보를 설정하는 역할을 수행한다.
  • 서버는 HTML 파일(또는 다른 뷰 파일)을 뷰(View)로 변환하여 클라이언트에게 응답으로 보낸다.는 말
package com.example.springbasicmvc.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

// Spring Bean 이름을 URL로 설정
@Component("/view-controller")
public class ViewController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

        System.out.println("view-controller가 호출 되었습니다.");

        // "test"는 논리적인 ViewName이다. ViewResolver가 물리적인 이름으로 변환해야 한다.
        return new ModelAndView("test");
    }
}
  • Template Engine JSP
    • webapp/WEB-INF/form.JSP

 

[sql입니다]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <meta charset="UTF-8">
  <title>블로그 포스트 작성 페이지</title>
</head>
<body>
<h1>블로그 글쓰기</h1>
<form action="save" method="post">
  title: <input type="text" name="title" placeholder="제목" />
  content: <input type="text" name="content" placeholder="내용" />
  <button type="submit">저장</button>
</form>

</body>
</html>
  • ViewResolver
    • application.properties 설정
    • 설정을 기반으로 Spring Boot가 InternalResourceViewResolver 를 만든다.
[xml입니다]
spirng.mvc.view.prefix=/WEB-INF/views/
spirng.mvc.view.suffix=.jsp

 

localhost:8080/view-controller 호출

 

  • ViewName으로 View를 찾지 못하는 경우(View가 존재하지 않음)
@Component("/error-controller")
public class WhitelabelErrorController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("error-controller가 호출 되었습니다.");
				// viewName "sparta"는 존재하지 않는다.
        return new ModelAndView("sparta");
    }
}

 

  • 컨트롤러는 호출되지만 View를 못 찾아 Whitelabel Error Page를 응답한다.

 

 

 

Spring Boot의 ViewResolver

📌 Spring Boot를 사용하면 개발에 필요하여 자동으로 등록되는 ViewResolver들이 있다.

 

  • 우선순위 순서
    • 아래 두 가지 이외에도 많은 ViewResolver가 존재한다.
    1. BeanNameViewResolver
      1. Bean Name으로 View를 찾아 반환
    2. InternalResourceViewResolver(위 예시코드)
      1. application.properties 설정 파일에 등록한 prefix, suffix 설정 정보를 사용하여 ViewResolver 등록
// 아래 코드를 자동으로 해주는것과 마찬가지이다.
@Bean
InternalResourceViewResolver internalResourceViewResolver() {
	return new InternalResourceViewResolver("/WEB-INF/views", ".jsp");
}

 

 

InternalResourceViewResolver로 알아보는 Spring MVC 동작 순서

  1. HandlerAdapter 호출
    • HandlerAdapter를 통해 “test” 논리 View Name 얻음
  2. ViewResolver 호출
    • ”test” 이라는 View Name으로 viewResolver를 우선순위 대로 호출
      • BeanNameViewResolver는 View를 찾지 못한다.
      • InternalResourceViewResolver 호출
  3. InternalResourceViewResolver
    • InternalResourceViewResolver.buildView(String viewName)
    InternalResourceView 반환

 

4. InternalResourceView

  • JSP와 같이 서버에서 이동하는 forward()를 호출하는 경우와 같을 때 사용한다.

 

renderMergedOutputModel() → Model을 Request로 바꾼다.

 

5. view.render()

  • 외부에서 view.render()를 호출 후 ****RequestDispatcher를 가져와 forward()한다.

→ 매우 복잡한 구조를 가지고 있으니 모두 찾아볼 필요가 없습니다.

 

Thymeleaf는 View와 Resolver가 이미 존재한다. 라이브러리 의존성만 추가해주면 SpringBoot가 모두 자동으로 해준다. 즉, return “viewName”; 만으로 View가 Rendering 된다.

 

 

 

 

 

 

 

 

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Request Mapping  (1) 2024.12.16
[Spring] Spring Annotation  (1) 2024.12.15
[Spring] Spring Boot  (0) 2024.12.10
[Spring] Spring Framework  (2) 2024.12.09
[Spring] 웹 개발의 흐름  (1) 2024.12.05

📚 Java 웹 기술은 서블릿과 JSP로 시작해 MVC 패턴을 거쳐 MVC 프레임워크의 도입으로 구조화되었으며, 이후 어노테이션 기반의 Spring MVC와 비동기 처리에 최적화된 Spring WebFlux로 발전해왔다.

 

  1. Servlet의 등장 (1997)
    • 개요
      • Java를 사용한 웹 개발의 시초로, 서버에서 동적으로 콘텐츠를 생성하기 위해 사용되었다. 클라이언트의 요청을 받고, 그에 대한 응답을 생성하는 기본적인 구조를 제공했다.
    • 단점
      • 코드의 복잡성이 증가하고 유지보수가 어려워졌다.
  2. JSP (JavaServer Pages) 도입 (1999)
    • 개요
      • JSP는 HTML 내에 Java 코드를 삽입할 수 있는 기술로, 웹 페이지를 더 쉽게 동적으로 생성할 수 있도록 도와주었다.
    • 단점
      • JSP 내에 비즈니스 로직을 분리할 수 없다.
  3. Servlet, JSP 기반의 MVC 패턴 도입
    • 개요
      • MVC 패턴이 도입되면서, UI, 비지니스 로직, 데이터를 분리하여 개발하는 방식이 등장했다.
      • Servlet은 주로 컨트롤러(비지니스 로직)로 사용되었고, JSP는 뷰(UI)를 담당하게 되었다.
    • 장점
      • MVC 패턴은 웹 애플리케이션의 유지보수성과 확장성을 크게 향상시켰다.
    • 단점
      • 개발자가 중복적으로 설정해줘야 하는 부분들이 다수 발생했다.
  4. MVC 프레임워크의 등장과 발전 (2000~2010)
    • 개요
      • Struts, Spring 등의 MVC 프레임워크가 등장하며, 웹 애플리케이션 개발이 더욱 구조화되고 효율적으로 변했다.
      • 그중 Spring MVC는 단순하면서도 강력한 기능을 제공하여, Java 웹 개발의 표준으로 자리 잡게 되었다.
      • 중복적으로 설정해야 하는 부분들을 프레임워크로 자동화 했다.
    • 단점
      • 여전히 애플리케이션 개발 관련 설정이 복잡했다.
  5. Annotation 기반의 Spring MVC(2007~현재)
    • 개요
      • Annotation을 통해 애플리케이션 설정의 복잡함을 줄여주었다.
    • 장점
      • 더 직관적이고 간결한 방식으로 웹 애플리케이션을 개발할 수 있게 되었다.
  6. Spring Boot의 등장(2014~현재)
    • 개요
      • Spring 프레임워크를 보다 쉽게 사용하도록 만든 도구로, 설정과 복잡한 초기 설정 작업을 자동화했다.
      • 내장 Tomcat을 가지고 있다.
    • 장점
      • 개발자들이 빠르게 애플리케이션을 개발할 수 있도록 도와준다.
  • 최신 기술 동향
    1. Web Servlet
      • Spring MVC
        • 안정적이고 동기식 프로그래밍 모델을 기반으로 한 웹 애플리케이션 개발에 널리 사용된다.
    2. Web Reactive
      • Spring WebFlux
        • 비동기 및 넌블로킹 모델을 기반으로 한 웹 프레임워크로, 높은 동시성을 요구하는 애플리케이션에서 효율적인 성능을 제공한다. 함수형 프로그래밍 스타일을 지원하며, 서블릿 기술 대신 Netty 등의 비동기 서버를 사용한다.
        • 서블릿 기술을 사용하지 않으며, 실시간 데이터를 처리하거나 높은 동시성을 요구하는 애플리케이션에 적합하다.
        • RDBMS 지원 부족과 높은 기술적 난이도 등으로 인해, 아직은 MVC 모델이 많은 실무에서 더 많이 사용되고 있다.

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] 열거형 ( Enum )  (0) 2024.11.22
[JAVA] HASH란 무엇인가  (1) 2024.11.21
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13

Spring Boot

📌 Spring Framework를 기반으로 하여 간편하고 신속하게 애플리케이션을 개발할 수 있도록 도와주는 도구이다.

Web Application 이라는 라면을 끓일 때(만들 때) 조리 도구 세트를 사용한다. 라면 : Java 냄비 : Spring 조리 도구 세트 : Spring Boot

 

 

[1]  Spring Boot의 주요 특징

특징설명

자동 설정 (Auto Configuration) Spring Boot는 애플리케이션의 환경을 자동으로 감지하여 적절한 설정을 자동으로 적용합니다. 이를 통해 많은 설정을 수동으로 할 필요 없이, 개발자가 설정에 대해 걱정하지 않고 애플리케이션을 시작할 수 있습니다.
독립 실행형 애플리케이션 Spring Boot는 내장된 서버(예: Tomcat, Jetty, Undertow 등)를 포함하여, WAR 파일 없이도 실행할 수 있는 독립 실행형 애플리케이션을 만듭니다.
스타터 프로젝트 (Starter Projects) Spring Boot는 필요한 라이브러리나 종속성을 손쉽게 추가할 수 있도록 "스타터" 의존성(예: spring-boot-starter-web, spring-boot-starter-data-jpa 등)을 제공합니다.
프로덕션 준비 Spring Boot는 메트릭스, 상태 점검, 로깅과 같은 프로덕션 환경에서 필요한 다양한 기능을 기본적으로 제공합니다. Actuator 라이브러리를 통해 애플리케이션의 상태를 모니터링하고 관리할 수 있습니다.
Spring Initializr Spring Boot는 프로젝트 초기화를 쉽게 할 수 있도록 https://start.spring.io/에서 다양한 설정을 선택하여 빠르게 프로젝트를 생성할 수 있습니다.
간단한 설정 Spring Boot는 application.properties 또는 application.yml 파일을 사용해 간단하게 애플리케이션의 설정을 관리할 수 있습니다.
서버의 자동 실행 내장 웹 서버를 통해 별도의 설정 없이, 애플리케이션을 java -jar 명령어로 실행할 수 있습니다.
개발자 친화적인 환경 Spring Boot는 애플리케이션의 개발과 디버깅을 빠르게 할 수 있도록 DevTools라는 기능을 제공하며, 코드 변경 시 자동으로 애플리케이션을 재시작하여 개발 편의성을 제공합니다.

 

[2]  Spring Boot의 장점

  1. 빠른 시작:
    • Spring Boot는 자동 설정 및 기본적인 템플릿을 제공하여, 애플리케이션을 몇 가지 설정만으로 빠르게 시작할 수 있습니다. @SpringBootApplication 어노테이션을 추가하면, 기본적인 설정이 자동으로 이루어지고, 애플리케이션을 실행할 수 있는 상태가 됩니다.
  2. 설정 최소화:
    • Spring Boot는 많은 설정을 자동으로 처리하므로 개발자는 비즈니스 로직에만 집중할 수 있습니다. 예를 들어, 데이터베이스 설정, 서버 설정 등 대부분의 설정을 자동으로 처리하여 개발자가 별도로 신경 쓸 필요가 없습니다.
  3. 내장 서버:
    • 내장 서버를 제공하여 별도의 외부 웹 서버(Tomcat, Jetty 등)를 설치할 필요 없이 바로 실행할 수 있습니다. 이는 애플리케이션의 배포를 간소화하고, 실행 파일 하나로 애플리케이션을 배포할 수 있게 만듭니다.
  4. 생산성 향상:
    • Spring Boot는 개발자가 빠르게 애플리케이션을 작성하고 실행할 수 있도록 돕기 위해 많은 스타터 의존성을 제공합니다. 예를 들어, 웹 애플리케이션을 만들기 위한 spring-boot-starter-web, 데이터베이스 연동을 위한 spring-boot-starter-data-jpa 등이 있습니다.
  5. DevTools:
    • Spring Boot는 개발 중에 자동 재시작Hot swapping을 지원하여, 개발자가 코드 수정 후 애플리케이션을 다시 시작하지 않고도 변경 사항을 바로 반영할 수 있도록 돕습니다.

 

[3]  Spring Boot 애플리케이션 구조

  1. 애플리케이션 클래스
    • @SpringBootApplication 어노테이션이 붙은 클래스는 Spring Boot 애플리케이션의 진입점입니다. 이 클래스는 자동으로 필요한 설정을 수행하고 애플리케이션을 실행합니다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 자동 설정:
    • Spring Boot는 애플리케이션을 실행할 때, 주어진 환경에 맞춰 자동으로 설정을 적용합니다. 예를 들어, 데이터베이스가 설정되면 자동으로 DataSource와 관련된 설정이 적용됩니다.
  2. 프로퍼티 파일:
    • Spring Boot는 application.properties 또는 application.yml 파일을 통해 애플리케이션의 설정을 관리할 수 있습니다. 데이터베이스 연결 정보, 서버 포트, 로깅 수준 등 다양한 설정을 이 파일에서 처리합니다.
  3. 내장 서버:
    • Spring Boot는 기본적으로 내장된 Tomcat 서버를 포함하고 있으며, 설정에 따라 다른 내장 서버(Undertow, Jetty 등)를 사용할 수 있습니다.

 

[4]  Spring Boot 애플리케이션 실행

  1. Maven 또는 Gradle을 사용한 빌드 후 실행:
    • 애플리케이션을 빌드하고 실행하려면, mvn spring-boot:run 또는 gradle bootRun 명령을 사용할 수 있습니다.
  2. JAR 파일로 실행:
    • Spring Boot 애플리케이션을 JAR 파일로 빌드한 후, java -jar 명령어로 실행할 수 있습니다.
java -jar myapp.jar

 

[5]  Spring Boot 사용 예시

  • 간단한 RESTful 웹 서비스:
@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}
  • 자동 설정 예시: Spring Boot는 데이터베이스 연결을 자동으로 처리할 수 있습니다. 예를 들어, 데이터베이스 설정을 application.properties에서 지정하면, Spring Boot는 이를 자동으로 인식하고 설정을 완료합니다.
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret

 

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring Annotation  (1) 2024.12.15
[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Framework  (2) 2024.12.09
[Spring] 웹 개발의 흐름  (1) 2024.12.05
[Spring] Spring 웹 애플리케이션 계층 구조  (2) 2024.11.13

Spring Framework

📌 Java Application Framework로 엔터프라이즈 애플리케이션 개발에 주로 사용된다.

  • 엔터프라이즈 애플리케이션은 대규모로 복잡한 비즈니스 프로세스와 데이터를 처리하는 애플리케이션을 뜻한다.
  • IoC (Inversion of Control), AOP (Aspect-Oriented Programming), DI (Dependency Injection) 등의 개념을 바탕으로 개발되었으며, 이를 통해 애플리케이션의 구조를 유연하고 모듈화하여 유지보수성을 높이고, 복잡한 엔터프라이즈 애플리케이션 개발을 더 간단하게 만들어 줍니다.

Spring Framework로 만드는 Web Application 라면 : Java 냄비 : Spring

[1]  Spring Framework 특징

애플리케이션의 다양한 구성 요소를 유연하게 연결하고 관리할 수 있도록 해준다.

  • Spring Framework는 누구나 사용할 수 있는 오픈소스 이다.
  • 모듈화되어 있어 필요에 따라 특정 기능만 선택적으로 사용할 수 있다.
  • Java언어의 가장 큰 특징인 객체 지향 언어의 특징을 살려낸 프레임워크이다.
    • 캡슐화
    • 상속
    • 추상화
    • 다형성

 

[2]  Spring의 구조

Spring Framework는 여러 모듈로 구성되어 있으며, 각 모듈은 특정 목적에 맞는 기능을 제공합니다. 주요 모듈은 다음과 같습니다:

  1. Core Container:
    • Core: Spring의 핵심 기능인 IoC (Inversion of Control) 및 DI (Dependency Injection)를 제공.
    • Beans: 객체 생성 및 관리.
    • Context: Spring의 애플리케이션 컨텍스트 기능을 제공, 이벤트 관리와 같은 다양한 기능 포함.
    • Spring AOP: AOP 기능을 제공하여, 메서드 호출 전후의 동작을 정의.
  2. Data Access/Integration:
    • JDBC: 데이터베이스와의 상호작용을 쉽게 할 수 있도록 지원.
    • ORM: Hibernate, JPA 등 객체-관계 매핑을 처리.
    • JMS: Java 메시지 서비스 (JMS)를 지원하여 메시지 기반 애플리케이션 구현.
  3. Web:
    • Spring MVC: 웹 애플리케이션을 위한 모델-뷰-컨트롤러 패턴을 지원.
    • WebSocket: 실시간 통신을 위한 WebSocket 지원.
    • WebFlux: 반응형 프로그래밍 모델을 기반으로 한 웹 프레임워크.
  4. Security:
    • 인증, 권한 부여 및 다양한 보안 관련 기능을 제공합니다.
  5. Testing:
    • Spring Test 모듈은 Spring 애플리케이션을 테스트하는 데 필요한 다양한 유틸리티를 제공합니다. @SpringBootTest 등을 통해 통합 테스트를 쉽게 할 수 있습니다.

 

[3]  Spring Framework의 주요 특징

1. IoC (Inversion of Control) / DI (Dependency Injection)

  • Spring은 객체를 직접 생성하는 대신, IoC 컨테이너를 통해 객체를 관리합니다. 객체 간의 의존 관계를 DI 방식으로 해결하여, 애플리케이션의 결합도를 낮추고 유연성을 높입니다.
  • 예를 들어, 하나의 클래스가 다른 클래스에 의존할 때, 이를 자동으로 주입해주는 방식입니다.
@Component
public class Service {
    private final Repository repository;

    @Autowired  // Repository를 자동으로 주입
    public Service(Repository repository) {
        this.repository = repository;
    }
}

 

2. AOP (Aspect-Oriented Programming)

  • AOP는 관점 지향 프로그래밍으로, 핵심 비즈니스 로직과 공통 기능(로깅, 트랜잭션 관리 등)을 분리하여 개발할 수 있게 도와줍니다. 이로 인해 코드가 더 깔끔해지고 재사용성이 증가합니다.
  • Spring에서는 AOP를 이용해 메서드 호출 전후에 특정 처리를 추가할 수 있습니다.
@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Method called: " + joinPoint.getSignature().getName());
    }
}

 

 

3. 모듈화

  • Spring은 여러 개의 모듈로 구성되어 있어 필요한 기능만 선택적으로 사용할 수 있습니다. 예를 들어, Spring Data, Spring Security, Spring Web 등 다양한 서브모듈을 통해 애플리케이션의 요구에 맞는 기능을 추가할 수 있습니다.

4. 트랜잭션 관리

  • Spring은 선언적 트랜잭션 관리 기능을 제공하여, 비즈니스 로직에서 트랜잭션을 쉽게 관리할 수 있도록 도와줍니다. @Transactional 어노테이션을 사용해 메서드 또는 클래스 수준에서 트랜잭션을 자동으로 관리할 수 있습니다.

 

@Transactional
public void transferMoney(Account fromAccount, Account toAccount, double amount) {
    // 돈 이체 처리 로직
}

 

5. Spring MVC

  • Spring MVC는 웹 애플리케이션을 위한 모델-뷰-컨트롤러(MVC) 프레임워크입니다. 요청을 처리하고, 뷰를 렌더링하며, 사용자 인터페이스를 구축하는 데 필요한 기능을 제공합니다.
  • Spring MVC는 RESTful API 개발도 지원하여, REST 기반의 웹 서비스를 쉽게 구축할 수 있습니다.
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}

 

6. Spring Boot

  • Spring Boot는 Spring Framework의 복잡성을 줄여주는 자동 설정내장 서버를 제공합니다. 이를 통해 설정을 최소화하고, 독립 실행형 애플리케이션을 쉽게 만들 수 있습니다. Spring Boot는 Spring Cloud와 결합하여 마이크로서비스 아키텍처를 쉽게 구현할 수 있습니다.
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

7. Spring Security

  • Spring Security는 인증(Authentication)과 권한 부여(Authorization)를 처리하는 프레임워크로, 웹 애플리케이션에 필요한 보안 기능을 제공합니다. 로그인, 로그아웃, CSRF 방어, 권한 체크 등의 기능을 손쉽게 설정할 수 있습니다.

 

8. Spring Data

  • Spring Data는 데이터베이스와의 상호작용을 단순화하는 데 도움을 줍니다. 특히 JPA(Java Persistence API)를 사용하여 관계형 데이터베이스와의 통합을 돕고, CRUD 작업을 자동화합니다.
public interface PersonRepository extends JpaRepository<Person, Long> {
    List<Person> findByLastName(String lastName);
}

 

 

9. Spring Batch

  • Spring Batch는 대용량 데이터 처리 및 배치 작업을 처리하기 위한 프레임워크입니다. 데이터를 읽고, 처리하고, 쓰는 작업을 효율적으로 처리할 수 있도록 도와줍니다.

 

10. Spring Cloud

  • Spring Cloud는 마이크로서비스 아키텍처를 지원하는 다양한 도구를 제공합니다. 예를 들어, 서비스 디스커버리, 분산 설정 관리, API 게이트웨이, 회로 차단기 패턴 등을 지원합니다.

 

IoC (Inversion of Control) 객체 생성 및 의존성 관리(Dependency Injection)를 통해 애플리케이션의 결합도를 낮추고 유연성 제공
DI (Dependency Injection) 객체 간의 의존 관계를 자동으로 주입하여 코드의 유지보수성과 테스트 용이성 향상
AOP (Aspect-Oriented Programming) 공통 관심사를 분리하여 코드 재사용성을 높이고 비즈니스 로직과 분리된 처리 가능 (예: 트랜잭션 관리, 로깅)
모듈화 필요한 모듈만 선택적으로 사용할 수 있어 애플리케이션의 크기와 복잡도를 줄임
Spring MVC 웹 애플리케이션을 위한 모델-뷰-컨트롤러(MVC) 패턴 지원, RESTful API 개발 가능
Spring Boot 자동 설정과 내장 서버를 제공하여 설정을 간소화하고 독립 실행형 애플리케이션을 쉽게 만들 수 있음
트랜잭션 관리 선언적 트랜잭션 관리 지원, @Transactional 어노테이션을 통해 트랜잭션을 쉽게 처리
Spring Security 인증 및 권한 부여를 처리하며, 웹 애플리케이션의 보안을 쉽게 설정할 수 있음
Spring Data 데이터베이스와의 상호작용을 단순화하며, JPA, Hibernate와의 통합을 지원
Spring Batch 대용량 배치 작업 처리 지원, 데이터를 읽고 쓰는 배치 작업을 효율적으로 처리할 수 있음
Spring Cloud 마이크로서비스 아키텍처 지원, 서비스 디스커버리, API 게이트웨이 등 분산 시스템 구축에 필요한 도구 제공
강력한 커뮤니티 활발한 오픈소스 프로젝트로, 커뮤니티와의 상호작용을 통해 지속적으로 발전
유연성 다양한 기술과 쉽게 통합 가능, 애플리케이션 아키텍처에 맞게 확장 가능

 

[4]  Spring Framework 사용 시 장점:

  • 유연성: 다양한 기술과 쉽게 통합할 수 있으며, 다양한 애플리케이션 아키텍처에 맞게 확장 가능합니다.
  • 모듈화: 필요한 기능만 선택하여 사용할 수 있습니다.
  • 표준화: 널리 사용되는 표준을 기반으로 구축되어 있으며, 많은 라이브러리 및 프레임워크와 호환됩니다.
  • 강력한 커뮤니티: Spring은 활발한 오픈소스 프로젝트로, 커뮤니티와의 상호작용을 통해 지속적으로 발전합니다.

 

[5]  Spring Framework 사용 시 고려할 점:

  • 학습 곡선: 많은 기능이 포함되어 있어 초보자에게는 다소 복잡할 수 있습니다.
  • 구성 파일: 초기 설정이 다소 복잡할 수 있지만, Spring Boot로 자동 설정을 사용할 수 있습니다.

Spring Framework는 엔터프라이즈 애플리케이션을 위한 강력한 솔루션을 제공하며, 다양한 기능을 활용하여 효율적인 개발을 할 수 있도록 돕습니다.

 

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10
[Spring] 웹 개발의 흐름  (1) 2024.12.05
[Spring] Spring 웹 애플리케이션 계층 구조  (2) 2024.11.13
[Spring] 스프링 웹 개발 기초  (0) 2024.11.13

웹 프로젝트 개발 단계 (쇼핑몰 프로젝트 예시)


1. 프로젝트 계획 및 기획

  1. 목표 정의:
    • 쇼핑몰의 주요 기능과 목표를 정의합니다.
      (예: 상품 검색, 장바구니, 결제 시스템 등)
  2. 사용자 요구사항 분석:
    • 고객이 원하는 기능과 서비스를 파악합니다.
      (예: 반응형 디자인, 빠른 검색, 다양한 결제 수단 제공)
  3. 와이어프레임 작성:
    • 웹사이트의 레이아웃 및 사용자 인터페이스를 설계합니다.
      (Figma, Sketch 등을 활용)
  4. 기술 스택 결정:
    • 사용할 기술 스택을 선정합니다:
      • 프론트엔드: React
      • 백엔드: NestJS
      • 데이터베이스: RDS MySQL
      • 클라우드: AWS (EC2, S3, RDS, Nginx)
      • CI/CD: GitLab

2. 개발 환경 설정

  1. GitLab 프로젝트 생성:
    • 프론트엔드와 백엔드 코드를 저장할 레포지토리를 생성.
  2. AWS 계정 설정:
    • 프로젝트에 필요한 AWS 서비스(EC2, S3, RDS 등)를 활성화하고 설정.
  3. 배포 환경 준비:
    • EC2 인스턴스 생성: 프론트엔드 및 백엔드 서버를 배포할 환경 생성.
    • Nginx 설정: 로드 밸런싱, SSL 인증서를 설정하여 보안 및 성능 향상.

3. 초기 배포 및 CI/CD 파이프라인 구축

  1. 초기 프로젝트 설정:
    • 프론트엔드와 백엔드의 기본 폴더 구조 및 빌드 스크립트를 작성.
  2. 배포 환경 설정:
    • EC2 인스턴스에서 실행할 환경 구성(Nginx, Node.js 등 설치).
  3. GitLab CI/CD 구성:
    • .gitlab-ci.yml 파일 작성하여 빌드, 테스트, 배포를 자동화:
      stages:
        - build
        - test
        - deploy
      
    • 프론트엔드와 백엔드에 대한 파이프라인 단계 정의.
  4. 초기 테스트 및 배포:
    • 첫 번째 코드를 푸시하고 파이프라인이 정상적으로 동작하는지 확인.

4. 프론트엔드 개발

  1. React로 웹사이트 컴포넌트 작성:
    • 주요 페이지 개발:
      • 메인 페이지
      • 상품 목록
      • 상세 페이지
      • 장바구니
      • 결제 페이지
  2. CSS 및 디자인 작업:
    • 반응형 디자인 및 사용자 친화적인 인터페이스 구현.
  3. API 연동:
    • Axios 등을 사용해 백엔드와의 통신을 구현.

5. 백엔드 개발

  1. NestJS로 서버 구축:
    • REST API 또는 GraphQL로 웹사이트 기능 지원:
      • 상품 CRUD API
      • 회원 관리 API
      • 주문 관리 API
  2. RDS MySQL 데이터베이스 설계:
    • ERD 설계 후 데이터베이스 생성:
      CREATE TABLE products (
          id INT AUTO_INCREMENT PRIMARY KEY,
          name VARCHAR(255),
          price DECIMAL(10, 2),
          stock INT
      );
      
  3. 서비스 로직 구현:
    • 데이터베이스와 상호작용하는 서비스 계층 개발.

6. 코드 리뷰 및 협업

  1. GitLab 머지 리퀘스트:
    • 팀원 간 코드 리뷰와 피드백 진행.
  2. 이슈 트래커 활용:
    • GitLab 이슈 트래커로 버그 및 기능 개선사항 관리.
  3. 마일스톤 설정:
    • 주요 목표와 일정 관리.

7. 테스트 및 최적화

  1. 테스트:
    • 단위 테스트: 각 컴포넌트 및 API의 동작 확인.
    • 통합 테스트: 전체 시스템이 제대로 동작하는지 확인.
    • 부하 테스트: 많은 사용자가 동시에 접근할 경우의 성능 확인.
  2. 최적화 작업:
    • 이미지 최적화, CSS/JS 파일 압축.
    • 캐싱 설정 및 코드 개선으로 로딩 속도 향상.

8. 프로젝트 완료 및 유지보수

  1. 웹사이트 공식 출시:
    • 모든 테스트 완료 후 사용자들에게 공개.
  2. 사용자 피드백 수집:
    • 실시간으로 사용자 피드백을 수집하여 개선사항을 반영.
  3. 지속적 유지보수:
    • 새로운 기능 추가, 버그 수정, 시스템 안정성 개선.

전체 개발 단계 요약

  1. 기획 및 설계: 목표 정의, 와이어프레임 작성, 기술 스택 결정.
  2. 환경 설정: GitLab, AWS, 배포 환경 구성.
  3. CI/CD 구축: 자동화된 빌드, 테스트, 배포 설정.
  4. 프론트엔드 개발: React로 사용자 인터페이스 개발.
  5. 백엔드 개발: NestJS와 RDS MySQL로 서버 및 데이터베이스 구축.
  6. 코드 리뷰 및 협업: GitLab 머지 리퀘스트 및 이슈 관리.
  7. 테스트 및 최적화: 단위/통합 테스트, 성능 최적화.
  8. 출시 및 유지보수: 공식 릴리스 및 지속적 개선.

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10
[Spring] Spring Framework  (2) 2024.12.09
[Spring] Spring 웹 애플리케이션 계층 구조  (2) 2024.11.13
[Spring] 스프링 웹 개발 기초  (0) 2024.11.13

열거형 ( Enum )

📌 상수들의 집합을 정의하는 자료형입니다.

  • 일반적으로 정수나 문자열로 정의할 수 있는 여러 상수들을 열거형을 사용하여 보다 의미 있게 묶을 수 있습니다.
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    // 0      1       2             3        4         5       6
}

 

  • 열거형을 사용하면 자동적으로 0부터 시작하는 정수값이 할당됩니다
class Card{
	enum Kind { A, B, C }
    enum Value { test1, test2 }
    
    if(Card.Kind.A==Card.Value.test1) {} // 이거 타입이 달라 비교불가 에러난다.

}
  • 열거형으로 상수를 정의한 경우 값을 비교하기 전에 타입을 먼저 맞춰본다. 즉, 설령 값이 같아도 타입이 다르면 에러가 난다.

 

열거형의 특징

  • 상수값을 그룹화하여 코드의 가독성을 높입니다.
  • 타입 안전을 보장하여 잘못된 값을 사용할 가능성을 줄입니다.
  • 각 상수에 (예: 정수, 문자열 등)을 할당할 수 있으며, 그 값을 활용할 수 있습니다.
  • equals()가 아닌 '=='으로 비교가 가능하다

 

열거형(Enum)에는 맴버(필드, 메서드 등)를 추가할 수 있습니다. 각 열거형 상수는 고유한 상태동작을 가질 수 있도록 설계할 수 있습니다. 이를 통해 열거형 상수마다 다른 값을 저장하거나 다른 행동을 할 수 있습니다.

 

열거형에 필드, 생성자, 메서드 추가하기

  1. 필드 추가: 열거형 상수에 추가적인 값을 할당할 수 있습니다.
  2. 생성자 추가: 열거형 상수를 초기화할 때 사용하는 생성자를 추가합니다.
  3. 메서드 추가: 각 열거형 상수가 자신의 동작을 정의할 수 있는 메서드를 추가합니다.
public enum Day {
    MONDAY(1, "Start of the work week"),
    TUESDAY(2, "Second day of the work week"),
    WEDNESDAY(3, "Middle of the work week"),
    THURSDAY(4, "Almost there"),
    FRIDAY(5, "Last day of the work week"),
    SATURDAY(6, "Weekend!"),
    SUNDAY(7, "Weekend!");

    private final int dayNumber;   // 필드: 요일의 숫자
    private final String message;  // 필드: 요일에 대한 메시지

    // 생성자: 열거형 상수마다 값 초기화
    Day(int dayNumber, String message) {
        this.dayNumber = dayNumber;
        this.message = message;
    }

    // getter 메서드
    public int getDayNumber() {
        return dayNumber;
    }

    public String getMessage() {
        return message;
    }

    // 특정 동작을 정의하는 메서드
    public void printDetails() {
        System.out.println(dayNumber + ": " + message);
    }
}

public class Main {
    public static void main(String[] args) {
        Day today = Day.MONDAY;
        System.out.println(today.getDayNumber());  // 출력: 1
        System.out.println(today.getMessage());    // 출력: Start of the work week
        today.printDetails();  // 출력: 1: Start of the work week
    }
}

 

더보기

설명

  1. 필드: dayNumber와 message는 각 요일에 대한 추가적인 정보를 저장하는 필드입니다.
  2. 생성자: Day(int dayNumber, String message) 생성자는 열거형 상수에 값(숫자와 메시지)을 할당합니다.
  3. 메서드:
    • getDayNumber()와 getMessage()는 각 요일의 숫자와 메시지를 반환하는 메서드입니다.
    • printDetails()는 각 요일의 정보를 출력하는 메서드입니다

 

상속 관계에서의 열거형 사용

열거형은 다른 클래스를 상속할 수 없지만, 인터페이스를 구현할 수 있습니다. 예를 들어, 모든 연산을 Operation 인터페이스로 정의하고, 각 열거형에서 apply 메서드를 구현할 수 있습니다.

public interface Operation {
    double apply(double x, double y);
}

public enum MathOperation implements Operation {
    ADD {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    SUBTRACT {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    };

    // 추가 메서드를 열거형에 정의할 수도 있음
}

 

 

 

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] JAVA 웹 기술의 역사  (0) 2024.12.12
[JAVA] HASH란 무엇인가  (1) 2024.11.21
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13

HASH

📌 입력 데이터를 고정된 크기의 값으로 변환하는 함수 또는 그 과정을 의미합니다

    • 다른 말로는 해시 값, 해시 코드, 체크섬 이라고 합니다.
    • key와 value 쌍으로 데이터를 저장하며, 키를 해시 함수를 통해 고유한 인덱스에 대응시켜 빠르게 값을 조회합니다.

 

  •  중복 허용을 안함, 순서 유지 안함, 빠른 검색, 삭제 가능
  • 해시 함수를 통해 시간복잡도가 1(반드시 1은 아닙니다. 충돌할 경우 달라지기도 합니다.) 한번에 정보의 위치를 찾을 수 있는 자료구조형태(해시 테이블)를 제작하는 과정
  • 꼭 주소로 구현할 필요는 없다 
  • 왼쪽의 index/data table의 경우 이차원 배열로 map은 아니다.
  • map으로 만들경우 메서드 차제적으로 for문으로 index값을 일일히 찾을 필요 없이 바로 index(key)를 찾을 수 있다.
  • 그래서 시간복잡도가 1으로 속도 개선에 큰 장점을 가지게 된다.
  • 위 그림은 엄밀히 말해, 에러난것, 키값이 중복되고 있다.
  • 2번째는 map이 반드시 주소를 이용해 구현되는 것은 아니지만 주소를 이용할 경우, 삭제가 편하다
  • 배열의 경우 삭제를 할 경우, 한번 복사를 하고 삭제를 하다보니 속도가 느리다. 다만 map의 경우 연결된 주소만 끊어버리면 삭제가 바로 진행되기에 삭제도 빠르다.
  • 여기서 중요한게, 오른쪽의 Map은 HASH MAP이라 불러야 한다. MAP은 자료구조

 

해시의 사용 사례

  1. 해시 테이블(Hash Table):
    • 키-값 쌍 데이터를 저장하고 검색할 때 사용. (예: Java의 HashMap, Python의 dict)
  2. 데이터베이스:
    • 데이터베이스에서 인덱스를 생성하거나 검색 속도를 높이기 위해 사용.
  3. 캐싱(Caching):
    • 자주 사용하는 데이터를 해시를 통해 빠르게 검색.
  4. 암호화 및 보안:
    • 비밀번호 저장, 데이터 무결성 검증 등에 사용. (예: SHA-256, MD5)
  5. 파일 검색:
    • 파일의 고유한 해시 값을 이용하여 중복 파일을 찾거나 무결성을 확인.

장점

  1. 빠른 검색 속도 (O(1))
    • 해시 테이블을 사용하면 데이터를 저장하거나 검색하는 데 평균적으로 **O(1)**의 시간 복잡도를 가짐.
      예: 키를 기반으로 바로 접근하므로 순차 탐색이 필요 없음.
  2. 효율적인 데이터 저장 및 관리
    • 다양한 크기의 데이터(문자열, 숫자 등)를 간단한 키-값 구조로 저장할 수 있음.
    • 해시 함수를 통해 고정된 크기의 배열에 데이터를 저장해 메모리 사용을 최적화함.
  3. 충돌 해결 기법의 다양성
    • 체이닝(Chaining), 오픈 어드레싱(Open Addressing) 등의 충돌 해결 기법으로 충돌 발생 시에도 효율적으로 처리 가능.
  4. 유연한 데이터 처리
    • 문자열, 숫자, 객체 등 다양한 유형의 데이터를 처리할 수 있어 범용적임.
      예: Java의 HashMap, Python의 dict처럼 키-값 쌍을 쉽게 저장하고 검색.
  5. 확장성
    • 데이터베이스, 캐싱 시스템, 파일 검색 등에서 널리 사용되며, 다양한 응용 가능.
  6. 보안 기능
    • 해시 알고리즘(SHA-256, MD5 등)은 암호화, 데이터 무결성 검증 등에 사용됨.

단점

  1. 충돌(Collision) 문제
    • 서로 다른 키가 같은 해시 값을 가질 경우 성능이 저하될 수 있음.
    • 충돌 해결 기법을 적용해야 하며, 이로 인해 성능이 떨어질 수 있음.
      • 예: 체이닝 사용 시, 연결 리스트를 순차적으로 탐색해야 하는 경우 발생.
  2. 메모리 낭비 가능성
    • 해시 테이블은 초기 크기를 고정하거나 동적으로 조정하는데, 너무 큰 크기를 할당하면 메모리 낭비가 발생.
    • 너무 작은 크기일 경우 충돌이 잦아져 성능 저하 가능.
  3. 정렬되지 않은 데이터
    • 해시 테이블은 키-값을 저장하는 구조이므로 데이터가 정렬되지 않음.
    • 정렬된 데이터가 필요하면 별도의 작업 필요.
  4. 해시 함수 설계의 중요성
    • 해시 함수가 데이터를 균등하게 분배하지 못하면 충돌이 증가해 성능이 저하될 수 있음.
      • 예: 키 값이 편향된 데이터(연속된 숫자 등)를 처리할 때.
  5. 해시 함수의 연산 비용
    • 복잡한 해시 함수는 연산 비용이 증가할 수 있어 작은 데이터에서는 오히려 비효율적.
  6. 재해시(Rehashing) 비용
    • 테이블이 가득 차거나 특정 임계치를 초과할 경우 크기를 늘려야 하며, 이 과정에서 모든 데이터를 다시 해싱해야 함.
    • 이는 큰 데이터셋에서 성능에 영향을 미칠 수 있음.
  7. 데이터 삭제 시 문제
    • 오픈 어드레싱을 사용하는 경우, 데이터를 삭제하면 그 자리의 값이 비워지면서 연결된 데이터 검색에 영향을 줄 수 있음.

 

 

해시 함수 ( Hash Function )

📌 입력받은 데이터를 해시 값으로 출력시키는 알고리즘

  • 알고리즘의 형태는 다양합니다. 목적에 맞게 다양하게 설계되고 유용하게 사용됩니다.
  • 자료구조, 캐시, 검색, 에러 검출, 함도 등 유용하게 많이 사용됩니다.
// 해시 함수 예시
Integer hashFunction(String key) {
	
    return (int)(key.charAt(0)) % 10;
}

 

  • 위의 그림을 코드로 만든 것, 이 경우 위 그림처럼 return으로 key값을 반환해 준다.

해시 함수의 특징

  • 일관성: 동일한 입력값에 대해 항상 동일한 해시 값을 반환.
  • 고른 분배: 입력값이 다양해도 출력값이 균일하게 분포.
  • 빠른 계산: 빠른 시간 안에 값을 계산.
  • 충돌 최소화: 서로 다른 입력값이 동일한 해시 값을 가지는 확률을 낮춤.

 

 

해시 테이블 ( Hash Table )

📌 키와 값을 함께 저장해 둔 데이터 구조

  • 아래와 같이 행, 열로 구성된 표에 저장되는 것과 유사합니다.
  • 다만 테이블에 데이터를 저장할 때 순서는 무작위로 지정되어 저장됩니다.
  • 그로인해 중간에 여유 공간이 발생할 수 있습니다.
  • 해시 테이블은 map 형태의 자료구조입니다.

 

  • 하나의 주소를 갖는 파일 영역을 Bucket, 한개의 레코드를 저장할 수 있는 공간을 Slot이라 말합니다.

 

 

해싱 ( Hashing )

📌 데이터를 해시 함수를 이용해 고유한 *해시 값(Hash Value)*으로 변환하는 과정

  • 변환된 해시 값은 위 예시처럼 데이터의 저장 위치를 결정하거나, 데이터 검색 시 활용됩니다.
  • 해싱을 잘못해서 key가 중복되면 충돌이 발생할 수 있습니다.

 

 

충돌 (Collision)

📌 해시 함수로 계산된 key값이 곂치는 경우, 동일한 값을 인덱스에 저장하려 할 때 발생합니다.

 

왜 충돌이 발생하는가?

  1. 해시 테이블 크기의 한계:
    • 해시 값의 범위(테이블 크기)가 유한하기 때문에, 입력값(키)이 다양해도 모든 값을 고유한 해시 값으로 매핑할 수 없음.
    • 예: 테이블 크기가 10이면, hash(21) = 1, hash(11) = 1처럼 서로 다른 키가 동일한 인덱스에 매핑될 수 있음.
  2. 해시 함수의 설계 문제:
    • 해시 함수가 키를 균일하게 분산하지 못하거나 특정 키 값 패턴에 대해 편향된 결과를 생성하면 충돌 발생 가능성이 증가.

충돌 해결 방법

1. 체이닝 (Chaining)

  • 방식: 충돌이 발생한 동일한 해시 값의 데이터를 연결 리스트(Linked List) 형태로 저장.
  • 동작 과정:
    • 충돌 시, 같은 인덱스에 새로운 데이터를 리스트의 형태로 추가.
    • 데이터를 검색할 때 해당 인덱스의 리스트를 순회하며 원하는 데이터를 찾음.
  • 장점:
    • 테이블 크기를 초과하는 데이터도 저장 가능.
    • 테이블의 크기를 크게 증가시키지 않아도 됨.
  • 단점:
    • 충돌이 많아지면 리스트가 길어져 검색 시간이 O(n)으로 증가.

 

2. 오픈 어드레싱 (Open Addressing)

  • 방식: 충돌이 발생하면 다른 빈 슬롯(인덱스)을 찾아 데이터를 저장.
  • 기법:
    1. 선형 탐사 (Linear Probing):
      • 충돌이 발생하면 고정된 간격(보통 1)을 더해 다음 인덱스를 탐색.
      • 예: hash(21) = 1에서 충돌 시, index = 2, index = 3 탐색.
      장점: 구현이 간단.
      단점: 충돌이 많아지면 클러스터링(연속된 충돌) 발생 가능.
    2. 이차 탐사 (Quadratic Probing):
      • 충돌 시 인덱스 이동 간격을 제곱 형태로 증가.
      • 예: index = 1, index = 1 + 1^2, index = 1 + 2^2.
      장점: 클러스터링 문제를 어느 정도 해결.
      단점: 테이블의 크기가 소수(Prime)여야 효율적.
    3. 이중 해싱 (Double Hashing):
      • 충돌 시 다른 해시 함수를 사용해 새로운 위치를 계산.
      • 예: hash1(21) = 1, 충돌 발생 시 hash2(21) = 3.
      장점: 충돌 분산 효과가 높음.
      단점: 해시 함수 설계가 복잡.


충돌 해결 기법의 선택

  • 데이터의 크기와 성격에 따라 충돌 해결 기법이 달라짐.
    • 데이터가 많고 빈도가 높은 경우 → 체이닝 적합.
    • 메모리가 제한적일 경우 → 오픈 어드레싱 적합.

충돌 해결 방법 비교

 

 

 

해시셋 ( Hashset )

📌 Set 인터페이스를 구현한 클래스 중 하나로, 데이터 중복, 순서를 유지하지 않는 집합입니다.

  • HashMap, HashSet 등 해시 기반 컬렉션입니다.
  • 컬렉션 = java에서 데이터를 효율적으로 저장하고 조작하기 위한 객체들의 그룹(container)을 말합니다.

 

HashSet의 작동 원리

HashSet은 내부적으로 HashMap을 사용해 데이터를 저장합니다.
데이터를 저장할 때, 키(key)와 값(value) 중에서 값(value)은 더미 객체로 설정되고, 키(key) 저장됩니다.

  1. 저장: add(E e) 메서드를 호출하면, 입력 데이터의 해시 코드(hashCode)를 계산해 해시 테이블의 위치를 결정.
  2. 중복 확인: equals() 메서드를 사용해 중복 여부를 판단.
    • 같은 해시 코드를 가진 데이터라도 equals() 메서드가 true를 반환하면 중복으로 간주.
import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        // HashSet 선언
        HashSet<String> set = new HashSet<>();

        // 데이터 추가
        set.add("Apple");
        set.add("Banana");
        set.add("Cherry");

        // 중복 추가 시 무시
        set.add("Apple");

        // 출력
        System.out.println(set);  // 출력: [Banana, Apple, Cherry] (순서가 랜덤)

        // 특정 값 포함 여부 확인
        System.out.println(set.contains("Banana"));  // true

        // 데이터 삭제
        set.remove("Cherry");

        // 크기 확인
        System.out.println(set.size());  // 2
    }
}

 

HashSet의 주요 메서드

  • set의 주요 메서드를 계승하고 있습니다.

 

HashSet과 다른 Set 구현체 비교

 

 

Set 인터페이

 🐳 중복을 허용하지 않는 집합(Collection)을 나타내는 인터페이스입니다.

 

Set의 주요 특징

  1. 중복 허용 안 함
    • 동일한 요소를 여러 번 저장할 수 없습니다.
    • 예: {1, 2, 2, 3} -> {1, 2, 3}
  2. 순서 유지 안 함
    • 데이터의 저장 순서를 보장하지 않습니다.
    • 일부 구현체(LinkedHashSet)는 예외적으로 삽입 순서를 유지.
  3. null 값
    • null 값을 허용하지만, 한 번만 저장 가능.
  4. 기본 연산 지원
    • 집합 연산을 쉽게 구현할 수 있습니다(교집합, 합집합, 차집합).

 

 

해시맵 ( Hashmap )

📌 Map 인터페이스의 구현체로, 키(Key)와 값(Value)의 쌍으로 데이터를 저장합니다.

  • 각각의 키는 고유하며, 각 키에 대해 값을 매핑합니다. HashMap은 해시 테이블을 사용하여 데이터를 빠르게 검색하고 관리할 수 있도록 합니다.
  • HashMap, HashSet 등 해시 기반 컬렉션입니다.
  • 컬렉션 = java에서 데이터를 효율적으로 저장하고 조작하기 위한 객체들의 그룹(container)을 말합니다.

 

HashMap의 주요 특징

  1. 키-값 쌍(Key-Value Pair)
    • HashMap은 데이터를 **키(Key)**와 **값(Value)**의 쌍으로 저장합니다.
    • 키는 중복될 수 없고, 값은 중복될 수 있습니다.
    • 예: map.put("Alice", 25)에서 "Alice"는 키, 25는 값입니다.
  2. 중복 키를 허용하지 않음
    • 동일한 키로 여러 번 값을 넣으면, 기존의 값이 새로운 값으로 덮어씌워집니다.
    • 예: map.put("Alice", 25) 이후 map.put("Alice", 30)을 호출하면 "Alice"의 값은 30으로 변경됩니다.
  3. 순서 보장하지 않음
    • HashMap은 저장된 데이터를 순서대로 유지하지 않습니다.
      순서가 중요한 경우, LinkedHashMap을 사용할 수 있습니다.
  4. 빠른 검색 속도
    • HashMap은 해시 테이블을 사용하여 **O(1)**의 평균 시간 복잡도로 검색, 삽입, 삭제가 가능합니다.
    • 해시 충돌을 처리하는 방법이 있기 때문에 최악의 경우에도 성능이 크게 저하되지는 않습니다.
  5. null 값 허용
    • HashMap은 한 개의 null 키여러 개의 null 값을 허용합니다.
    • 예: map.put(null, "some value") 또는 map.put("some key", null)이 가능합니다.

 

import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        // HashMap 생성
        HashMap<String, Integer> map = new HashMap<>();

        // 데이터 추가
        map.put("Alice", 25);  // "Alice" 키에 25 값 저장
        map.put("Bob", 30);    // "Bob" 키에 30 값 저장
        map.put("Charlie", 35); // "Charlie" 키에 35 값 저장

        // 값 출력
        System.out.println("Alice's age: " + map.get("Alice")); // 출력: Alice's age: 25

        // 존재 여부 확인
        System.out.println("Contains 'Bob'? " + map.containsKey("Bob")); // 출력: Contains 'Bob'? true

        // 모든 키와 값 출력
        for (String key : map.keySet()) {
            System.out.println(key + ": " + map.get(key));
        }
        // 출력:
        // Alice: 25
        // Bob: 30
        // Charlie: 35

        // 키 삭제
        map.remove("Bob");  // "Bob" 삭제
        System.out.println("Contains 'Bob' after removal? " + map.containsKey("Bob")); // 출력: false
    }
}

 

HashMap의 성능

  • 검색, 삽입, 삭제: 평균적으로 O(1)의 시간 복잡도를 가집니다.
    • 해시 테이블을 사용하기 때문에, 키를 해시 함수로 변환하여 빠르게 위치를 찾습니다.
  • 충돌 처리: 여러 항목이 동일한 해시 값을 갖는 경우 체이닝(Chaining) 또는 오픈 어드레싱(Open Addressing) 방식으로 충돌을 처리합니다.
  • 최악의 경우: 해시 충돌이 많아지면, 검색/삽입/삭제가 O(n)으로 성능이 저하될 수 있습니다. 이를 방지하기 위해 적절한 해시 함수충돌 처리 전략이 중요합니다.

 


 

HashMap의 장점과 단점

장점

  1. 빠른 데이터 검색: 해시 테이블을 사용하여 평균적으로 O(1)의 시간 복잡도로 데이터를 빠르게 조회, 삽입, 삭제할 수 있습니다.
  2. 키를 통한 데이터 접근: 키를 사용하여 데이터를 빠르고 효율적으로 관리할 수 있습니다.
  3. null 허용: null 값을 키와 값에 사용할 수 있어 유연성 제공.

단점

  1. 순서 보장 없음: HashMap은 순서가 보장되지 않으므로, 데이터가 저장된 순서를 유지하려면 LinkedHashMap을 사용해야 합니다.
  2. 메모리 사용량: 해시 테이블은 추가 메모리를 사용하므로, 작은 데이터 세트에서는 오히려 비효율적일 수 있습니다.
  3. 충돌 처리 필요: 해시 충돌이 발생하면 성능이 저하될 수 있으므로, 충돌 처리 방법에 대한 고려가 필요합니다.

 


 

HashMap의 주요 메서드

 

 

HashCode( ) 메서드

📌Object 클래스에 정의된 메서드로, 객체를 식별하기 위한 정수 값(해시 코드)을 반환합니다. 이 값은 객체를 빠르게 검색하거나 비교할 때 사용됩니다.

 

hashCode()의 특징

  1. 정수 값 반환
    • 객체의 해시 코드를 나타내는 정수를 반환.
    • 예: 123456, -987654, 등.
  2. 같은 객체는 같은 해시 코드
    • 동일한 객체(equals() 메서드로 비교 시 true)라면 동일한 hashCode를 반환해야 함.
    • 하지만, 서로 다른 객체가 같은 해시 코드를 가질 수도 있음(해시 충돌).
  3. 기본 구현
    • Object 클래스에서 기본적으로 정의되어 있으며, 객체의 메모리 주소를 기반으로 해시 코드를 생성.
  4. 재정의 가능
    • 개발자가 특정 로직에 따라 hashCode()를 재정의 가능. 주로 equals() 메서드와 함께 재정의.
public class HashCodeExample {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        System.out.println(obj1.hashCode()); // 출력: (예) 366712642
        System.out.println(obj2.hashCode()); // 출력: (예) 1829164700
    }
}
  • obj1과 obj2는 서로 다른 객체이므로 해시 코드가 다름.

 

hashCode()와 equals()의 관계

  1. 규칙:
    • equals()가 true인 두 객체는 항상 같은 hashCode() 값을 가져야 함.
    • 그러나, 같은 hashCode() 값을 가진 객체가 **반드시 equals()가 true**는 아님.
  2. 의미:
    • hashCode()는 빠른 검색을 위한 값이고,
    • equals()는 객체의 논리적 동등성을 비교하는 메서드.

재정의된 hashCode()와 equals()

Java에서 hashCode()와 equals()는 함께 재정의해야 합니다.
HashSet이나 HashMap 같은 컬렉션은 hashCode()와 equals()의 일관성을 가정하여 동작합니다.

 

 

< 예제: 커스텀 클래스에서 hashCode()와 equals() 재정의 >

import java.util.Objects;

class Person {
    String name;
    int age;

    // 생성자
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // hashCode() 재정의
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    // equals() 재정의
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

public class HashCodeEqualsExample {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);

        // 해시 코드가 동일함
        System.out.println(p1.hashCode() == p2.hashCode()); // true

        // equals() 비교
        System.out.println(p1.equals(p2)); // true
    }
}

실행 결과:

  • p1과 p2는 같은 name과 age를 가지므로 equals()와 hashCode() 모두 동일하게 동작.

 


hashCode()와 컬렉션

  • HashSet, HashMap 등은 hashCode()를 사용해 데이터를 관리.
  • 데이터를 추가할 때, 먼저 hashCode() 값으로 버킷(bucket)을 결정하고,
    동일한 버킷에 여러 데이터가 들어올 경우 **equals()**로 중복 여부를 확인.

< 예제: HashSet에서 hashCode()와 equals() >

import java.util.HashSet;

class Student {
    int id;
    String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        return id; // ID를 기반으로 해시 코드 생성
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return id == student.id && name.equals(student.name);
    }
}

public class HashSetExample {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();

        set.add(new Student(1, "Alice"));
        set.add(new Student(1, "Alice")); // 중복 데이터
        set.add(new Student(2, "Bob"));

        System.out.println(set.size()); // 출력: 2
    }
}

 


hashCode() 사용 시 주의점

  1. equals와 일관성 유지
    • equals()가 true면 hashCode()도 같아야 함.
  2. 해시 충돌
    • 서로 다른 객체가 같은 hashCode()를 가질 경우, 충돌 발생.
    • 충돌이 많아지면 해시 기반 자료구조의 성능 저하.
  3. 재정의 필요성
    • 사용자 정의 클래스에서는 반드시 hashCode()와 equals()를 함께 재정의해야 올바르게 동작.

 

 

equals메서드 정의시 항상 hashcode()를 함께 정의해야하는 이유

 

Java의 기본 규약

Java에서 equals()와 hashCode()의 관계를 다음과 같이 규정합니다:

  1. equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 가져야 한다.
    • 동일한 객체를 나타내므로 동일한 해시코드가 필요.
  2. hashCode()가 같다고 해서 equals()가 반드시 true는 아니다.
    • 다른 객체들이 해시 충돌을 일으킬 수 있음.

 

함께 정의하지 않으면 발생하는 문제

1. HashMap과 HashSet에서 데이터 검색 실패

  • HashMap과 HashSet은 내부적으로 객체를 저장할 때 먼저 hashCode()로 버킷(bucket)을 찾고, 이후 equals()로 동등성을 확인합니다.
  • 만약 equals()와 hashCode()가 일관되지 않다면, 검색, 삽입, 삭제 작업이 제대로 동작하지 않습니다.
import java.util.HashSet;

class Person {
    String name;

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return name.equals(person.name);
    }

    // hashCode()를 정의하지 않음
}

public class HashCodeEqualsTest {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));

        // 동일한 이름의 객체를 검색하려고 함
        System.out.println(set.contains(new Person("Alice"))); // false
    }
}

 

더보기

원인

  • equals()는 동일한 객체라고 판단하지만, hashCode()를 재정의하지 않았으므로 다른 해시 버킷에 저장됩니다.
  • 결과적으로 HashSet은 데이터를 찾지 못합니다.

 

< 수정 >

import java.util.Objects;

class Person {
    String name;

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name); // name 기반으로 해시코드 생성
    }
}

public class HashCodeEqualsTest {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("Alice"));

        // 동일한 이름의 객체를 검색
        System.out.println(set.contains(new Person("Alice"))); // true
    }
}

결과

  • equals()와 hashCode()를 함께 재정의하여, 동일한 이름의 객체를 올바르게 검색합니다.

 

왜 hashCode()를 함께 정의해야 하는가?

  1. 효율성:
    • hashCode()는 검색을 빠르게 하기 위해 사용됩니다.
      (equals()는 최종 확인 단계로만 호출되므로 성능 부담이 적음).
  2. 충돌 방지:
    • hashCode()가 올바르게 정의되지 않으면, 모든 데이터가 같은 해시 버킷에 저장되어 성능이 크게 저하됩니다.
      예: O(1) → O(n)
  3. 컬렉션 동작 보장:
    • HashMap, HashSet 등 해시 기반 컬렉션이 의도한 대로 작동하려면 반드시 두 메서드가 일관성 있어야 합니다.

정리

  • equals()는 객체의 논리적 동등성을 비교.
  • hashCode()는 빠른 검색과 데이터 저장을 위해 사용.
  • 두 메서드는 일관성을 유지해야 하며, 함께 재정의하지 않으면 해시 기반 자료구조가 잘못 동작하거나 성능이 저하될 수 있습니다.
    따라서 항상 equals()와 hashCode()를 함께 재정의하세요.

 

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] JAVA 웹 기술의 역사  (0) 2024.12.12
[JAVA] 열거형 ( Enum )  (0) 2024.11.22
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13

간단한 정렬 Sort

 

1. Arrays 클래스의 정렬

  • java.util.Arrays 클래스는 배열을 정렬하는 데 사용됩니다.
  • 기본적으로 오름차순으로 정렬하며, 사용자 정의 기준으로 정렬하려면 Comparator를 사용합니다.
  • 문자열의 경우 아스키코드 순 (알파벳 순)으로 오름차순 정렬되며, 한글도 가나다 순으로 정렬됩니다.
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] numbers = {5, 2, 8, 3, 1};
        Arrays.sort(numbers);
        System.out.println(Arrays.toString(numbers)); // [1, 2, 3, 5, 8]

        String[] names = {"Charlie", "Alice", "Bob"};
        Arrays.sort(names);
        System.out.println(Arrays.toString(names)); // [Alice, Bob, Charlie]

        // 사용자 정의 기준 정렬
        Arrays.sort(names, (a, b) -> b.compareTo(a)); // 내림차순
        System.out.println(Arrays.toString(names)); // [Charlie, Bob, Alice]
    }
}

 

2. Collections 클래스의 정렬

  • java.util.Collections 클래스는 리스트를 정렬할 때 사용됩니다.
  • Collections.sort()는 Comparable 인터페이스를 구현한 클래스의 자연 정렬 기준을 따르며, 필요하면 Comparator로 미리 설정된 정렬 기준을 제공합니다. (오름차순 외의 내림차순과 같은)
  • 인터페이스의 장점대로 다양한 정렬기준을 프로그래머가 직접 제시할 수 있습니다. 

 

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 3, 1);
        Collections.sort(numbers);
        System.out.println(numbers); // [1, 2, 3, 5, 8]

        // 내림차순 정렬
        Collections.sort(numbers, Collections.reverseOrder());
        System.out.println(numbers); // [8, 5, 3, 2, 1]
    }
}

 

  • 둘이 다르지만 사실, Collections.Sort() -> list.sort() 함수-> List.java.sort 메서드-> Array.sort(a Comparator) 함수
  • 위와 같은 순서로 호출하다보니 결국 array, collections 배열 모드 실상은 array.sort로 돌아간다
  • 즉 근본적으로 차이가 없다

 

+ 공식 Collections 클래스 문서

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collections.html#reverseOrder()

 

 

복잡한 정렬 Stream API

📌 Java 8 이상에서 도입된 Stream API는 컬렉션 또는 배열을 쉽게 정렬할 수 있는 기능을 제공합니다.

  • sorted() 메서드는 기본적으로 자연 정렬을 따르며, 사용자 정의 기준도 제공합니다.

 

import java.util.*;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob");

        // 오름차순 정렬
        List<String> sortedNames = names.stream()
                                        .sorted()
                                        .collect(Collectors.toList());
        System.out.println(sortedNames); // [Alice, Bob, Charlie]

        // 내림차순 정렬
        List<String> reversedNames = names.stream()
                                          .sorted(Comparator.reverseOrder())
                                          .collect(Collectors.toList());
        System.out.println(reversedNames); // [Charlie, Bob, Alice]
    }
}

 

 

복잡한 정렬 Comparator와 Comparable

📌 Java에서 객체를 정렬하기 위해 사용하는 두 가지 인터페이스입니다.

 

1. Comparable

  • 정의: 객체 자체가 "자연 정렬 순서"를 정의하도록 설계된 인터페이스.
  • 메서드: compareTo(T o) 메서드를 구현해야 합니다.
  • 사용 방법:
    • 클래스가 직접 Comparable을 구현하고, 정렬 로직을 내부에서 정의합니다.
    • 정렬 기준이 고정적이며, 클래스 설계 시 미리 정의되어야 합니다.
import java.util.*;

class Student implements Comparable<Student> {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.age, other.age); // 나이를 기준으로 오름차순 정렬
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 23),
            new Student("Bob", 21),
            new Student("Charlie", 22)
        );

        Collections.sort(students); // Comparable에 정의된 정렬 기준 사용
        System.out.println(students); // [Bob (21), Charlie (22), Alice (23)]
    }
}

 

2. Comparator

  • 정의: 객체 외부에서 정렬 기준을 정의할 수 있는 인터페이스.
  • 메서드: compare(T o1, T o2) 메서드를 구현해야 합니다.
  • 사용 방법:
    • 정렬 기준을 필요할 때마다 변경할 수 있도록 별도의 클래스를 작성하거나 람다식을 사용합니다.
    • 동일한 클래스에 대해 여러 정렬 기준을 제공할 수 있습니다.
    • 사용자 정렬 기준을 제기하면 보통 Comparator를 쓰게됩니다.
import java.util.*;

class Student {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("Alice", 23),
            new Student("Bob", 21),
            new Student("Charlie", 22)
        );

        // 이름 기준으로 정렬
        students.sort(Comparator.comparing(Student::getName));
        System.out.println(students); // [Alice (23), Bob (21), Charlie (22)]

        // 나이 기준으로 정렬
        students.sort(Comparator.comparingInt(Student::getAge));
        System.out.println(students); // [Bob (21), Charlie (22), Alice (23)]
    }
}

 

 

 

 

  • 이외에도 Guava와 같은 서드파티 라이브러리도 정렬 도구를 제공합니다.
  • 우선순위 큐, 트리셋을 사용한 데이터를 추가하면서 정렬 상태를 유지하는 정렬도 존재합니다.
  • Java의 기본 정렬은 Timsort를 기반으로 하며, 효율적이고 안정적입니다.

 

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] 열거형 ( Enum )  (0) 2024.11.22
[JAVA] HASH란 무엇인가  (1) 2024.11.21
[JAVA] 응용 정리  (1) 2024.11.15
[JAVA] NULL  (0) 2024.11.13
[JAVA] 쓰레드 & 람다 함수 & 스트림  (3) 2024.11.13

★Enum (열거형)

 

Enum의 주요 메서드

  • values(): 열거형에 정의된 모든 값을 배열로 반환.
  • valueOf(String name): 이름으로 열거형 상수를 찾음.
// 모든 값을 배열로 반환
enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class EnumValuesExample {
    public static void main(String[] args) {
        Day[] days = Day.values();
        for (Day day : days) {
            System.out.println(day);  // MONDAY, TUESDAY, ... 출력
        }
    }
}

 

사용 시 유의점

  • Enum은 상수 값을 정의하는 데 유용하지만, 값의 추가나 변경이 자주 일어날 경우 유연성에서 부족할 수 있습니다.
  • Enum을 사용할 때는 상수의 집합이 의미가 있는지, 그 값들이 변경되지 않도록 보장해야 하는 경우 적합합니다.

 

< 예제 >

enum Day {
    MONDAY("월요일"), TUESDAY("화요일"), WEDNESDAY("수요일"),
    THURSDAY("목요일"), FRIDAY("금요일"), SATURDAY("토요일"), SUNDAY("일요일");

    private String koreanName;

    Day(String koreanName) {
        this.koreanName = koreanName;
    }

    public String getKoreanName() {
        return koreanName;
    }
}

public class EnumExample {
    public static void main(String[] args) {
        Day today = Day.MONDAY;
        System.out.println(today.getKoreanName());  // 월요일 출력
    }
}

//////////////////////////////응용


enum Season {
    SPRING, SUMMER, FALL, WINTER
}

public class EnumComparisonExample {
    public static void main(String[] args) {
        Season currentSeason = Season.SUMMER;

        if (currentSeason == Season.SUMMER) {
            System.out.println("It's summer!");
        }
    }
}

 

 

★람다 (Lambda Expression)

 

 

★스트림 (Stream)

 

 

 

★제네릭 (Generics)

📌 클래스, 인터페이스, 메서드 등에서 사용하는 타입을 매개변수화하여 코드의 재사용성유연성을 높여주는 Java의 중요한 기능

제네릭의 사용 상황

  1. 컬렉션 프레임워크:
    • List, Set, Map 등에서 제네릭을 사용하여 타입을 명확히 지정함으로써 타입 안전성을 보장하고, 불필요한 캐스팅을 피할 수 있습니다.
  2. 유틸리티 클래스 작성:
    • 제네릭을 사용하면 다양한 타입에 대해 범용적으로 사용할 수 있는 클래스나 메서드를 작성할 수 있습니다. 예를 들어, Box 클래스를 사용하면 Integer, String 등 다양한 타입을 처리할 수 있습니다.
  3. 타입 제한이 필요한 경우:
    • 특정 타입이나 그 하위 타입에 대해서만 작동하도록 제네릭을 사용할 때 타입 제한을 두어, 보다 강력한 타입 안전성을 유지할 수 있습니다.
     
  4. API 설계:
    • 제네릭을 사용하면 클래스나 메서드의 재사용성을 높이기 위해, 라이브러리나 프레임워크를 설계할 때 유용합니다. 예를 들어, Java의 ArrayList는 다양한 타입을 받을 수 있도록 제네릭을 사용합니다.
public <T extends Number> void printNumber(T num) {
    System.out.println(num);
}

제네릭의 단점

  • 타입 불일치: 컴파일 타임에 타입을 지정하므로 런타임에 타입을 변경할 수 없습니다.
  • 다형성 제한: 제네릭 타입은 **원시 타입(primitive type)**을 사용할 수 없습니다. 예를 들어, int 대신 Integer와 같은 래퍼 클래스를 사용해야 합니다.
  • 복잡성: 제네릭을 사용할 때 코드가 다소 복잡해질 수 있습니다.

1. 제네릭 클래스

// 제네릭 클래스를 정의할 때 타입 파라미터 <T>를 사용
class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>();
        intBox.setItem(10);
        System.out.println(intBox.getItem());  // 10

        Box<String> strBox = new Box<>();
        strBox.setItem("Hello");
        System.out.println(strBox.getItem());  // Hello
    }
}

 

2. 제네릭 메서드

// 제네릭 메서드는 메서드 레벨에서 타입 파라미터를 지정
public class Main {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3};
        String[] strArray = {"Hello", "World"};

        printArray(intArray);  // 1 2 3
        printArray(strArray);  // Hello World
    }
}

 

3. 제네릭 인터페이스

interface Pair<K, V> {
    K getKey();
    V getValue();
}

class KeyValuePair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public KeyValuePair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        Pair<Integer, String> pair = new KeyValuePair<>(1, "One");
        System.out.println(pair.getKey() + ": " + pair.getValue());  // 1: One
    }
}

 

 

★static

📌 Java에서 클래스나 객체의 특정 구성 요소에 대한 정적(Static) 특성을 정의하는 키워드입니다. 이를 사용하면 해당 구성 요소가 클래스에 속하게 되며(객체가 아닌 클래스의 변수, 메소드 선언 가능), 객체의 인스턴스화 없이 접근할 수 있습니다.

 

 

★추상 클래스 (Abstract Class)

📌 구현되지 않은 메서드(즉, 추상 메서드)를 포함할 수 있는 클래스입니다. 추상 클래스는 직접 인스턴스를 생성할 수 없으며, 다른 클래스에서 상속받아 사용하도록 설계됩니다.

 

정의:

  • 추상 클래스abstract 키워드로 선언되며, 구현되지 않은 메서드를 포함할 수 있습니다.
  • 인스턴스를 생성할 수 없다는 특징이 있으며, 이를 상속받은 자식 클래스에서 추상 메서드를 구현해야 합니다.

사용처:

    • 공통된 속성과 동작을 여러 클래스에서 공유해야 할 때 사용합니다.
    • 공통된 기본 기능을 부분적으로 구현하고, 그 외의 구체적인 구현은 자식 클래스에서 하도록 유도할 때 유용합니다.

  • 저 인터페이스 공통된 기능을 다른 클래스들에게 강제 한다는 말은 인터페이스에서 사용된 변수, 메소드는 반드시 인터페이스를 구현할 클래스에서 반드시 구현되어야만 한다. (추상 클래스는 안해도 상관없다.)
  • 이런 일이 발생하는 이유는 객체의 청사진이 클래스라면, 클래스의 청사진이 인터페이스이기 때문

 

★인터페이스

📌 클래스의 청사진으로, 인터페이스를 구현할 클래스는 반드시 인터페이스에서 사용된 변수, 메소드를 반드시 구현해야한다.

 

  • implements 키워드를 사용하여 인터페이스를 구현할 수 있습니다. 
public class 클래스명 implements 인터페이스명 { 
			// 추상 메서드 오버라이딩
			@Override
	    public 리턴타입 메서드이름(매개변수, ...) {
			       // 실행문
	    }
}

 

  • 인터페이스 간의 상속은 implements 가 아니라 extends 키워드를 사용합니다.
public class Main extends D implements C {

    @Override
    public void a() {
        System.out.println("A");
    }

    @Override
    public void b() {
        System.out.println("B");
    }

    @Override
    void d() {
        super.d();
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a();
        main.b();
        main.d();
    }
}

interface A {
    void a();
}

interface B {
    void b();
}

interface C extends A, B {
}

class D {
    void d() {
        System.out.println("D");
    }
}

 

 

This

📌 현재 객체 자신을 참조하는 키워드로, 객체 내부에서 객체의 멤버(필드, 메서드, 생성자)를 명확히 참조하기 위해 사용됩니다.

정의 객체의 현재 인스턴스를 참조하는 키워드로, 객체 내부에서 멤버 변수와 메서드를 호출하거나 생성자 호출에 사용.
특징 ✔ 객체 자신의 주소를 나타냄
✔ 인스턴스 멤버에 접근할 때 사용
✔ 메서드나 생성자에서 주로 활용됨
✔ 정적(static) 맥락에서는 사용 불가

 

  • 위 경우 입력값 model이 객체의 model이 아닌 매개변수 model에 입력됨

 

  • 위 경우 입력값 model이 객체의 model에 입력됨
  • 매개변수, 지역 변수, 클래스 변수의 이름이 동일할 경우 구분을 위해 사용
  • this. = 현재 객체의 변수
  • this() = 현재 객체 생성자

 

final

📌 현재 클래스, 변수를 더이상 변경 불가하게 만들어 버린다. 상속도 막힌다.

 

Object

📌 Java 내 모든 클래스들의 최상위 부모 클래스, 다양한 기능 제공

 


 

★객체지향 프로그래밍

class Car {
    String model;
    String color;
    
    // 기본 생성자
    public Car(String model, String color) {
        this.model = model;
        this.color = color;
    }
    
    // gasPedal 메서드: 속도를 설정하는 메서드로 kmh 값을 speed 필드에 저장하고 반환
    public double gasPedal(double kmh) {
        speed = kmh; // 자동차의 현재 속도를 kmh 값으로 설정
        return speed; // 설정된 속도 반환
    }
}

public class Main {
    public static void main(String[] args) {
        // 인스턴스화 과정
        Car car1 = new Car("Sedan", "Red"); // Car 클래스로부터 car1 객체 생성
        Car car2 = new Car("SUV", "Blue");  // Car 클래스로부터 car2 객체 생성
        
        System.out.println(car1.model);  // Sedan 출력
        System.out.println(car2.model);  // SUV 출력
    }
}

Car car1 = new Car("Sedan", "Red");는 Car 클래스를 사용하여 "Sedan" "Red"라는 속성값을 가지는 새로운 Car 객체를 생성하고, 그 객체를 car1이라는 참조형 변수에 참조하도록 하는 코드

인스턴스(객체) car1, car2
인스턴스화 new
메서드 public double gasPedal(double kmh)
클래스 class Car
생성자 public Car(String model, String color)    /     생성자는 메서드와 다르게 클래스와 이름이 같다

 

 

★오버라이딩

 🐳 부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것을 오버라이딩이라고 합니다.

 

  1. 선언부가 부모 클래스의 메서드와 일치해야 합니다.
  2. 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없습니다.
  3. 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없습니다

오버라이딩 사용 상황:

  • 부모 클래스의 메서드를 자식 클래스에서 새롭게 정의하고자 할 때.
  • 상속 구조에서 특정 메서드를 변경하고자 할 때.
  • 다형성을 활용하여, 자식 클래스의 특성에 맞는 기능을 구현하고자 할 때.

 

★오버로드

 🐳 오버로딩은 하나의 클래스 내에서 동일한 이름을 가진 메서드를 여러 개 정의하는 것입니다. 하지만 오버로딩된 메서드는 매개변수의 개수나 타입이 달라야 합니다.

  • 즉, 메서드 이름은 같지만 매개변수의 타입, 개수, 순서가 다르면 컴파일러가 어떤 메서드를 호출할지 구분할 수 있습니다. 반환 타입만 달라서는 오버로딩이 발생하지 않습니다. 컴파일 시점에 메서드가 결정됩니다

오버로딩 사용 상황:

  • 하나의 메서드 이름으로 다양한 입력을 처리하고자 할 때.
  • 매개변수의 개수나 타입이 다른 경우에도 같은 작업을 처리하고 싶을 때.
  • 메서드를 다수 정의해야 하는 경우, 메서드 이름을 통일하고 코드의 가독성을 높이기 위해.

 

 

다형성 & 추상화

동일한 메서드 이름이지만 다른 방식으로 동작할 수 있게 함

  • sound를 한 부모에서 상속받았지만 내용은 다름 = 다형성은 오버라이딩으로 구현됨
// 부모 클래스
class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

// 자식 클래스 1
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

// 자식 클래스 2
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("Cat meows");
    }
}

 

참조형 자료구조 

 

 

클래스 & 객체 & 인터페이스

 

 

인스턴스 맴버와 클래스 맴버

 

 

생성자

항목 내용
정의 객체 생성 시 초기화 작업을 수행하는 특수한 메서드. 클래스 이름과 동일한 이름을 가지며 반환 타입이 없음.
(초기화의 이유 : 안정적이고 예측 가능한 상태에서 객체를 사용할 수 있도록 보장하기 위해서 )
특징 ✔ 클래스 이름과 동일
✔ 반환 타입이 없음 (void도 사용하지 않음)
✔ 객체 생성 시 자동 호출
✔ 오버로딩 가능
사용처 ▶ 객체 초기화 시 필수 데이터를 설정하거나 특정 작업 수행
객체를 생성하면서 동시에 원하는 상태로 설정해야 할 때 사용
종류 - 기본 생성자: 매개변수가 없는 생성자 (컴파일러가 자동 생성, 명시적 정의 가능).
- 매개변수 생성자: 원하는 값으로 객체 초기화
기본값 - 생성자를 정의하지 않으면 컴파일러가 기본 생성자를 자동으로 제공.
- 생성자를 정의하면 기본 생성자는 제공되지 않음.

 

 

< 접근 제어자 정리표 >

접근 제어자 같은 클래스 같은 패키지 (클래스 폴더) 자식 클래스 외부 클래스
public O O O O
protected O O O X
default O O X X
private O X X X

 

 

접근 제어

< 접근 제어자 정리표 >

접근 제어자 같은 클래스 같은 패키지 (클래스 폴더) 자식 클래스 외부 클래스
public O O O O
protected O O O X
default O O X X
private O X X X

 

< 사용 가능한 접근 제어자  >

클래스 public, default
메서드 & 멤버 변수 public, protected, default, private
지역변수 없음

 

 

'Back-End (Web) > JAVA' 카테고리의 다른 글

[JAVA] HASH란 무엇인가  (1) 2024.11.21
[JAVA] 자바의 정렬  (0) 2024.11.21
[JAVA] NULL  (0) 2024.11.13
[JAVA] 쓰레드 & 람다 함수 & 스트림  (3) 2024.11.13
[JAVA] Generic  (3) 2024.11.12

Spring 웹 애플리케이션 계층 구조

📌  계층 구조는 애플리케이션을 여러 층으로 나누어 각 계층의 책임을 분리하여 관리하는 구조입니다. 이렇게 계층화하면 유지보수성, 확장성, 테스트 용이성 등이 향상됩니다.

  • 대표적인 계층 구조는 Controller, Service, Repository, Domain(또는 Entity) 계층으로 나누어져 있습니다.
  • 네트워크 계층 구조와는 다른 개념입니다만, 계층구조라는 점에선 같습니다.

Spring 웹 계층 구조

 

1. Controller 계층 (Web Layer)

  • 책임: 클라이언트의 HTTP 요청을 받아서 적절한 비즈니스 로직( 실제 문제를 해결하는 코드 = 문제 해결책 )을 처리할 서비스 계층에 전달하고, 처리 결과를 반환하는 계층입니다.
  • 역할:
    • 클라이언트(웹 브라우저, 모바일 앱 등)로부터 들어오는 HTTP 요청을 처리합니다.
    • Service 계층을 호출하여 비즈니스 로직을 수행합니다.
    • 결과를 View에 전달하여 응답을 반환합니다. JSON 데이터를 반환할 수도 있습니다 (REST API).
    • 주로 @Controller나 @RestController로 정의됩니다.
  • 주요 어노테이션: @Controller, @RestController, @GetMapping, @PostMapping
@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public String getUsers(Model model) {
        List<User> users = userService.getAllUsers();
        model.addAttribute("users", users);
        return "userList"; // view name을 반환 (thymeleaf 템플릿에서 사용)
    }
}

 


 

 

어노테이션(Annotation)

 🐳 사람보다는 프레임워크 컴퓨터가 코드를 이해하고 처리하는 데 도움을 주는 메타데이터입니다. 즉, 어노테이션은 단순한 주석이 아니라 코드의 동작 방식을 지정하는 기능을 부여하는 역할을 합니다.

 

일반적인 주석은 코드의 설명을 사람에게 제공하는 반면, 어노테이션은 Spring과 같은 프레임워크가 코드의 동작을 자동으로 처리하도록 도와주는 특별한 주석입니다. 예를 들어, @Controller 어노테이션은 Spring에게 "이 클래스는 웹 요청을 처리하는 컨트롤러"라고 알려줍니다. 이를 통해 Spring은 해당 클래스를 자동으로 관리하고 필요한 처리를 해줍니다

 

// 이 메서드는 사용자 정보를 반환합니다.
public User getUser() {
    return userService.getUser();
}


@GetMapping("/users")
public List<User> getUsers() {
    return userService.getAllUsers();
}

 

  • 위의 주석에서는 아무일도 일어나지 않지만
  • 밑의 @GetMapping 어노테이션은 Spring에게 "이 메서드는 GET 요청을 처리하고, /users 경로로 요청이 오면 실행되어야 한다"는 의미를 전달한다. Spring은 이 어노테이션을 보고 자동으로 이 메서드와 URL 경로를 연결해 준다.
  • 즉, 어노테이션은 프로그래머가 제작한 코드가 스프링에게 호출을 보낼 때 둘사이의 중간 연락망의 역활을 한다.

+ 프레임워크는 소프트웨어 개발을 위한 템플릿이다. 엄연히 하나의 프로그램임으로 내가 만든 코드와 소통을 해야한다.


2. 서비스 (Service)

  • 책임: 비즈니스 로직을 처리하는 계층 (실제로 일하는 노동자 = 해결사)입니다. Controller에서 전달받은 데이터를 처리하고, 레포지토리를 사용하여 데이터를 가져옵니다. 비즈니스 규칙을 적용하고 필요한 데이터를 처리한 후 반환합니다.
  • 역할:
    • 애플리케이션의 핵심 비즈니스 로직을 처리합니다.
    • 여러 도메인 객체와의 연산을 통해 데이터를 처리합니다.
    • Controller에서 요청을 받은 후 레포지토리를 통해 데이터를 조회하거나 업데이트합니다.
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

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

    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

 

3. 도메인 (Domain)

  • 책임: 애플리케이션에서 처리할 비즈니스 데이터를 모델링 (구현 대상[문제] 정의)하는 계층입니다. 도메인 객체(예: User, Product)는 애플리케이션에서 다루는 실제 데이터를 나타내며, 그 데이터를 위한 필드와 메서드를 정의합니다.
  • 도메인은 기획의 요구사항을 구현하고, 문제를 풀기 위해 설계된 소프트웨어 프로그램의 기능성을 정의하는 영역이라고 설명합니다.
  • 역할:
    • 비즈니스 데이터(중요정보)를 표현하는 클래스입니다.
    • 주로 **엔티티(Entity)**로 불리며, 데이터베이스 테이블과 매핑되는 객체입니다.
    • 서비스 계층에서 처리되는 데이터를 담고 있으며, 필요한 경우 **DTO (Data Transfer Object)**로 변환되기도 합니다.
  • 간단히 말해서, 앱의 기능(사진기, 앨범 등), 회사의 비즈니스 로직(회원, 주문 등)을 구현 대상을 정의하는 파트라고 생각하면 편하다.
  • 쿠팡의 창을 보면 화장품 - 립스틱, 아이브러쉬, 파운데이션 등의 선택창이 있다. 이 중 뭐가 도메인일까?
  • 정답은 화장품, 립스틱, 아이브러쉬 등 전부 다이다.
  • 왜냐 전부 구현이 필요한 대상이니까
  • 현업에서는 특정 비지니스에 대한 전문 지식을 말한다. 금융쪽 기업이 스프링을 통해 서버를 구축하면 당연히 금융 관련 지식과 거래 기능 서버 관리 등의 특정 전문성을 포함하는 프로그래밍을 진행해야한다. 여기서 특정 전문 분야를 도메인이라 한다.
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    // getters and setters
}

 

4. 레포지토리 (Repository)

  • 책임: 데이터베이스와의 상호작용을 담당하는 계층입니다. 레포지토리 JPA 또는 MongoDB와 같은 ORM(Object-Relational Mapping) 기술을 사용하여 데이터베이스와의 CRUD(Create, Read, Update, Delete) 작업을 처리합니다.
  • 역할:
    • 데이터베이스에서 데이터를 조회하고, 저장하며, 수정하거나 삭제하는 작업을 처리합니다.
    • Spring Data JPA를 사용하면 Repository 인터페이스만 작성하여 자동으로 CRUD 기능을 구현할 수 있습니다.
    • 서비스에서 요청한 데이터를 처리하고 반환합니다.
  • 자바 서버라는 회사의 직원 중 데이터베이스 회사랑 소통할 때 필요한 중개인(번역가) 정도로 보면 된다.
  • 번역가라는 표현이 정확한데 자바는 말 그대로 Java 언어로 보내면 레포지토리는 데이터베이스가 이해할 수 있도록 SQL문으로 변역해서 전달한다.
  • 그런데 단순 번역기의 역활이라면 JPA나 MongoDB와 같이 왜 레포지토리의 종류가 다양한가? 
  • JPA는 관계형 데이터베이스를  MongoDB를 비관계형 데이터베이스를 번역한다 = 즉, 번역하는 언어가 다르다.
  • 전문적으로 데이터 소스가 다양하다보니 각 데이터 소스에 맞는 레포지토리 구현이 필요했다.
@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    List<User> findByName(String name);

    Optional<User> findById(Long id);
}

 

 

 

5. 데이터베이스 (Database)

  • 책임: 실제 데이터를 영구적으로 저장하는 계층입니다. 데이터베이스는 관계형(RDBMS) 또는 비관계형(NoSQL) 데이터베이스를 사용할 수 있습니다. 데이터베이스는 레포지토리에서 처리한 데이터를 영속적으로 저장하고 조회하는 역할을 합니다.
  • 역할:
    • 레포지토리를 통해 데이터에 접근하며, 데이터를 저장하고, 수정하며, 삭제할 수 있습니다.
    • SQL 또는 NoSQL 쿼리를 통해 데이터를 관리합니다.

< 전체 계층 구조 예시 >

+-----------------------+     +------------------------+     +-------------------------+
|     Controller        |     |       Service          |     |       Repository        |
|   (입구, 요청 처리)    | --> |   (비즈니스 로직 처리)  | --> |  (데이터베이스 CRUD)    |
+-----------------------+     +------------------------+     +-------------------------+
         |                           |                           |
         v                           v                           v
+----------------------------+  +-------------------------+  +----------------------------+
|        View (HTML, JSP)    |  |      Domain (Entity)     |  |        Database (SQL/NoSQL)  |
|   (결과 응답, UI 표시)      |  |   (데이터 객체, 모델)    |  |  (데이터 저장, 조회, 수정)  |
+----------------------------+  +-------------------------+  +----------------------------+

 

각 계층의 흐름

  1. Controller는 클라이언트의 요청을 처리하고 Service로 전달합니다.
  2. Service는 비즈니스 로직을 처리하고, 필요한 데이터를 Repository에서 조회하거나 저장합니다.
  3. Repository는 데이터베이스와 상호작용하여 실제 데이터를 저장하거나 조회합니다.
  4. 데이터를 처리한 후, Service는 결과를 Controller로 반환하고, Controller는 이를 View (HTML 등)로 전달하여 사용자에게 응답합니다.

 

< 결론 >

컨트롤러 클라이언트와 서버 사이의 소통 중개인
서비스 문제 해결 일꾼
도메인 문제 정의
레포지토리 DB와 서버간의 번역가
DB 정보 저장 창고

 

'Back-End (Web) > Spring' 카테고리의 다른 글

[Spring] Spring MVC 패턴  (1) 2024.12.14
[Spring] Spring Boot  (0) 2024.12.10
[Spring] Spring Framework  (2) 2024.12.09
[Spring] 웹 개발의 흐름  (1) 2024.12.05
[Spring] 스프링 웹 개발 기초  (0) 2024.11.13

+ Recent posts