교재 내용은 매우 레거시한 내용

 

이 챕터에서 살펴봐야 할 내용인 

HttpSession

HandlerInterceptor

쿠키

 

이 세가지에 대해 간략하게 설명

 

 

 

컨트롤러에서 HttpSession 사용하기

 

public class test (HttpSession session){

	...
}

public class test (HttpServletRequest request) {
	HttpSession session = reqeust.getSession();
    ..
}

 

파라메터로 HttpSession을 받는 방식, HttpServletRequest.getSession() 으로 세션 가져오는 방식

두 개 모두 사용 가능

 

HttpSession 객체에서는 Session Attribute 접근 가능

 

session.getAttribute("authInfo", authInfo);

와 같이 사용

 

 

인터셉터 사용하기

 

HandlerInterceptor 인터페이스를 상속받는 Interceptor 커스텀 클래스를 구현해서

인터셉터 사용 가능

 

preHandle :: 컨트롤러 객체 실행 이전

postHandle :: 컨트롤러 객체 실행 이후, 뷰 실행 전

afterCompletion :: 뷰 실행 이후

 

각각의 시점에 맞게 동작에 관해 인터셉터 구현 가능

 

 

 

 

** 이거 구현하는거보다 

Aop

Spring security FilterChain

Servlet Filter 

사용

 

대부분의 인터셉터의 경우 인증 상태 검사, 권한 검사, 로깅, XSS 대응 등에 사용하는데

그냥 Security Filter 에서 구현하는게 나음

 

 

컨트롤러에서 쿠키 사용

 

@CookieValue

 

쿠키 생성

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/hello")
public class HelloController {
    @PostMapping
    public String submit(HttpServletResponse response) {

      //쿠키  객체 생성, "cookieName"이라는 이름으로 쿠키를 생성하고, 그 값은 "cookieValue"로 설정
      Cookie rememberCookie = new Cookie("cookieName", "cookieVlaue");

      // 쿠키 경로 설정, "/"는 모든 경로에서 사용하겠다는 뜻
      rememberCookie.setPath("/");

      // 쿠키를 유지할 시간 설정(단위 : 초)
      rememberCookie.setMaxAge(60*60*24*30); // 30일 동안 쿠키 유지.

      response.addCookie(rememberCookie);

      return "hello";

    }

}

 

 

쿠키 전달

import org.springframework.web.bind.annotation.CookieValue;
import javax.servlet.http.Cookie;

@Controller
@RequestMapping("/hello")
public class HelloController {
    @GetMapping
    public String form(@CookieValue(value = "cookieName", required = false) Cookie coookie) {

        //이름이 "cookieName"인 쿠기가 존재한다면?
        if(cookie != null) {

              //cookieValue 변수에 쿠키 값을 저장한다.
             String cookieValue = cookie.getValue();         
        }

        return "hello";
    }
}

매핑 어노테이션을 이용한 경로 매핑

 

@GetMapping

@PostMapping

@RequestMapping 

 

등을 사용하여 url 매핑

 

 

굳이 

GetMapping, PostMapping 쓰는 이유?

 

가독성, 명확성

하나의 url 에 대해 GET, POST 모두 매핑 가능

 

@GetMapping("/page")
public String getPage(){

	...
}

@PostMapping("/page")
public String editPage(){

	...
}

이처럼 동일한 url에 대해 GET, POST 각각 나눠서 매핑이 가능

 

 

 

요청 파라미터 접근

 

 

HttpServletRequest

