SpringCore-3일차

박상원·2024년 5월 13일

spring

목록 보기
3/15

Advice

  • Advice는 포인트컷과 관련하여 메소드 실행 전, 후, 전/후 를 결정하기위해 사용한다.
Advice형태설명
BeforeJoin Point 앞에서 실행할 Advice
AfterJoin Point 뒤에서 실행할 Advice
AfterReturningJoin Point가 완전히 정상 종료한다음 실행하는 Advice
AroundJoin Point 앞과 뒤에서 실행되는 Advice
AfterThrowingJoin Point에서 예외가 발생했을 때 실행되는 Advice

JoinPoint 활용

  • 모든 Advice 메서드에는 첫 번째 인자로 JoinPoint를 받을 수 있다. (Around Advice는 JoinPoint의 서브 클래스인 ProceedingJoinPoint를 반드시 사용해야 한다.

JoinPoint의 메서드

  • getArgs(): 타겟 메서드의 인자.
  • getThis(): 프록시 객체
  • getTarget(): 타겟 객체
  • getSignature(): 타겟 객체의 메서드 시그니쳐
  • toString(): 타겟 객체의 메서드 정보

Spring AOP vs Full AspectJ

Spring AOPAspectJ
구현순수 자바자바 언어 확장 사용
GoalSimple SolutionComplete Solution
특징별도의 컴파일 과정 불필요AspectJ compiler(ajc)가 필요
WeavingRuntime weavingcompile-time, post-compile, load-time weaving 지원
대상Spring Container에 의해서 관리되는 Spring Bean모든 객체들
JoinPointMethod 실행시에만 가능Method 실행시, Constructor 실행시, field 참조시, field 할당시 등등
성능비교적 느림비교적 빠름

Spring AOP Proxies

  • Spring AOP는 JDK Proxy와 CGLIB을 활용하여 AOP기능을 제공한다.
  • Target 메서드가 실행되는 시점에 IoC 컨테이너에 의해 Proxy 빈을 생성한다.(Runtime Weaving)
  • TargetObject(스프링 빈)가 인터페이스를 구현한 경우 JDK Proxy를 사용하고 그렇지 않은 경우는 CGLIB Proxy를 사용한다.

CGLIB 프록시 강제

  • 인터페이스의 존재와 상관없이 CGLIB을 사용하도록 설정하려면 @EnableAspectAutoProxy 어노테이션의 proxyTargetClass 속성을 true로 주면 된다.

IoC 컨테이너와 AOP Proxy의 관계

먼저 Spring AOP는 Proxy의 메커니즘을 기반으로 AOP Proxy를 제공하고 있다.

다음 그림처럼 Spring AOP는 사용자의 특정 호출 시점에 IoC 컨테이너에 의해 AOP를 할 수 있는 Proxy Bean을 생성해준다. 동적으로 생성된 Proxy Bean은 타깃의 메서드가 호출되는 시점에 부가기능을 추가할 메서드를 자체적으로 판단하고 가로채어 부가기능을 주입해주는데 이처럼 호출 시점에 동적으로 위빙을 하여 런타임 위빙(Runtime Weaving)이라 한다.

따라서 Spring AOP는 런타임 위빙의 방식을 기반으로 하고 있으며, Spring에선 런타임 위빙을 할 수 있도록 상황에 따라 JDK Dynamic Proxy와 CGLIB 방식을 통해 Proxy Bean을 생성을 해준다.

두 가지 AOP Proxy는 어떠한 상황에 생성하게 될까?

Spring은 AOP Proxy를 생성하는 과정에서 자체 검증 로직을 통해 타깃의 인터페이스 유무를 판단한다.

Target이란 횡단기능(Advice)이 적용될 객체(Object)를 뜻한다.

이때 만약 타깃이 하나 이상의 인터페이스를 구현하고 있는 클래스라면 JDK Dynamic Proxy의 방식으로 생성되고 인터페이스를 구현하지 않은 클래스라면 CGLIB의 방식으로 AOP 프록시를 생성해준다.

Spring AOP의 근간이 되는 JDK Dynamic Proxy 방식

우선 JDK Dynamic Proxy란 Java의 리플렉션 패키지에 존재하는 Proxy라는 클래스를 통해 생성된 Proxy 객체를 의미한다.

리플랙션의 Proxy 클래스가 동적으로 Proxy를 생성해준다하여 우리가 아는 JDK Dynamic Proxy라 불리는 것이다. 이 클래스를 사용하여 Proxy를 생성하기 위해선 몇 가지 조건이 있지만, 아무래도 그 중 핵심은 타깃의 인터페이스를 기준으로 Proxy를 생성해준다는 점이다.

무엇보다 Spring AOP는 JDK Dynamic Proxy를 기반으로 AOP 기술을 구현했을 만큼, JDK Dynamic Proxy가 어떻게 Proxy를 생성하는지에 대한 부분은 Spring AOP를 통해 Aspect를 구현한다면 꼭 짚고 넘어가야할 부분이다.

  • 타깃의 인터페이스를 자체적인 검증 로직을 통해 ProxyFactory에 의해 타깃의 인터페이스를 상속한 Proxy 객체 생성
  • Proxy 객체에 InvocationHandler를 포함시켜 하나의 객체로 변환

다음과 같이 Proxy를 생성하는 과정에서 핵심적인 부분은, 무엇보다 인터페이스를 기준으로 Proxy 객체를 생성해 준다는 점이다. 따라서 구현체는 인터페이스를 상속받아야하고, @Autowired를 통해 생성된 Proxy Bean을 사용하기 위해선 반드시 인터페이스의 타입으로 지정해줘야 된다.

인터페이스 기준 그리고 내부적인 검증 코드

다른 관점에서 보자면 JDK Dynamic Proxy는 Proxy 패턴의 관점을 구현한 구현체라 할 수 있다.

이 Proxy 패턴은 점근제어의 목적으로 Proxy를 구성한다는 점도 중요하지만, 무엇보다 사용자의 요청이 기존의 타깃을 그대로 바라볼 수 있도록 타깃에 대한 위임코드를 Proxy 객체에 작성해줘야 한다. 생성된 Proxy 객체의 타깃에 대한 위임코드는 바로 InvocationHandler에 작성해줘야 한다.

CGLIB(Code Generator Library)

CGLIB는 Code Generator Library의 약자로, 클래스의 바이트코드를 조작하여 Proxy 객체를 생성해주는 라이브러리이다.

Spring은 CGLIB을 사용하여 인터페이스가 아닌 타깃의 클래스에 대해서도 Proxy를 생성해주고 있는데 CGLIB은 Enhancer라는 클래스를 통해 Proxy를 생성할 수 있다.

다음과 같이 CGLIB은 타깃 클래스를 상속받아 다음 그림과 같이 Proxy를 생성해준다.

이 과정에서 CGLIB은 final 메서드 또는 클래스에 대해 재정의를 할 수 없으므로 Proxy를 생성할 수 없다는 단점이 있지만, CGLIB은 바이트 코드로 조작하여 Proxy를 생성해주기 때문에 성능에 대한 부분이 JDK Dynamic Proxy보다 좋다.

성능의 차이

성능의 차이의 근본적인 이유는 CGLIB은 타깃에 대한 정보를 제공받기 때문이다.

  • 메소드가 처음 호출되었을 때 동적으로타깃의 클래스의 바이트 코드를 조작
  • 이후 호출시엔 조작된 바이트 코드를 재사용

CGLIB은 성능이 좋긴 하지만, Spring은 JDK Dynamic Proxy를 기반으로 Proxy를 생성해주고 있다.

권장하지 않았던 CGLIB

이러한 이유엔, 기존의 CGLIB은 3가지의 한계가 존재했기 때문이다.

  • net.sf.cglib.proxy.Enhancer 의존성 추가
  • default 생성자
  • 타깃의 생성자 두 번 호출

Spring boot로 넘어가면서 다음의 3가지 한계가 개선이 되었고 CGLIB방식을 사용하게 되었다.

AOP가 동작하지 않는 경우

  • 스프링 빈 내부에서 내부 메서드를 실행하는 경우, Proxy가 개입할 수 없기 때문에 AOP가 동작하지 않는다.

예외처리

Exception Handling 이라고 하며, 잘못된 하나로 인해 전체 시스템이 무너지는 결과를 방지하기 위한 기술적인 처리이다. java에서는 예외와 에러도 객체로 처리한다.

예외가 주로 발생하는 원인

  • 사용자의 잘못된 데이터 입력
  • 잘못된 연산
  • 개발자가 로직을 잘못 작성
  • 하드웨어, 네트워크 오작동
  • 시스템 과부하

Throwable 클래스

Throwable 클래스는 예외처리를 할 수 있는 최상위 클래스이다. Exception과 Error는 Throwable의 상속을 받는다.

에러(Error)와 예외(Exception)

  • 에러는 시스템 레벨에서 발생하여, 개발자가 어떻게 조치할 수 없는 수준을 의미 ex) JVM OOM
  • 예외는 개발자가 구현한 로직에서 발생하며, 개발자가 다른 방식으로 처리가능한 것들, JVM은 정상동작. ex) 인덱스범위 검사

