1. 웹 환경 추가
웹 스코프는 웹 환경에서만 동작하므로 라이브러리 추가
- build.gradle
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
// web 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
...
}
...
CoreApplication 에서 main() 메서드 실행시, 웹 애플리케이션이 실행되는 것을 확인할 수 있다.
- 콘솔
...
14:11:32.874 [main] INFO o.a.coyote.http11.Http11NioProtocol --Starting ProtocolHandler ["http-nio-8080"]
14:11:32.887 [main] INFO o.s.boot.tomcat.TomcatWebServer --Tomcat started on port 8080 (http) with context path '/'
14:11:32.890 [main] INFO hello.core.CoreApplication --Started CoreApplication in 0.974 seconds (process running for 1.29)
* spring-boot-starter-web 라이브러리 추가시, 스프링 부트는 내장 톰캣 서버를 활용해 웹 서버와 스프링을 함께 실행
* 스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext 기반으로 애플리케이션을 구동한다.
웹 라이브러리가 추가되면 웹 관련 추가 설정과 환경들이 필요하므로 AnnotationConfigServletWebServerApplicationContext 기반으로 애플리케이션을 구동한다.
만약 기본 포트인 8080 포트를 다른 곳에서 사용중이면 오류가 발생하므로 포트 변경이 필요하다.
- src/main/resources/application.properties
spring.application.name=core
server.port=9090
2. request 스코프 예제 개발
동시에 여러 HTTP 요청이 오면 어떤 요청이 남긴 로그인지 구분이 힘들다. 이 때 request 스코프를 활용해 구분할 수 있는 로그를 남기도록 개발한다.
- 예시 콘솔
[d06b992f...] request scope bean create
[d06b992f...][http://localhost:8080/log-demo] controller test
[d06b992f...][http://localhost:8080/log-demo] service id = testId
[d06b992f...] request scope bean close
공통 포멧 : UUID {message}
UUID 를 사용해 HTTP 요청을 구분한다.
requestURL 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인한다.
1) MyLogger (로그 출력을 위한 클래스)
- src/main/java/hello/core/common/MyLogger.java
package hello.core.common;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
@Scope(value = "request")
public class MyLogger {
private String uuid;
private String requestURL;
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String message) {
System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message);
}
@PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
System.out.println("[" + uuid + "] request scope bean create: " + this);
}
@PreDestroy
public void close() {
System.out.println("[" + uuid + "] request scope bean close: " + this);
}
}
· @Scope(value = "request") 를 사용해 request 스코프로 지정한다.
이제 이 빈은 HTTP 요청당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
· 이 빈이 생성되는 시점에 @PostConstruct 초기화 메서드를 사용해 UUID 를 생성하여 저장한다.
이 빈은 HTTP 요청당 하나씩 생성되므로, UUID 를 저장하면 다른 HTTP 요청과 구분할 수 있다.
· 이 빈이 소멸되는 시점에 @PreDestroy 소멸 메서드를 사용해 종료 메시지를 남긴다.
· 이 빈이 생성되는 시점에 requestURL 을 알 수 없으므로, 외부에서 setter 로 입력 받는다.
2) LogDemoController (MyLogger 가 잘 작동하는지 확인하는 테스트 컨트롤러)
- src/main/java/hello/core/web/LogDemoController.java
package hello.core.web;
import hello.core.common.MyLogger;
import hello.core.logdemo.LogDemoService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final MyLogger myLogger;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) {
String requestURL = request.getRequestURL().toString();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
· HttpServletRequest 를 통해서 요청 URL을 받아온다.
-> requestURL 값 : http://localhost:8080/log-demo
이렇게 받은 requestURL 값을 MyLogger 에 저장한다.
· MyLogger 는 HTTP 요청당 각각 구분되므로 다른 HTTP 요청 때문에 값이 섞이는 걱정이 사라진다.
· 컨트롤러에서 "controller test"라는 로그를 남긴다.
* requestURL 값을 MyLogger 에 저장하는 부분은 컨트롤러 보다 공통 처리가 가능한 스프링 인터셉터나 서블릿 필터를 활용하는 것이 좋다.
스프링 웹에 익숙하다면, 인터셉터를 활용해 구현해본다.
* 인터셉터
HTTP 요청이 컨트롤러 호출 직전에 어떤 작업을 공통화해서 처리할 수 있게 해주는 인터셉터 클래스
3) LogDemoService (비즈니스 로직이 있는 서비스 계층에서 로그 출력)
- src/main/java/hello/core/logdemo/LogDemoService.java
package hello.core.logdemo;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
· 만약 request scope를 사용하지 않고 파라미터로 모든 정보를 서비스 계층에 넘기면, 파라미터가 많이 지저분해진다.
requestURL 같은 웹 관련 정보는 웹과 관련없는 서비스 계층까지 넘어오면 안 되고, 컨트롤러까지만 사용해야 한다.
서비스 계층은 웹 기술에 종속되지 않고 순수하게 유지하는 것이 유지보수 관점에서 좋다.
· request scope의 MyLogger 덕분에 웹 관련 정보를 서비스 계층에 파라미터로 넘기지 않고 MyLogger 필드(멤버 변수)에 저장해서 코드와 계층을 깔끔하게 유지할 수 있다.
4) 실행
애플리케이션 실행 시점에 오류 발생)
- 콘솔
13:40:39.812 [main] ERROR o.s.boot.SpringApplication --Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'logDemoService' defined in file [C:\Users\admin\IdeaProjects\core\build\classes\java\main\hello\core\logdemo\LogDemoService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1382)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1221)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:565)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:525)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:371)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:331)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1218)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1184)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1121)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:994)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:621)
at org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:756)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:445)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1365)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354)
at hello.core.CoreApplication.main(CoreApplication.java:10)
Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:381)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:201)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:1225)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1704)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1651)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:912)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 21 common frames omitted
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:130)
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:43)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:369)
... 27 common frames omitted
스프링 애플리케이션 실행시 오류가 발생한다.
핵심 에러 메시지 : Error creating bean with name 'myLogger': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
스프링 애플리케이션 실행 시점에 싱글톤 빈은 생성해서 주입이 가능하지만, request 스코프 빈은 아직 생성되지 않는다. 이 빈은 실제 고객의 요청이 와야 생성할 수 있기 때문이다!
참고링크 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8?cid=325969
스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 49,600명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프
www.inflearn.com
'강의 실습 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 스코프와 프록시 (0) | 2026.05.12 |
|---|---|
| 스코프와 Provider (0) | 2026.05.11 |
| 웹 스코프 (0) | 2026.05.09 |
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결 (0) | 2026.05.09 |
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점 (0) | 2026.05.07 |
댓글