@PostMapping("/test/step1"){
public String handleStep1(HttpServletRequest request){
	String agreeParam = request.getParam("agree");
    	 ...
 }

 

@RequestParam

@PostMapping("/test/step1"){
public String handleStep1(@RequestParam(value = "agree", defaultValue = 
"false")Boolean agreeVal){
	if(!agreeVal){
    return "test/step1";
    }
    return "test/step2";
 }

 

 

리다이렉트 처리

 

@GetMapping("test/step1")
public String handleStep2Get(){
	return "redirect:/test/step2";
}

 

 

커맨드 객체를 이용해서 요청 파라미터 사용하기

 

 

HttpServletRequest 방식으로 여러 파라미터를 받으면

 

@PostMapping("/test/step3")
public String handleStep3(HttpServletRequest request) {
	String email = request.getParameter("email");
    String name = request.getParameter("name");
	String password = request.getParameter("password");
	String confirmPassword = request.getParameter("confirmPassword");
    
    RegisterRequest regReq = new RegisterRequest();
    regReq.setEmail(email);
    regReq.setName(name);
    ...
}

와 같이 파라미터 하나하나 다 HttpServletRequest 에서 추출해야 함

 

but

커맨드 객체를 사용하면

 

 

public class RegisterRequest(){

	private String name;
    private String email;
    ...
    
    
    public void setName(name){
    	name = this.name;
    }
    
    public String getName(){
    
   		return name;
    }
    
    ...
}
@PostMapping("/test/step3")
public String handleStep3(RegisterRequest reqReq){
	...
}

 

와 같이 객체 안의 필드와 파라미터를 매핑해줌

 

그냥 우리가 흔히 쓰는 dto 라고 보면 됨

 

 

뷰 JSP 코드에서 커맨드 객체 사용하기

<p><strong>${registerRequest.name}님</strong></p>

와 같이 jsp 에서 커맨드 객체 사용 가능

 

 

@ModelAttribute 어노테이션으로 커맨드 객체 속성 이름 변경

커맨드 객체에 접근할 때 속성 이름을 변경하고 싶으면 

public String handleStep3(@ModelAttribute("formData") RegisterRequest regReq){

	...
}

와 같이 사용

 

주요 에러 발생 상황

요청 매핑 애노테이션과 관련된 주요 익셉션

  • 404 에러: 요청 경로를 처리할 컨트롤러가 존재하지 않거나, WebMvcConfigurer를 이용한 설정이 없거나 뷰 이름에 해당하는 JSP 파일이 존재하지 않는다면 발생하는 에러.
  • 405 에러: 지원하지 않는 전송 방식을 사용한 경우 발생하는 에러.
    ex) POST 방식만 처리하는 요청 경로를 GET방식으로 연결하면 발생

@RequestParam이나 커맨드 객체와 관련된 주요 익셉션

  • 400에러:
    -요청 파라미터의 값을 @RequestParam이 적용된 파라미터 타입으로 변환할 수 없는 경우 발생
    -요청 파라미터 값을 커맨드 객체에 복사하는 과정에서도 발생. 만약 커맨드 객체의 프로퍼티가 int 타입인데 요청 파라미터의 값이 "abc"라면, "abc"를 int 타입으로 변환할 수 없기에 에러 발생

Model을 통해 컨트롤러에서 뷰에 데이터 전달하기

컨트롤러는 뷰가 응답 화면을 구성하는데 필요한 데이터를 생성해서 전달해야하는데 이때 사용하는 것이 Model이다.
뷰에 데이터를 전달하는 컨트롤러는 다음 두가지를 하면 된다.

  • 요청 매핑 애노테이션이 적용된 메서드의 파라미터로 Model을 추가
  • Model 파라미터의 addAttribute()메서드로 뷰에서 사용할 데이터 전달
@Controller
public class HaewonController{
	@RequestMapping("/test")
    public String test(Model model, @RequestParam(value = "name",
    required = false)String name){
		model.addAttribute("greeting","안녕하세요"+name);
        return "test";
     }
}

addAttribute()메서드의 첫번째 파라미터는 속성 이름이다. 뷰 코드는 이 이름을 사용해서 데이터에 접근한다.
위 코드에서는 greeting을 사용해 데이터에 접근하고 있다.
JSP에선 ${greeting}을 사용해서 속성값에 접근한다.

지금까지 구현한 컨트롤러는 두가지 특징이 있었다.

  • Model을 이용해서 뷰에 전달할 데이터 설정
  • 결과를 보여줄 뷰 이름을 리턴

지금까진 이 둘을 따로 처리한 예시들을 봤으나 ModelAndView는 모델과 뷰 이름을 함께 제공하기에 이를 사용하면 이 두 가지를 한번에 처리할 수 있다.

 

 

커맨드객체 : 중첩, 콜렉션 프로퍼티

 

jsp 에서 체크박스처럼 같은 속성이름으로 여러 데이터가 들어올 때

커맨드 객체에 

	private List<String> checkBoxValues;

와 같이 선언해주면 리스트에 해당 데이터가 들어옴

이거뭐 다아는거라 설명할 필요성이 없네

 

 

Model을 통해 컨트롤러에서 뷰에 데이터 전달하기

 

model.addAttribute("data", data);

처럼 사용하면

jsp에서 

${data}

로 사용 가능

 

 

Spring MVC 란?

 

Spring에서 MVC패턴을 구성하고 구성요소들을 확장할 수 있게 만든 도구

 

 

Spring MVC 구조

주요 구성요소는 Model, View, Controller 이지만, 이들이 유기적으로 동작하기 위한 다양한 구성요소가 포함

 

  • DispatcherServlet(Front Controller)
  • HandlerMapping
  • HandlerAdapter(ControllerAdapter)
  • ModelAndView
  • ViewResolver



주요 구성요소 설명

1. DispatcherServlet (디스패처 서블릿)

  • Front Controller 역할 수행
  • 모든 HTTP 요청을 중앙에서 받고, 적절한 Controller에 위임
  • web.xml 또는 Spring Boot의 경우 @SpringBootApplication에 자동 설정됨
  • 예: localhost:8080/app/home → DispatcherServlet이 수신

Spring legacy 프로젝트에서는 xml 설정 필요

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

 

 

Spring boot 프로젝트에서는 DispatcherServlet 자동 등록

커스터마이징을 원할 땐 

 

Java config 를 통해

@Configuration
public class WebConfig {

    @Bean
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet servlet = new DispatcherServlet();
        servlet.setThrowExceptionIfNoHandlerFound(true); // 예외 처리 커스터마이징 예시
        return servlet;
    }

    @Bean
    public ServletRegistrationBean<DispatcherServlet> dispatcherServletRegistration(DispatcherServlet dispatcherServlet) {
        ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(dispatcherServlet, "/");
        registration.setName("dispatcherServlet");
        return registration;
    }
}