예외 (Exception)

예외의 2가지 종류

  • Checked Exception : 예외 처리가 필수이며, 처리하지 않으면 컴파일X. JVM 외부와 통신(네트워크, 파일시스템 등)할 때 주로 사용
    • RuntimeException 이외에 있는 모든 예외
    • IOException, SQLException 등
  • Unchecked Exception : 컴파일 때 체크되지 않고, runtime에 발생하는 Exception을 말한다.
    • RuntimeException 하위의 모든 예외
    • NullPointException, IndexOutOfBoundException 등

에러 (Error)

개발자는 error가 발생했다는 사실을 알 수는 있다. 유명한 에러로는 다음과 같다.

  • OutOfMemoryError : JVM에 설정된 메모리의 한계를 벗어난 상황일 때 발생한다. 힙 사이즈가 부족하거나, 너무 많은 class를 로드할 때, 가용가능한 swap이 없을 때, 큰 메모리의 native메서드가 호출될 때 등이 있다. 이를 해결하기 위해 dump 파일분석, jvm 옵션 수정 등이 있다.

Thread pool과 DB Connection pool은 공유되는 자원이다.

StereoType은 언제 어떻게 사용되는가?

  • useCase를 @Service를 사용해서 만든다.
  • useCase라고 하기에는 자잘한 것들은 @Component로 만든다.

