IoC 컨테이너

Seunghee Ryu·2023년 12월 17일
1

1. 스프링 IoC 컨테이너와 빈

Inverse of Controll

  • 의존 관계 주입(Dependency Injection)이라고 하며 어떤 객체가 사용하는 의존객체를 직접 생성하지 않고 외부로부터 주입 받아 사용하는 방법

스프링 IoC 컨테이너

  • IoC 컨테이너는 스프링에서 쓰이는 여러 객체들을 생성, 관리하는 객체다 여기서 IoC 컨테이너가 관리하는 객체들을 빈 이라고 한다
  • IoC 컨테이너 내부적으로, BeanFactory 객체를 통해, 빈 설정 소스로부터 빈 정의를 읽고, 빈을 구성 및 제공한다
  • 객체를 의존성 주입을 통해 받고 싶으면, 해당 객체가 IoC 컨테이너에 등록되어 있어야 한다
  • BeanFactory : 스프링 빈 컨테이너에 접근하기 위한 최상위 인터페이스, 애플리케이션 컴포넌트의 중앙 저장소

Bean이란 ?

  • 스프링 IoC 컨테이너가 관리하는 객체

장점

  • 의존성 관리가 쉽다
  • 스코프 설정이 쉽게 가능하다
    - 싱글톤 : 흔히 알고 있는 싱글톤 패턴과 같이 하나만 생성
    - 프로포토 타입 : 빈이 생성될 때 마다 매번 다른 객체를 생성하는 방법
  • 라이프사이클 인터페이스를 지원한다

ApplicationContext

  • BeanFactory를 상속받고, 스프링에서 제공하는 다양한 기능을 사용할 수 있는 인터페이스
  • ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver 등

2. ApplicationContext

고전적인 Application 설정

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

    <bean id="bookService" class="me.freelife.BookService"> <!-- bookService 빈 등록 -->
        <!-- bookRepository 프로퍼티에 bookRepository 빈을 레퍼런스로 주입  -->
        <!-- bookRepository name은 setter에서 가지고 온 것 -->
        <!-- ref는 레퍼런스로 다른 빈을 참조한다는 뜻 -->
        <!-- ref에는 setter 에 들어갈 수 있는 다른 bean의 id가 와야됨 -->
        <property name="bookRepository" ref="bookRepository"/>
    </bean>

    <bean id="bookRepository" class="me.freelife.BookRepository"/> <!-- bookRepository 빈 등록 -->

</beans>
  • BookRepository
public class BookRepository {
}
  • BookService
public class BookService {

    BookRepository bookRepository;

    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}
  • application
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRepository != null);
    }

}

일일히 빈으로 등록하는 단점을 보완하기 위한 방법

단점을 보완하기위해 패키지를 스캔해서 @Component @Service @Repository 처럼
@Component를 확장한 애노테이션들을 스캐닝해서 빈으로 자동으로 등록해줌
이렇게 등록된 빈은 @Autowired 나 @Inject를 통해 의존성을 주입하여 사용
애너테이션 기반에 빈을 등록하고 설정하는 기능은 스프링 2.5부터 가능한기능

<!-- @Compnent @Service @Repository 애노테이션을 스캐닝 해서 빈으로 등록 해줌 -->
<context:component-scan base-package="me.freelife"/>
  • BookRepository
@Repository
public class BookRepository {
}
  • BookService
@Service
public class BookService {

    @Autowired
    BookRepository bookRepository;

    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}

java 설정 파일

  • 빈 설정 파일을 xml이 아닌 java로 설정하는 파일
@Configuration
public class ApplicationConfig {

    @Bean
    public BookRepository bookRepository() {
        return new BookRepository();
    }

    @Bean
    public BookService bookService() {
        BookService bookService = new BookService();
        bookService.setBookRepository(bookRepository());
        return bookService;
    }
}
  • BookRepository
public class BookRepository {
}
  • BookService
public class BookService {

    BookRepository bookRepository;

    public void setBookRepository(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }
}
  • application
public class Application {

    public static void main(String[] args) {
        //ApplicationConfig 를 빈 설정으로 사용하겠다는 설정
        ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);

        // ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println(Arrays.toString(beanDefinitionNames));
        BookService bookService = (BookService) context.getBean("bookService");
        System.out.println(bookService.bookRepository != null);
    }

}

ComponentScan

  • Application.class 가 있는 곳에서 ComponentScan하여 빈으로 등록
@Configuration
@ComponentScan(basePackageClasses = Application.class)
public class ApplicationConfig {

}

현재의 방식

  • 스프링 부트에서 사용하는 방식 @ComponentScan과 다수의 설정이 포함되어 있음
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
    }

}