위와같이 DispatcherServlet 객체를 직접 구현이 가능

 

 

 

 

2. HandlerMapping

  • 어떤 Controller가 어떤 URL 요청을 처리할지 결정
  • @RequestMapping이나 @GetMapping, @PostMapping 등의 어노테이션 기반 매핑
@RequestMapping("/home") public String home() {
	return "homeView";
}

 

쉽게 말해서 어떤 컨트롤러의 어떤 메서드로 요청을 처리할지 결정

@RequestMapping

@GetMapping

@PostMapping

...

 

 

** @EnableWebMvc 어노테이션

Spring Framework 에서 Spring MVC 구성 요소를 명시적으로 활성화 할 때 사용

기본적으로 @Configuration 클래스에 붙여 사용

내부적으로 다양한 MVC 관련 설정(RequestMappingHandlerMapping, MessageConverter, ViewResolver 등..) 등록 가능

 

 

3. Controller

  • 클라이언트의 요청을 처리하고, 모델(Model) 데이터를 생성하고, 뷰(View) 이름을 반환
  • 비즈니스 로직은 보통 Service 계층에 위임
  • 반환 타입: 문자열(뷰 이름), ModelAndView, @ResponseBody, 등
@Controller
public class HomeController {
    @GetMapping("/home")
    public String home(Model model) {
        model.addAttribute("msg", "Hello, Spring MVC!");
        return "home";  // ViewResolver가 home.jsp로 매핑
    }
}

mvc에서 매우 자주 쓰게 되는 Contorller

 

 

4. Model (모델)

  • View로 전달할 데이터
  • Model, ModelMap, 또는 ModelAndView 객체를 통해 설정

 

But

 

@RestController + ResponseEntity 사용 방식이 더 현대적인 방식

REST API 응답에 최적화 되어있고

JSON/XML 형태로 HTTP 상태 코드, 헤더, 본문을 명확히 제어 가능

@ResponseBody를 기본 포함하거나 직접 명시

 

항목 Model & View ResponseEntity
용도 웹 페이지 렌더링 (JSP 등) REST API 응답 (JSON 등)
컨트롤러 타입 @Controller @RestController
데이터 전달 방식 Model 객체에 담아 뷰로 전달 HTTP 응답 본문에 JSON 등으로 전달
HTTP 제어 상태 코드/헤더 설정 어려움 status, headers, body 직접 제어
추천 환경 서버 렌더링 기반 웹 SPA, 모바일, 프론트-백 분리 구조
모르겠으면, 그냥 ResponseEntity 쓰자
 

5. ViewResolver

  • Controller가 "home" 같은 논리적인 뷰 이름을 반환하면
    ViewResolver가 이를 /WEB-INF/views/home.jsp와 같이 실제 물리적 파일 경로로 변환
  • MVC에서 View를 찾는 책임을 분리한 구성 요소

1. Java Config 방식

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");  // 경로 앞부분
        resolver.setSuffix(".jsp");             // 확장자
        return resolver;
    }
}

 

2. XML 설정 방식 (Spring Legacy)

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

 

 

 

** 주의할 점으로 

@RestController에서는 ViewResolver가 작동하지 않음

JSON 변환(HttpMessageConverter)이 사용됨

@Controller + @ResponseBody 조합도 마찬가지

**ViewResolver는 서버 사이드 렌더링(View 기반 응답)**에만 사용됨

 

 

6. View

  • 실제 사용자에게 보여지는 UI
  • JSP, Thymeleaf, Mustache, JSON(ResponseBody) 등 다양한 뷰 기술 사용 가능

 

 

AOP 

Aspect Oriented Programming

관점 지향적 프로그래밍

 

 

공통의 관심사를 모듈화하여 핵심 로직과 분리하는데 집중

ex) 로깅, 트랜잭션, 보안 등

 

얘네는 반복되면서 중요한 작업이지만, 비즈니스 로직과는 분리가 필요한 기능들

 

 

 

 

용어 정리

  • Aspect: 공통 관심사 (예: 로그 출력)
  • Join point: Advice가 적용될 수 있는 지점 (예: 메서드 실행)
  • Advice: 언제 무엇을 실행할지 정의하는 코드 (예: @Before, @After)
  • Pointcut: Advice가 적용될 join point를 선정하는 식
  • Weaving: Advice를 실제 객체에 적용하는 과정 (런타임에 프록시 방식으로)

 

 

Aspect 종류

 

Before Advice @Before 대상 메서드 실행 전에 실행됨
After (finally) Advice @After 대상 메서드 성공/실패 여부와 관계없이 종료 후 실행
After Returning Advice @AfterReturning 대상 메서드가 정상 종료되었을 때만 실행
After Throwing Advice @AfterThrowing 대상 메서드에서 예외가 발생했을 때만 실행
Around Advice @Around 대상 메서드의 실행 전/후 모두 제어 가능하며 proceed()를 통해 직접 실행함

 

대부분의 경우 Around Advice 사용해서 구현 가능

 

 

 

구현

 

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("[Before] 메서드 실행 전 로그 출력");
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter() {
        System.out.println("[After] 메서드 실행 후 로그 출력");
    }
}

Aspect 클래스 정의