thread safe 하게 만드는 기본적인 방법은 공유자원을 만들지 않는 것이다.
@Bean과 StereoType Annotation은 서로 같은 bean이고 설정은 로직에 의존하면 안되고 로직은 설정에 의존해야 한다.

Aspect도 Bean으로 등록을 해야 ApplicationContext가 Weaving을 해줄 수 있다.

Spring Framework Testing


테스트 개요

  • 테스트의 종류
    • 단위 테스트
    • 통합 테스트
    • 시스템 테스트
    • 인수 테스트
    • 회귀 테스트(Regression Test)
  • 테스트에 필요한 것들
    • 테스트 프레임워크(Junit)
    • Mocking(Mockito)

단위 테스트

  • Dependency Injection를 지향하기 때문에 웹컨테이너나 다른 컴포넌트에 의존성이 없어 쉽게 단위 테스트를 할 수 있다.
  • POJO 기반의 스프링 빈 코드는 스프링 프레임워크 없이 테스트 코드 내에서 new 연산자로 쉽게 생성할 수 있다.

단위 테스트 지원 클래스 - ReflectionTestUtils

  • Reflection 기반의 유틸리티 메서드를 제공한다.
  • 테스트시 public이 아닌 필드, 메소드 등에 접근할 수 있도록 도와준다.
    • 스프링 프레임워크는 private, protected 필드에 @Autowired, @Inject, @Resource 어노테이션을 설정하여 DI를 수행하므로 테스트에서 필요한 경우가 많다.

AopTestUtils

  • AOP 관련 유틸리티를 제공한다.
  • AOP Proxy 뒤에 숨은 타겟 객체를 가져오느 기능을 제공한다.

단위 테스트와 Mockito

  • mokito는 JUnit위에서 동작하며 Mocking과 Verification을 도와주는 유닛 테스트를 위한 프레임워크이다.

Spring Integration Test

  • spring framework 설정을 모두 포함한 통합 테스트를 위해서 @ContextConfiguration을 사용할 수 있다.