스프링 IoC 컨테이너의 역할

  • 빈 인스턴스 생성
  • 의존 관계 설정
  • 빈 제공

AppcliationContext

  • ClassPathXmlApplicationContext (XML)
  • AnnotationConfigApplicationContext (Java)

빈 설정

  • 빈 명세서
  • 빈에 대한 정의를 담고 있다
    • 이름
    • 클래스
    • 스코프
    • 생성자 아규먼트 (constructor)
    • 프로퍼트 (setter)
    • ..

컴포넌트 스캔

  • 설정방법
    • XML 설정에서는 context:component-scan
    • 자바 설정에서 @ComponentScan
  • 특정 패키지 이하의 모든 클래스 중에 @Component 애노테이션을 사용한 클래스를 빈 으로 자동으로 등록 해 줌

3. @Autowired

required

  • @Autowired 를 처리 하다가 해당하는 빈의 타입을 못찾거나 의존성 주입을 할수 없는 경우 에러가 나고 애플리케이션 구동이 불가능함
  • 아래와 같이 설정하면 의존성 주입을 할 수없는 경우 패스함
@Autowired(required = false)
  • 생성자를 사용한 의존성 주입에는 사용할 수 없음
  • 생성자를 사용한 의존성 주입은 빈을 만들때에도 개입이 됨
  • 생성자에 전달받아야 되는 타입의 빈이 없으면 인스턴스를 만들지 못하고 서비스도 등록이 되지 않음

사용할 수 있는 위치

  • 생성자 (스프링 4.3 부터는 생략 가능)
  • 세터
  • 필드

경우의 수

  • 해당 타입의 빈이 없는 경우
  • 해당 타입의 빈이 한 개인 경우
  • 해당 타입의 빈이 여러 개인 경우
    • 빈 이름으로 시도
      • 같은 이름의 빈 찾으면 해당 빈 사용
      • 같은 이름 못 찾으면 실패

같은 타입의 빈이 여러개 일 때

  • @Primary -
  • 해당 타입의 빈 모두 주입받기 - List bookRepositories;
  • @Qualifier(빈 이름으로 주입)
  • 그외 위의 어노테이션을 사용하지 않고 필드이름과 동일하게 지정해서 의존성을 주입하면 그이름과 동일한 레파지토리를 자동으로 주입받음
@Autowired
BookRepository myBookRepository;

동작 원리

  • BeanFactory 가 자신에게 등록된 BeanPostProcessor 들을 찾아서 일반적인 Bean들에게 로직을 적용함

  • AutowiredAnnotationBeanPostProcessor 가 기본적으로 Bean으로 등록되어있음

  • BeanPostProcessor 의 라이프사이클 구현체에 의해 동작함

  • @Autowired 는 postProcessBeforeInitialization 단계

  • ApplicationRunner 의 경우 애플리케이션 구동이 다 끝나고 동작함

  • Runner을 사용하지 않으면 애플리케이션 구동 중에 처리됨

  • BeanPostProcessor
    - 아래의 세가지 라이프사이클 단계가 있다
    - postProcessBeforeInitialization
    - InitializingBean's afterPropertiesSet
    - postProcessAfterInitialization

  • Bean을 Initializer(만들다)하여 인스턴스를 만든 다음에

  • Bean의 초기화 라이프사이클(Initialization LifeCycle) 이전 OR 이후에 부가적인 작업을 할 수 있는 또다른 라이프사이클 콜백

  • Initialization

  • @PostConstruct 등의 어노태이션으로 Bean이 만들어진 이후에 해야할일을 정의 해주는것

  • InitializingBean을 implement 해서 afterPropertiesSet 메서드를 구현

4. @Component와 컴포넌트 스캔

애플리케이션 구동방법

1. 기본적인 static 메서드 사용

  • 싱글톤 스코프의 Bean들을 초기에 다 생성함 등록할 Bean이 많다면 구동시에 초기 구동시간이 꽤 걸림
SpringApplication.run(Demospring51Application.class, args);

2. Builder를 활용한 방법

3. 인스턴스를 만들어 사용하는 방법

var app = new SpringApplication(Demospring51Application.class);
app.run(args);

4. Functional 방법

  • 구동 시 리플렉션이나 Proxy 같은 cg라이브러리 기법을 안쓰므로 성능상의 이점이 있음
  • 하지만 일일히 Bean들을 등록해주기에는 너무 불편함 @ComponentScan을 사용하는 것에 비해 너무 불편함
  • lambda 적용