ex) 로깅을 위한 클래스

 

** excution 으로 point cut 지정

execution(...) 메서드 실행 지점을 의미하는 포인트컷 타입
* 반환 타입: 모든 반환 타입 허용 (void, int, String, 등)
com.example.service.* 패키지: com.example.service 하위의 모든 클래스 (바로 아래 클래스만 포함, 하위 패키지는 포함되지 않음)
* 클래스 내 모든 메서드 이름
(..) 메서드 인자: 매개변수 개수와 타입에 상관없이 전부 허용

point cut 표현식은

수식어, 리턴타입, 클래스 이름, 파라미터 등에 대해 조건 명시 가능

 

@Service
public class SampleService {
    public void doSomething() {
        System.out.println("핵심 로직 수행");
    }
}

 excution point cut 표현식 조건에 맞는 메서드가 실행 될 때, 전 후로 aspect 메서드 수행

 

[Before] 메서드 실행 전 로그 출력
핵심 로직 수행
[After] 메서드 실행 후 로그 출력

 

 

 

Pointcut

  • 하나의 Pointcut 에 여러 Advice를 적용할 수 있으며, 어떤 Aspect가 먼저 적용될지는 스프링 프레임워크나 자바 버전에 따라 달라질 수 있음
  • Aspect 적용 순서가 중요하다면 @Aspect 애노테이션과 함께 @Order 애노테이션을 붙여 적용 순서 결정 가능
@Aspect
@Order(1)
public class FirstAspect {
}

@Aspect
@Order(2)
public class SecondAspect {
}

 

  • @Pointcut 애노테이션이 아닌 @Around 애노테이션에 execution 명시자를 직접 지정할 수도 있음
@Aspect
public class ExamAspect {

	@Around("execution(public * package..*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
    	...
    }

}

 

 

 

컨테이너 초기화와 종료

 

Spring container는 초기화, 종료라는 라이프사이클을 가짐

 

간단하게

 

1. 초기화

(빈 객체 생성, 의존성 주입, 초기화)

AnnotaionCOnfigApplicationContext ctx = new AnnotaionCOnfigApplicationContext(AppContext.class);

 

2. 빈 객체 사용

Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("spring");
System.out.println(msg);

 

3. 종료

(빈 객체 소멸)

ctx.close();

 

 

스프링 빈 객체의 라이프사이클

 

객체 생성 - 의존 설정 - 초기화 - 소멸

 

빈 객체의 초기화와 소멸..?

 

교재에서는 

org.springframework.beans.factory.InitializingBean (생성)

org.springframework.beans.factory.DisposableBean (소멸)

에 대해 설명하는데

 

매우매우매우 레거시한 코드가 아닌 이상 이 두가지 방식은 실무하면서 사용할 기회 없다고 봐도 무방

그럼 뭘 쓸까?

 

@PostConstructor

@PreDestroy

 

두 가지 어노테이션 사용

 

@Component
public class MyService {

    @PostConstruct
    public void init() {
        System.out.println("초기화 로직");
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("종료 전 정리 로직");
    }
}

 

이 방식만 알고있으며 됨

 

InitializingBean,  @PostConstructor, 

빈이 생성되고, 의존성까지 주입이 된 이후 실행

 

DisposableBean, @PreDestory 

Spring 컨테이너가 종료될 때, 해당 빈에 대해 소멸 콜백을 제공하는 수단

 

 

빈 객체의 생성과 범위

 

한개의 객체만 존재하는 빈은 싱글톤 범위

But

별도 설정 시, 프로토타입 범위 빈 설정 가능

 

프로토타입 빈?

Spring 에서 요청할 때 마다 새로운 인스턴스를 생성하는 방식의 빈

 

@Component
@Scope("prototype")
public class MyPrototypeBean {
    public MyPrototypeBean() {
        System.out.println("새 인스턴스 생성됨");
    }
}

 

인스턴스 생성 Spring 컨테이너에 요청할 때마다 매번 새로 생성
빈 등록 시점 요청 시 생성 (lazy by nature)
생명주기 관리 Spring은 초기화까지만 관리하고, 소멸은 관리하지 않음

 

소멸 전 콜백

@PreDestroy, DisposableBean.destroy() 

두 가지 메서드는 프로토타입 빈에서는 동작하지 않음

 

MyPrototypeBean bean = context.getBean(MyPrototypeBean.class);
try {
    // 사용
} finally {
    if (bean instanceof DisposableBean) {
        ((DisposableBean) bean).destroy(); // 직접 소멸 처리
    }
}

빈 소멸체크를 통해 직접 소멸 처리 필요

 

그럼 얘를 언제 쓰냐?

1. 생성되는 빈 마다 각기 다른 Status 를 가질 때

사용자 입력을 담는 임시 객체, UI 상태 모델 등

@Component
@Scope("prototype")
public class UserInput {
    private String value;
}

 

 

2. 멀티스레드 환경에서

동시에 처리되는 로직에서 싱글톤 빈으로 상태 공유를 피하고 싶을 때 사용

>> 싱글톤 빈 사용 시, 한 스레드에서 그 빈의 속성을 변경하면 다른 빈에도 영향을 끼치니까

 