@ContextConfiguration

  • 통합 테스트를 위한 클래스 레벨 어노테이션이다.
  • 테스트 동안 로딩할 어플리케이션 컨텍스트 리소스(beans.xml, JavaConfig.java) 위치를 지정할 수 있다.

주의

bean 내부에서 생성자 주입방식을 쓰고 주입되는 필드가 final일 경우 @InjectMocks가 동작하지 않는다.

@SpringJUnitConfig

  • @ExtendWith과 @ContextConfiguration을 포함한 메타 어노테이션이다.

단위 테스트

  • 테스트가 가능한 최소 단위로 나누어진 소프트웨어(모듈, 프로그램, 객체, 클래스 등) 내에서 결함을 찾고 그 기능을 검증하는 것.
  • 구현 단계에서 각 모듈이 구현된 후에 단위 테스트를 수행
  • 모듈을 단독적으로 실행할 수 있는 환경이 필요하다.

테스타 데이터를 테스트 드라이버에 입력으로 넣고, 모듈을 호출하고 모듈 아래의 테스트 스텁들을 호출하여 연산을 하고, 모듈은 테스트 드라이버에 실행결과를 반환

테스트 드라이버

  • 테스트 대상이 되는 모듈을 호출하여 준비한 테스트 데이터를 제공하고 모듈의 실행결과를 받는 모듈
  • 일반적으로 상향식 테스트에서 아직 통합되지 않은 상위 컴포넌트의 동작을 시뮬레이션 하기 위헤 사용

테스트 스텁

  • 호출되는 모듈의 개발이 완료되지 않은 경우, 호출하는 모듈을 시험하기 위해 생성한 더미 모듈
  • 함수와 헤더 등의 코드 루틴만 정의하고 내부 코드는 제한적으로 구현하거나 구현하지 않는 경우가 많음

통합 테스트

  • 모듈을 통합하는 과정에서 수행되는 테스트
  • 모듈 간의 상호 작용이 올바르게 되는지를 검사하는 테스트
  • 통합 테스트에서 오류가 발생되는 경우
    • 개별적인 모듈에 대한 테스트가 불충분하여 오류가 발생
    • 개별 모듈에서 동일한 전역변수를 사용하는 등의 실수로 인해 모듈간의 예기치 못한 상호작용이 발생하여 오류가 발생

시스템 테스트

  • 통합 테스트가 완료된 후에 완전히 시스템에 대해 수행하는 테스트
  • 단위 테스트나 통합 테스트가 기능이 올바르게 수행되는지를 검증하는 것에 중점을 둔다면, 시스템 테스트는 시스템의 기능 측면에서 뿐만 아니라, 비기능적인 요구사항도 만족되는지를 검증한다.
    • 비기능 : 사용성, 견고성, 신뢰성, 보안성, 성능

인수 테스트

  • 실제 사용자 환경에서, 사용자의 입장으로 테스트 수행
  • 인수 기준을 만족하는 가를 검사하는 것이 주요 목적이다.
  • 시스템 테스트에서 사용한 테스트 케이스들을 이용할 수 있다.
  • 인수 테스트 유형에는 알파테스트/베타테스트가 있다.
    • 알파테스트 : 사용자에 의해 테스트하는데 개발자 환경
    • 베타테스트 : 클로즈베타(제한된실유저, 실환경), 오픈베타(누구나실유저, 실환경)

회귀 테스트

  • 소프트웨어가 수정된 후에 변경이 올바르게 되었는지를 검사하기 위한 테스트
  • 프로그램 수정 전에 정상적으로 동작했던 기능들이 수정된 후에도 여전히 동작하는지 시험
  • 수정되기 전에 작성된 테스트 케이스를 실행하여 수정전의 기능들이 정상적으로 동작하는지 확인한다.

Spring Core가 제공하는 기타 기능


Resource

  • Java에서 제공하는 URL 관련 처리가 제공되지만 부분적으로만 제공된다.
    • Classpath에서 자원을 얻는 표준 구현체가 제공되지 않는다.

Resource 인터페이스

  • Resource 인터페이스를 통해 다음의 기능을 사용할 수 있다.
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    boolean isFile();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    ReadableByteChannel readableChannel() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();
}
  • InputStreamSource를 상속했기 때문에 InputStream을 받을 수 있는 메서드도 사용할 수 있다.
public interface InputStreamSource {