var app = new SpringApplication(Demospring51Application.class);
app.addInitializers(new ApplicationContextInitializer<GenericApplicationContext>() {
    @Override
    public void initialize(GenericApplicationContext ctx) {
        ctx.registerBean(MyService.class);
        ctx.registerBean(ApplicationRunner.class, new Supplier<ApplicationRunner>() {
            @Override
            public ApplicationRunner get() {
                return new ApplicationRunner() {
                    @Override
                    public void run(ApplicationArguments args) throws Exception {
                        System.out.println("Funational Bean Definition!!");
                    }
                };
            }
        });
    }

});
app.run(args);
  • lambda 적용후
var app = new SpringApplication(Demospring51Application.class);
app.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) ctx -> {
    ctx.registerBean(MyService.class);
    ctx.registerBean(ApplicationRunner.class, () -> args1 -> System.out.println("Funational Bean Definition!!"));
});
app.run(args);

컴포넌트 스캔 주요 기능

    1. 스캔 위치 설정: 어디부터 어디까지 scan할 것인가에 관한 설정
    1. 필터: 어떤 애노테이션을 스캔 할 지 또는 하지 않을 지 scan하는 중에 어떤 것을 걸러낼 것 인가에 관한 설정

Component

  • @Repository
  • @Service
  • @Controller
  • @Configuration

@ComponentScan

  • @ComponentScan은 스캔할 패키지와 애노테이션에 대한 정보
  • 다른 Bean 들을 등록하기 전에 먼저 Bean을 등록 해줌
  • 실제 스캐닝 ConfigurationClassPostProcess 라는 BeanFactoryPostProcessor에 의해 처리됨
  • 주의 사항
    - @ComponentScan 이나 @SpringBootApplication 위치를 확인 위치에 따라 다른 패키지라면 Bean으로 등록되지않음
    - @Component 로 지정이되어있는지 확인
    - @ComponentScan 에 excludeFilters(예외)로 지정된 사항은 Bean으로 등록되지 않음

5. 빈의 스코프

스코프

  • 스프링에서는 기본적으로 싱글톤 스코프를 사용함
  • 싱글톤 스코프는 애플리케이션을 초기에 구동할때 ApplicationContext를 만들때 만드므로 시간이 좀 걸림
  • 싱글톤 : 해당 빈의 인스턴스가 오직 한개 뿐임
  • 프로토타입 : 매번 새로운 객체를 만들어서 사용해야되는 스코프
    - Request
    - Session
    - WebSocket
    - etc

프로토타입 빈이 싱글톤 빈을 참조하면?

  • 아무 문제 없음

싱글톤 빈이 프로토타입 빈을 참조하면?

  • 프로토타입 빈이 업데이트가 안된다
  • 업데이트 하려면
    - scoped-proxy
    - Object-Provider
    - Provider (표준)

프로토타입으로 적용해야될 경우

  • 그냥 호출하면 싱글톤으로 호출하게 되므로 프록시로 감싸서 매번 새로운 객체가 만들어지도록 함
  • 매번 바꿔줄 수 있는 Proxy로 감싸도록 아래와 같이 설정하면 됨
  • CG라이브러리라는 써드파트 라이브러리가 CLASS도 Proxy로 만들 수 있게 해줌
  • Java JDK 안에 있는 라이브러리는 인터페이스만 Proxy로 만들 수 있음
  • 프록시 인스턴스가 만들어지고 프록시 빈을 의존성으로 주입함