3. 비용이 큰 작업을 지연 초기화하고 재사용하지 않을 때

이미지 처리, 파일 파싱, 임시 암호화 모듈 등

객체 생성 비용이 큰데, 재사용하지 않고 매번 새로 써야 할 경우

 

4. 프록시 기반 지연 주입 또는 팩토리 형태로 활용할 때

싱글톤 빈에서 매번 새로운 인스턴스를 사용해야 할 때
→ ObjectProvider, @Lookup, Provider<T>로 주입받아 매번 새로 생성

@Component
public class SingletonService {

    @Autowired
    private ObjectProvider<MyPrototypeBean> provider;

    public void doWork() {
        MyPrototypeBean prototype = provider.getObject(); // 매번 새 객체
    }
}

 

 

** Proxy?

대리 객체 

 

@Component
@Scope("prototype")
public class PrototypeBean { }

@Component
public class SingletonBean {

    @Autowired
    private PrototypeBean bean; //  주입 시점에 한 번만 생성됨
}

 

SingletonBean은 애플리케이션 시작 시 1회만 생성되므로

PrototypeBean도 그 시점에 1번 생성된 후 계속 재사용됨 →  의도한 동작 아님

 

 

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PrototypeBean { }

@Component
public class SingletonBean {

    @Autowired
    private PrototypeBean bean; // 실제론 프록시 객체가 주입됨
}

 

 

bean 필드는 실제 PrototypeBean이 아닌, 프록시 객체

이 프록시는 사용할 때마다 내부적으로 새로운 PrototypeBean을 생성하여 위임

 

언제 씀?

싱글톤에서 프로토타입을 매번 새로 쓰고 싶을 때 자동으로 new처럼 작동함
웹 요청/세션 스코프 빈을 싱글톤에 주입할 때 웹 요청마다 프록시가 실제 객체로 연결해줌
동작 시점 지연 (Lazy) 메모리/속도 최적화 가능

 

 

 

컴포넌트 스캔

스프링이 클래스를 직접 빈으로 등록해서 사용

다른말로 스프링 빈으로 등록될 클래스패스(Classpath)를 스캔하여 빈으로 등록해주는 과정

 

@Configuration  // 설정 정보 등록
public class AppConfig {  
  
	@Bean  // 스프링 빈으로 등록
	public MemberService memberService(){  
		return new MemberServiceImpl(memoryMemberRepository());  // 의존성 주입
	}  
  
	@Bean  // 스프링 빈으로 등록
	public OrderService orderService(){  
		return new OrderServiceImpl(memoryMemberRepository(), discountPolicy());  // 의존성 주입
	}  

}

 

이와 같이 @Configuration, @Bean, 의존성 주입을 사용하여 빈 등록하는 과정

등록할 빈과 주입해야할 의존성이 많아지면 코드가 매우 복잡

 

 

 

@Component

 

클래스를 빈으로 자동 등록해주는 어노테이션

 

package com.kr.example

...


@Configuration 
@ComponentScan
public class AutoAppConfig { 

	...
}

 

이와 같이 @Configuration 클래스에 @ComponentScan 어노테이션을 달아줌으로서 

이 패키지가 존재하는 패키지 com.kr.example 패키지와 그 하위 패키지들에 대해서 @Component, @Service, @Controller, @Repository 등의 컴포넌트 스캔 수행

 

 

** 여기서는 java config 를 사용하는데

xml 을 통한 설정도 사용 가능

 

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

  <context:component-scan base-package="com.kr.example"/>
	
    ...
    
</beans>

 

이렇게 설정해주면, com.kr.example 패키지와, 그 하위 패키지를 모두 스캔하겠다는 의미

 

 

** Spring boot 의 경우, @ComponentScan 을 명시하지 않아도 되는데, 

Spring boot 프로젝트의 

 

@SpringBootApplication 

이 어노테이션 때문

 

Spring boot 프로젝트 처음 생성하면

@SpringBootApplication
public class SpringBootApplication {

    public static void main(String[] args) {

	}
}

 

이런 기본 클래스가 함께 생성되는데

 

@SpringBootApplication 이라는 어노테이션은 사실

 

@Target(...)
@Retention(...)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
    ...
}

 

위와 같은 의미를 가지고 있다

다른 어노테이션들도 중요하지만, 

우리가 배운 @Configuration 을 확장한 @SpringBootConfiguration 과 @ComponentScan 두 가지를 포함하고 있기 때문에

따로 ComponentScan 명시 불필요

 

 

 

 

스캔 대상에서 제외하거나 포함

 

@ComponentScan 의 속성들

 

basePackages String[] 스캔할 패키지 이름들 지정
예: {"com.example.service", "com.example.dao"}
basePackageClasses Class<?>[] 참조 클래스의 패키지를 기준으로 스캔
예: {MyService.class}
nameGenerator Class<? extends BeanNameGenerator> Bean 이름을 생성할 커스텀 전략 클래스 지정
scopeResolver Class<? extends ScopeMetadataResolver> 빈의 scope(singleton, prototype 등)를 결정할 전략 클래스 지정
scopedProxy ScopedProxyMode 프록시 모드 설정
DEFAULT, NO, INTERFACES, TARGET_CLASS
includeFilters ComponentScan.Filter[] 포함할 필터 지정 (조건 맞는 클래스만 스캔)
excludeFilters ComponentScan.Filter[] 제외할 필터 지정 (조건 맞는 클래스는 스캔에서 제외)
lazyInit boolean (Spring 2.2+) true로 설정하면 스캔된 모든 빈을 lazy-init 처리

 