    InputStream getInputStream() throws IOException;
}

스프링이 제공하는 Resource 구현체

UrlResource

  • file:, http:, https:, ftp: 등으로 시작하는 url 리소스로 부터 리소스를 받아오는 기능을 제공한다.
class ConsoleLogProcessorTest {
    @Test
    void testUrlResource() throws Exception {
        try (InputStream inputStream = new UrlResource("https://www.manty.co.kr").getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

ClassPathResource

  • 리소스를 클래스패스에서 가져오는 기능을 제공한다.
  • getInputStream() 메서드를 사용하면 jar 파일 내부의 정보도 가져올 수 있다.
    @Test
    void testClassPathResource() throws Exception {
        try (InputStream inputStream = new ClassPathResource("data/test.txt").getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }

FileSystemResource

  • 절대 경로로 파일시스템에서 리소스를 가져오는 기능을 제공한다.
   @Test
    void testFileSystemResource() throws Exception {
        try (InputStream inputStream = new FileSystemResource("/etc/hosts").getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }

PathResource

  • Path 객체를 넣어주고 사용하는 리소스이다.
    @Test
    void testPathResource() throws Exception {
        try (InputStream inputStream = new PathResource(Paths.get("/etc/hosts")).getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;
            while((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }

ServletContextResource

  • 웹 프로젝트에서 사용할 수 있다.

InputStreamResource

  • inputStream을 넣어주고 사용하는 리소스이다.

ByteArrayResource

  • byteArray를 넣어주고 사용하는 리소스이다.

Class.getResource()와 ClassLoader.getResource()의 차이

Class.getResource()는 결국 ClassLoader.getResource()에 위임(delegates)하게 된다. 때문에 두 개의 메서드는 실제로 매우 비슷하다. 하지만 첫 번째 메서드가 좀 더 좋다.

첫 번째 메서드가 더 좋은 이유는 ClassLoader.getResource() 메서드는 절대경로로 전부 적어줘야 해서 경로가 바뀌었을 때 전부 변경해줘야 하기 때문이다. 하지만 getClass()를 사용해서 getResource() 메서드를 사용하게 되면 현재 실행 클래스를 기준으로 찾기 때문에 해당 클래스 경로에 데이터를 넣어주거나 현재 클래스 이름.class.getResource()를 사용하여 오류를 피할 수 있다.

Null-Safety

  • null safety는 말 그대로 null에게서 안전한 프로그램 코드를 작성하는 것을 의미한다.
  • 여기서 주의 해야 할 점은 null safety라는 용어가 null을 없애자는 것이 아니라는 것이다.(null은 훌륭한 데이터 자료형 중 하나이다.)
  • 우리가 주목해야 하는 문제는, null 자체가 아니라 예상치 못한 null을 대응하지 못하는 함수이다.

Spring Null-Safety

  • Java에서 제공하지 않는 Null-Safety 표기를 위한 어노테이션을 스프링이 제공한다.(from 스프링5)

@Nullable

  • 특정 파라미터, 반환값, 필드가 null로 설정할 수 있음을 표시하는 어노테이션이다.

@NonNull

  • 특정 파라미터, 반환값, 필드가 null로 설정할 수 없음을 표시하는 어노테이션이다.

@NonNullApi

  • 패키지 레벨에서 파라미터, 반환값을 null로 설정할 수 없음을 표시하는 어노테이션이다.

@NonNullFields

  • 패키지 레벨에서 필드에 null로 설정할 수 없음을 표시하는 어노테이션이다.

logback 설정

  • 스프링 부트에서 기본적으로 logback을 제공하고 있기 때문에 logback 설정에 대해 가이드 한다.
  • logback 설정은 logback.xml 파일을 클래스패스 루트에 생성하여 적용한다.

logger

  • Logger는 태그를 통하여 구성된다. 필수 name 속성, 선택적으로 level 속성과 additivity 속성을 가진다.
  • level 속성은 대소문자를 구분하지 않으며 trace, debug, info, warn, error, all, off 중 하나의 값으로 지정할 수 있다.
  • 특별한 level 값으로는 inherited 또는 null이 존재하는데, 이는 자신이 상속하는 상위 로거의 level을 그대로 따른다.
  • additivity 속성은 true와 false의 값을 가질 수 있다. 이는 appender의 cumulative 속성에 대하여 설정한다.
  • 태그는 0개 이상의 태그를 포함할 수 있다. 각 태그로 포함된 appender는 명명된 로거에 추가된다.

root logger

  • 루트 로거는 태그를 통하여 구성된다. 루트 로거의 속성으로는 오직 단 하나의 level 속성만 허용된다.
  • 루트 로거의 이름은 이미 "ROOT"로 정해져 있기에 name 속성도 포함하지 않는다.
  • level 속성으로는 기존 logger와 동일하게 level 값들을 가질 수 있다. 하지만, 가장 최상단의 로거이기 때문에 inherited 혹은 null 값은 level 값으로 가질 수 없다.

appender

  • Appender는 태그를 통하여 구성되며 name와 class 속성을 필수적으로 가져야만 한다.
  • name 속성은 appender의 이름을 명시하며, class 속성은 인스턴스화 시킬 appender 클래스를 명시하여야 한다.
  • 태그는 0개 또는 1개의 태그와 0개 이상
  • 태그는 JavaBean으로 등록된 appender 클래스를 다수 포함할 수 있다.

업로드중..

appender 누적

  • 기본적으로 appender는 누적되는 성질을 가지고 있다.
  • logger는 자신에게 부착된 appender 이외에 자신의 상위 logger의 appender에게 로그를 전달한다.
  • 그렇기에 같은 appender를 다른 logger에 등록한다면 로그의 중복 출력에 주의하여야 한다.
  • logger에는 additivity 속성이 존재한다. 이 속성은 appender accumlate(축적하다)를 컨트롤하기 위한 속성이다.
  • additivity=false로 설정된 경우 로깅이 발생할 때 상위 로거의 appender로 전달되지 않는다.

변수 선언 및 치환

<configuration>

  <property name="USER_HOME" value="/home/nhn" />

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${USER_HOME}/myApp.log</file>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="FILE" />
  </root>
</configuration>
  • 변수는 태그 대신 시스템 변수로 전달하여도 위의 configuration과 동일하게 설정할 수 있다.
java -DUSER_HOME="/home/nhn" MyApp

Environment 추상화


Environment interface

public interface Environment extends PropertyResolver {
  String[] getActiveProfiles();

  String[] getDefaultProfiles();

  boolean acceptsProfiles(Profiles profiles);
}
public interface PropertyResolver {
  boolean containsProperty(String key);

  String getProperty(String key);

  String getProperty(String key, String defaultValue);

  // ..
}
  • myApplicatio.property 같은 설정도 Environment에서 관리하고 있음
Environment env = context.getBean(Environment.class);
System.out.println(env.getProperty("from"));

Profiles

  • 프로필은 Bean 정의의 명명된 논리적 그룹입니다.
  • 해당 프로필이 활성화된 경우에만

java command -d는 vm 옵션을 의미한다.

java command line option

  • spring.profiles.active
-Dspring.profiles.active=dev

Code Sample

@Configuration
public class MainConfig {

    @Bean("envGreeter")
    @Profile({"dev", "default"})
    public Greeter devGreeter(){
        return new Greeter() {
            @Override
            public boolean sayHello() {
                System.out.println("Dev Hi!");
                return true;
            }
        };
    }

    @Bean("envGreeter")
    @Profile("real")
    public Greeter realGreeter(){
        return new Greeter() {
            @Override
            public boolean sayHello() {
                System.out.println("Real Hi!");
                return true;
            }
        };
    }
}
  • 더 복합한 것을 원한다면 @Conditional을 사용한다.

콘솔 출력창 테스트


System.setOut

출력하려는 값을 OutputStream 객체에 저장하기 위해 기본적으로 설정되어 있는 System.out의 값을 내가 사용하고 싶은 타입의 객체로 변경하는 방법

private static ByteArrayOutputStream outputMessage = new ByteArrayOutputStream();
System.setOut(new PrintStream(outputMessage));

System의 out은 PrintStream 타입이기 때문에 PrintStream 객체를 생성한 값으로 지정한다.

테스트 종료 후 정상적인 콘솔창 출력을 위해서 다시 되돌려놓는 과정이 필요하다.

System.setOut(System.out);

0개의 댓글