@Component @Scope(value="prototype" , proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {

}
  • 넓은 생명주기의 싱글톤 스코프에서 짧은 생명 주기를 가진 스코프를 주입받아야 할 때는 위와 같이 설정하여 사용

ObjectProvider 로 지정하는 방법

  • ObjectProvider 로 지정하여 도됨 추천하지는 않음
  • 빈 설정하는 부분에만 설정하여 깔끔하게 코딩하는 것을 추천
@Component
public class Single {

    @Autowired
    private ObjectProvider<Proto> proto;

    public Proto getProto() {
        return proto.getIfAvailable();
    }
}

프록시

싱글톤 객체 사용시 주의할 점

  • 프로퍼티가 공유: 여기저기서 사용하면서 값을 바꾸면 그 값이 공유되어 위험하므로 쓰레드 세이프한 방법으로 사용해야 함
  • ApplicationContext 초기 구동시 인스턴스 생성되므로 구동하면서 시간이 좀 걸림

6-1. 프로파일

프로파일과 프로퍼티를 다루는 인터페이스

ApplicationContext extendsEnvironmentCapable
getEnvironment()

profile

  • 빈들의 묶음
  • 환경 설정(dev, prod, staging, alpha, beta, gamma...)
    - 각각의 환경에따라 다른 빈들을 사용해야되는 경우
    - 특정 환경에서만 어떠한 빈을 등록해야되는 경우
    - Environment​의 역할은 활성화할 프로파일 확인 및 설정

프로파일 유즈케이스

  • 테스트 환경에서는 A라는 빈을 사용하고 배포 환경에서는 B라는 빈을 쓰고 싶다
  • 이 빈은 모니터링 용도니까 테스트할 때는 필요가 없고 배포할 때만 등록이 되면 좋겠다

프로파일 정의하기

  • 클래스에 정의
    - @Configuration @Profile(“test”)
    - @Component @Profile(“test”)
  • 메소드에 정의
    - @Bean @Profile(“test”)

프로파일 설정하기

  • -Dspring.profiles.avtive=”test,A,B,...”
  • @ActiveProfiles​ (테스트용)

프로파일 표현식

  • ! (not)

  • & (and)

  • |(or)

  • 특정 환경(test)에서의 예시
    - Ultimate
    - intelliJ - Edit Configuration - Environment - Active profiles 에 test 입력

     - Community
     - intelliJ - Edit Configuration - Environment - VM option 에 `-Dspring.profiles.active="test"` 입력
  • Configuration 으로 프로파일 설정하는 방법

@Configuration
@Profile("test")
public class TestConfiguration {
    @Bean
    public BookRepository bookRepository() {
        return new TestBookRepository();
    }

}
  • Repository 에 직접 설정
  • Configuration으로 프로파일을 설정하는 방법은 번거로우므로 아래와 같이 설정하면 간편함
@Repository
@Profile("test")
public class TestBookRepository implements BookRepository {

}
  • !(not), &(and), |(or)
  • 아래 코드는 prod가 아니면 빈으로 등록 시킴
@Repository
@Profile("!prod & test")
public class TestBookRepository implements BookRepository {

}

6-2. Environment 2부. 프로퍼티

property

  • 다양한 방법으로 정의할 수 있는 설정값
  • Environment의 역할은 프로퍼티 소스 설정 및 프로퍼티 값 가져오기

우선순위

  • StandardServletEnvironment의 우선순위
  • ServletConfig 매개변수
  • ServletContext 매개변수
  • JNDI (java:comp/env/)
  • JVM 시스템 프로퍼티 (-Dkey="value")
  • JVM 시스템 환경변수 (운영체제 환경변수)
  • VM option에 주는 법
-Dapp.name=spring5

properties 파일로 설정하는법

    1. resources 에 app.properties 파일 생성
app.about=spring
    1. @Configuration or @SpringBootApplication 설정 파일이 있는 곳에 설정
@PorpertySource("classpath:/app.properties")
    1. Property 값 가져오기
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationContext ctx;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Environment environment = ctx.getEnvironment();
        System.out.println(environment.getProperty("app.name"));
        System.out.println(environment.getProperty("app.about"));
    }
}

스프링 부트에서 Property 가져오기

@Value("${app.name}")
String appName;

7. MessageSource

  • 국제화(i18n) 기능을 제공하는 인터페이스
    ApplicationContext 에서 상속 받은 인터페이스

  • MessageSource 직접설정 예시

  • ReloadableResourdeBundleMessageSource 로 메세지 변경 시 변경된 메세지를 반영

@Bean
public MessageSource messageSource() {
    var messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:/messages");
    messageSource.setDefaultEncoding("UTF-8");
    messageSource.setCacheSeconds(3); //캐싱하는 시간을 최대 3초까지만 캐싱하고 다시 읽음
    return messageSource;
}

스프링부트

  • 스프링 부트를 사용한다면 별다른 설정 필요없이 아래와 같이 messages.properties 사용할 수 있음
  • 원래 빈으로 각각 등록시켜줘야 하지만 스프링 부트를 쓰면 자동으로 ResourceBundleMessageSource 가 빈으로 등록되어있음
  • messages.properties
greeting=hello, {0}
  • messages_ko_KR.properties
greeting=안녕, {0}

8. ApplicationEventPublisher

  • 이벤트 프로그래밍에 필요한 인터페이스 제공 ​옵저버 패턴​ 구현체
ApplicationContext extendsApplicationEventPublisher
publishEvent(ApplicationEvent event)

이벤트 만들기

  • ApplicationEvent 상송
  • 스프링 4.2 부터는 이 클래스를 상속받지 않아도 이벤트로 사용할 수 있다.

이벤트 발생 시키는 방법

  • ApplicationEventPublisher.publishEvent();