여기서

excludeFilters, includeFilters 설정을 통해 스캔 범위 조절 가능

 

@ComponentScan(
  basePackages = "com.example",
  excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {DeprecatedService.class})
)

 

위와 같이 특정 클래스를 명시하거나

@ComponentScan(
  basePackages = "com.example",
  excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = "spring\\..*Dao"))
)

 

정규식 패턴을 사용하여 스캔을 원하는, 원하지 않는 클래스의 범위 조절

 

 

** FilterType.ASPECTJ 

일반 정규식과 다르게 관점지향적인 (AOP) 표현식

 

@ComponentScan(
    basePackages = "com.example",
    includeFilters = @ComponentScan.Filter(
        type = FilterType.ASPECTJ,
        pattern = "com.example..*Service+"
    ),
    useDefaultFilters = false
)
public class AppConfig {
}

 

com.example 이하 모든 패키지에서

이름에 Service로 끝나는 클래스 및 서브클래스(+ 기호) 포함

useDefaultFilters = false → 기본 필터(@Component, @Service 등 자동 스캔) 비활성화 

 

 

@ComponentScan(
    basePackages = "com.example",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = {Deprecated.class}
    )
)
public class AppConfig {
}

 

 

@Deprecated 어노테이션이 붙은 클래스 스캔 제외

 

 

 

기본 스캔 대상

@Component

@Controller

@RestController

@Service

@Repository

@Aspect

@Configuration

 

위 항목들은 기본적으로 @Component의 메타 애노테이션이기 때문에 스캔 대상

 

 

 

의존성 자동 주입

말 그대로 의존성을 자동으로 주입하는 방식

 

의존성 자동 주입을 위해서는 

@Autowired

@Resource

등의 어노테이션 사용

 

책에서는 @Autowired에 대한 설명만 진행

 

@Autowired

💡 @Autowired: 자동 주입 기능, 스프링이 알아서 의존 객체를 찾아 주입한다. 의존을 주입할 대상에 애노테이션을 붙이기만 하면 된다.
  • @Autowired 애노테이션을 붙이면 설정 클래스에서 의존을 주입하지 않아도 된다.
  • 스프링이 해당 타입의 빈 객체를 찾아서 필드에 할당

 

간단하게 말해서

 

pulbic class TestClass {

	@Autowired
    private TestObject testObject;
    
    ...
}

스프링 컨텍스트에 등록된 TestObject 의 빈 객체를 가져와 사용하겠다는 의미

 

 

이렇게 하면

public class AppCtx {

	...
    @Bean
    public TestObject testObjet() {
    	return new TestObject();
    }
    
    
    // 아래 부분 필요 X
    @Bean
    public TestClass testClass {
    	TestClass testClass = new TestClass();
        testClass.setTestObject(testObject);
        return testClass;
    }
}

@Configuration 

클래스에서  의존성 주입하는 클래스를 따로 명시하지 않아도 됨

 

 

일치하는 빈이 없는경우

@Autowired 어노테이션을 적용한 대상에 일치하는 빈이 없으면

UnsatisfiedDependencyException : Error creating bean.. 어찌구

Exception 발생

 

 

 

@Qualifier 어노테이션을 이용한 의존 객체 선택

단순하게 설명하자면

같은 클래스의 빈을 두개 이상 만들어서 사용하고 싶다!!

할때 @Qualifier 어노테이션으로 빈 이름을 설정해서 필요할 때 원하는 이름의 빈을 꺼내 사용하는 방법

 

빈 등록 할 때 @Qualifier 따로 명시하지 않으면 빈의 이름을 한정자로 지정

 

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = ac.getBean("memberService", MemberService.class);

 

스프링 컨텍스에서 getBean() 메서드로 빈 조회해서 이름 확인 가능

 

 

상위/하위 타입 관계와 자동 주입

상속관계를 가지는 빈 객체를 @Autowired 로 자동 주입할 때 충돌 발생 가능

 

스프링은 @Autowired로 빈 주입 시, 타입 기준으로 빈을 서치

 

@Autowired
public void setMemberPrinter(MemberPrinter printer) { ... }

 

이 경우 MemberPrinter 타입을 찾게 되는데 

AppCtx 설정에 

@Bean
public MemberPrinter memberPrinter1() { return new MemberPrinter(); }

@Bean
public MemberSummaryPrinter memberPrinter2() { return new MemberSummaryPrinter(); }

이처럼

MemberPrinter, MemberPrinter의 하위 클래스를 각각 반환하는 빈이 등록되있음

 

즉, MemberPrinter 타입 빈이 두개 존재

@Qualifier 어노테이션으로 한정자 지정해서 해결 가능

 

 

@Autowired 어노테이션 필수 여부

 

@Autowired는 기본적으로 @Autowired 어노테이션을 붙인 타입에 해당하는 빈이 존재하지 않으면 Exception 발생

required 속성을 false로 설정해서 매칭되는 빈이 없으면 자동주입을 하지 않는 방법 가능

 

@Autowired(required = false)
private Optional<MemberPrinter> printer;

이렇게 사용하게 되면 빈이 없을 경우, null 주입하거나 아무 동작도 하지 않음

 

** Optional 과 함께 사용해서 null 에 대해 처리해주는 코드 함께 작성 권장 **

2025.06.25 - [끄적/BE] - Optional

 

 

@Nullable

 

문자 그대로 null 가능하다고 명시적으로 표시하는 용도

목적 해당 변수/파라미터/리턴값이 null일 수 있음을 명시
주요 위치 필드, 메서드 파라미터, 메서드 반환값

 

 

1. 의존성 주입에서

@Component
public class MemberInfoPrinter {

    private MemberPrinter printer;

    @Autowired
    public void setPrinter(@Nullable MemberPrinter printer) {
        this.printer = printer;
    }
}

 

  • MemberPrinter 빈이 없어도 예외 없이 null이 주입됨
  • @Autowired(required = false)와 유사 기능

 

2. 일반 메서드 파라메터

public String getNickname(@Nullable String username) {
    return (username != null) ? username : "Guest";
}

 

 

 

3. return 값

@Nullable
public Member findByEmail(String email) {
    ...
    return null; // 없을 수도 있음
}

 

 

 

 

자동 주입과 명시적 의존 주입 간의 관계

@Configuration 클래스에서 의존성 주입했는데, 자동 주입 대상이면??

 

명시적 주입

@Bean
public MemberInfoPrinter infoPrinter() {
    MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
    infoPrinter.setPrinter(memberPrinter2()); // 명시적 주입: SummaryPrinter
    return infoPrinter;
}

 

자동 주입

@Autowired
@Qualifier("printer")
public void setPrinter(MemberPrinter printer) {
    this.printer = printer;
}

 

출력 결과는 **memberPrinter1() (일반 MemberPrinter)**를 사용한 것으로 확인됨.
즉, 명시적으로 주입한 memberPrinter2()가 무시되고, @Autowired에 의해 memberPrinter1()이 주입됨

 

자동 주입이 명시적 주입을 덮어쓰기 때문

Spring에서는 @Bean 메서드로 생성한 객체에 대해 내부적으로 생성 이후 @Autowired를 기반으로 DI 작업을 수행

  1. @Bean 메서드(infoPrinter())가 실행되어 객체 생성 및 명시적 주입 수행
  2. Spring 컨테이너는 해당 객체에 @Autowired가 선언된 세터가 있으면 자동 주입 수행
    >> 이 과정에서 이미 명시적으로 주입된 필드라도 다시 덮어씀

즉, 자동 주입이 명시적 Setter 호출보다 나중에 실행, 자동 주입이 반영됨

 

의존이란?

Dependency Injection

의존 주입

 

예를 들어

public class MemberRegisterService {
	private MemberDao memberDao = new MemberDao();
    
    public void regist(RegisterRequest req) {
    
        Member member = memberDao.selectByEmail(req.getEmail());

        if(member != null) {
            throw new DuplicatedMemberException("dup mem");
        }

        Member newMember = new Member(req.getEmail(), req.getPassword(), req.getName(), LocalDateTime.now());
        memberDao.insert(newMember);
	}
}

 

위와 같은 코드에서 

MemberRegisterService 클래스가 DB 사용을 위해 MemberDao클래스의 메서드를 사용

이렇게 한 클래스가 다른 클래스의 메서드를 실행할 때, 이를 의존한다 라고 표현

 

 

의존하는 대상이 있으면, 그 대상을 구하는 방법이 필요

가장 쉬운 방법은 의존 대상 객체를 직접 생성하는것

 

public class MemberRegisterService {

	private MemberDao memberDao = new MemberDao();
    
   	...
}

 

 

클래스 내부에서 의존 객체 직접 생성은 유지보수 관점에서 좋지 않은 방식

 

 

DI를 통한 의존 처리

 

DI는 의존 객체를 직접 생성하는 대신 의존 객체를 전달받는 방식 사용

 

public class MemberRegisterService {

	...
    private MemberDao memberDao;
    
    public MemberRegisterService (MemberDao memberDao){
    	this.memberDao = memberDao;
	}
    
    ...
}

 

의존 객체를 직접 생성하지 않고

생성자를 통해 의존 객체를 전달받음

 

MemberRegisterService service = new MemberRegisterService(memberDao);

와 같이 생성자 생성 시, 의존 객체 함께 전달

 

 

 

DI와 의존 객체 변경의 유연함

 

의존 객체 직접 생성방식은 필드, 생성자에서 new 연산자로 객체 생성

 

public class MemberRegisterService{
	private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
	...
}

public class ChangePasswordService{
	private MemberDao memberDao = new CachedMemberDao; // MemberDao에서 변경
	...
}

 

이처러머 각각 클래스에서 생성자를 직접 생성하는 경우

memberDao 를 다른 객체로 바꾸고 싶을 때, 모든 클래스의 코드를 수정해야함

 

but

생성자 주입 방식을 사용할 경우

 