이벤트 처리하는 방법

  • ApplicationListener<이벤트> 구현한 클래스 만들어서 빈으로 등록하기
  • 스프링 4.2 부터는 ​@EventListener​를 사용해서 빈의 메소드에 사용할 수 있다
  • 기본적으로는 synchronized
  • 순서를 정하고 싶다면 @Order와 함께 사용
  • 비동기적으로 실행하고 싶다면 @Async와 함께 사용

스프링이 제공하는 기본 이벤트

  • ContextRefreshedEvent: ApplicationContext를 초기화 했더나 리프래시 했을 때 발생

  • ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈들이 시작

    • 신호를 받은 시점에 발생
  • ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈들이 정지

    • 신호를 받은 시점에 발생.
  • ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에 발생

  • RequestHandledEvent: HTTP 요청을 처리했을 때 발생

  • 스프링 4.2 부터는 ApplicationEvent 클래스를 상속받지 않아도 이벤트로 사용할 수 있다

4.2이전 상속을 받아 Event를 정의하는 로직

public class MyEvent extends ApplicationEvent {

    private int data;
    /**
     * Create a new ContextStartedEvent.
     *
     * @param source the {@code ApplicationContext} that the event is raised for
     *               (must not be {@code null})
     */
    public MyEvent(Object source) {
        super(source);
    }

    public MyEvent(Object source, int data) {
        super(source);
        this.data = data;
    }

    public int getData() {
        return data;
    }

}

4.2 이후 Event 정의 로직

  • 스프링이 추구하는 스프링 프레임웍 코드가 전혀 들어가지 않은 POJO 기반의 프로그래밍의 비침투성 로직
  • 더 편하고 더 코드를 유지보수하기 쉬워짐
  • 이벤트는 빈이 아님
public class MyEvent {

    private int data;
    private Object source;

    public MyEvent(Object source, int data) {
        this.source = source;
        this.data = data;
    }

    public Object getSource() {
        return source;
    }

    public int getData() {
        return data;
    }

}

ApplicationEventPublisher

  • Event를 발생시키는 로직
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher publisherEvent;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        publisherEvent.publishEvent(new MyEvent(this, 100));
    }
}

ApplicationListener

  • Event를 받아서 처리하는 Handler 구현

4.2 이전 로직

@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 받았다. 데이터는 " + event.getData());
    }
}

4.2 이후 로직

@Component
public class MyEventHandler {

    @EventListener
    public void handle(MyEvent event) {
        System.out.println("이벤트 받았다. 데이터는 " + event.getData());
    }
}

Order

  • Event 실행 순서 지정
  • @Order(Ordered.HIGHEST_PRECEDENCE) 라고 설정하면 가장 먼저 실행됨
  • @Order(Ordered.HIGHEST_PRECEDENCE + 2) 라고 설정하면 조금 더 늦게 실행됨
@Component
public class MyEventHandler {

    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void handle(MyEvent event) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("이벤트 받았다. 데이터는 " + event.getData());
    }
}

@EnableAsync

  • 비동기 설정 @EnableAsync 를 설정하면 메인 Thread 가 아닌 별도의 Thread를 생성하여 수행
@SpringBootApplication
@EnableAsync
public class Ioccontainer8Application {

    public static void main(String[] args) {
        SpringApplication.run(Ioccontainer8Application.class, args);
    }

}

Handler 로직에 @Async 설정

@Component
public class MyEventHandler {

    @EventListener
//    @Order(Ordered.HIGHEST_PRECEDENCE)
    @Async
    public void handle(MyEvent event) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("이벤트 받았다. 데이터는 " + event.getData());
    }
}

LifeCycle Event

  • ContextRefreshedEvent: ApplicationContext를 초기화 했거나 리프래시 했을 때 발생
  • ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈 들이 시작신호를 받은시점에 발생
  • ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈 들이 정지신호를 받은시점에 발생
  • ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸 되는시점에 발생
  • RequestHandledEvent: HTTP 요청을 처리 했을 때 발생

9. ResourceLoader

  • 리소스를 읽어오는 기능을 제공하는 인터페이스
ApplicationContext extendsResourceLoader

리소스 읽어오기

  • 파일 시스템에서 읽어오기
  • 클래스패스에서 읽어오기
  • URL로 읽어오기
  • 상대/절대 경로로 읽어오기
Resource getResource(java.lang.String location)
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ResourceLoader resourceLoader;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Resource resource = resourceLoader.getResource("classpath:test.txt"); // 파일 읽어오기
        System.out.println(resource.exists()); // 파일 유무 
        System.out.println(resource.getDescription()); // 설명
        System.out.println(Files.readString(Path.of(resource.getURI()))); // URL로 읽어오기
    }
}

0개의 댓글