// 기존
// MemberDao dao = new MemberDao();
MemberDao dao = new CachedMemberDao();
MemberRegisterService svc = new MemberRegisterService(dao);
ChangePasswordService pwdSvc = new ChangePasswordService(dao);

 

기존 MemberDao 대신 MemberDao 를 상속받아 구현 된 CachedMemberDao 객체를 사용하고 싶은 경우

위처럼 한줄 수정으로 모든 클래스에 적용 가능

 

 

... 예제 프로젝트 import는 생략 ...

 

예제 프로젝트에서도 역시 의존성 생성자 주입을 통한 클래스 구현

객체 조립기라는것 사용

 

public class Assembler{
    private MemberDao memberDao;
    private MemberRegisterService regSvc;
    private ChangePasswordService pwdSvc;
    
    public Assembler() {
        memberDao = new MemberDao();
        regSvc = new MemberRegisterService(memberDao);
        pwdSvc = new ChangePasswordService();
        pwdSvc.setMemberDao(memberDao);
    }
	...
}

 

처럼 의존성 연관된 객체들을 조립해주는 클래스를 별도로 만들어

 

Assembler assembler = new Assembler();
ChangePasswordService changePwdSvc = assembler.getChangePasswordService();

 

 

와 같이 간편하게 사용할 수 있도록 도와주는 클래스

Spring 에서는 이 조립기가 내부에 존재해서 별도로 작성 필요 X

 

 

@Configuration
public class AppCtx {
    @Bean
    public MemberDao memberDao(){
        return new MemberDao();
    }

    @Bean
    public MemberRegisterService memberRegisterSvc(){
        // 생성자를 통한 의존주입
        return new MemberRegisterService(memberDao());
    }

    @Bean
    public ChangePasswordService changePasswordSvc(){
        ChangePasswordService pwdSvc = new ChangePasswordService();
        // set메서드를 통한 의존 주입
        pwdSvc.setMemberDao(memberDao());
        return pwdSvc;
    }
    ...
}

 

@Configuration : 스프링 설정 클래스

@Bean : 스프링 빈 객체로 생성

 

직접 객체를 생성하고 등록하고 조립하는 것이 아니라

Spring Context 에 Bean을 등록하고 그 객체를 사용

 

        ctx = new AnnotationConfigApplicationContext(AppCtx.class);
        ...
        ...
        
        
        MemberRegisterService regSvc = ctx.getBean("memberRegisterSvc", MemberRegisterService.class);
    ...

 

이처럼 스프링 컨텍스트에서 memberRegisterSvc 라는 Bean을 꺼내서 사용

 

 

 

DI 방식

 

생성자 주입

private MemberDao membe Dao;

public MemberRegisterService(Membe Dao memberDao) { 
	this.memberDao = memberDao;
}

 

 

이처럼 생성자를 생성할 때, 객체를 전달받는 방식

주입받은 객체가 변하지 않고

생성자 실행 시 1회 실행 보장

 

 

수정자 주입 (Setter 메서드) 주입

 

public class MemberInfoPrinter {
    private MemberDao memDao;
    private MemberPrinter printer;

    public void printMemberInfo(String email){
        Member member = memDao.selectByEmail(email);
        if(member == null){
            System.out.println("데이터 없음\\n");
            return;
        }
        printer.print(member);
        System.out.println();
    }

    public void setMemberDao(MemberDao memberDao){
        this.memDao = memberDao;
    }

    public void setPrinter(MemberPrinter printer){
        this.printer = printer;
    }
}

 

setXXX 메서드에 의존성 객체를 불러오는 방식

객체를 불러온 이후에도, set메서드를 통해 객체를 변경할 수 있기 때문에 위험요소 존재

 

필드 주입 방식

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MemberService memberService;

}

 

책에는 안나와있지만, 클래스에 선언된 private 필드에 직접 주입하는 방식

필드주입은 외부에서 접근이 불가능하기 때문에 TDD에 어울리지 않는 방식

 

 

@Configuration 설정 클래스의 @Bean 설정과 싱글톤

스프링 컨테이너가 생성한 빈은 싱글톤 객체

Bean이 붙은 메서드에 대해 한 개의 객체만 생성하여 사용

>> 싱글톤으로 직접 구현 불필요, 매우 간편!

 

@Configuration
public lcass AppCtx {
	
    @Bean
    public MemberDao memberDao() {
    	return new MemberDao();
    }
	
    ...
}

 

여러 클래스에서 MemberDao 메서드를 실행해도 같은 객체를 리턴

실제 스프링 내부적으로는 복잡하게 구현되어 있음

@Configuration 어노테이션, 빈, @Autowired 어노테이션

 

@Autowired 어노테이션이란?

스프링 빈에 의존하는 다른 빈을 자동 주입하는 어노테이션

 

public class MemberInfoPrinter{
	
    @Autowired
	private MemberDao memberDao;
    ...
}

 

필드에 @Autowired 어노테이션을 사용하면

위 Configuration 클래스를 내부에 하나하나 @Bean 메서드를 생성하지 않아도 의존성 주입 가능

 

 

@Import 어노테이션

 

은.. 넘어가자 

 

 

 

+ Recent posts