반응형

POJO 구성 방식

14. JoinPoint 정보 가져오기

AOP 에서 어드바이스는 여러 조인포인트, 즉 프로그램 실행지점 곳곳에 적용된다. 어드바이스가 정확하게 작동하려면 조인포인트에 대한 세부 정보가 필요한 경우가 존재한다.

 

어드바이스 메소드의 Signature 에 org.aspectj.lang.JoinPoint 형 인수를 선언하면 조인포인트 정보를 얻을 수 있다. 다음과 같이 logJoinPoint 어드바이스에서 조인포인트 정보를 액세스한다고 하자. 필요한 정보는 조인포인트 유형, 메소드 시그니처, 인수값, 대상 객체와 프록시 객체이다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Before("execution(* *.*(..))") 
    public void logJoinPoint(JoinPoint joinPoint) {
        log.info("Join point kind : {}", joinPoint.getKind());
        log.info("Signature declaring type : {}", joinPoint.getSignature().getDeclaringTypeName());
        log.info("Signature name : {}", joinPoint.getSignature().getName());
        log.info("Arguments : {}", Arrays.toString(joinPoint.getArgs()));
        log.info("Target class : {}", joinPoint.getTarget().getClass().getName());
        log.info("This class : {}", joinPoint.getThis().getClass().getName());
    }
}

 

프록시로 감싼 원본 빈은 Target Object 라고 하며 프록시 객체는 this 로 참조한다. 대상 객체와 프록시 객체는 각각 조인포인트에서 getTarget(), getThis() 메소드로 가져올 수 있다. 실행 결과는 다음과 같다.

더보기

Join point kind : method-execution
Signature declaring type : com.apress.springrecipes.calculator.ArithmeticCalculator
Signature name : add
Arguments : [1.0, 2.0]
Target class : com.apress.springrecipes.calculator.ArithmeticCalculatorImpl
This class : com.sun.proxy.$Proxy18

 

15. @Order 로 애스펙트 우선순위 설정

같은 조인포인트에 애스펙트를 여러 개 적용할 경우, 우선순위를 정해야 한다. 애스펙트 간 우선순위는 Ordered 인터페이스를 구현하거나 @Order 어노테이션을 붙여 지정할 수 있다.

 

@Aspect
@Component
public class CalculatorVaildationAspect {
	
    @Before("execution(* *.*(double, double))")
    public void validateBefore(JoinPoint joinPoint) {
    	for (Object arg : joinPoint.getArgs()) {
        	validate((Double) arg);
        }
    }
    
    private void validate(double a) {
    	if (a < 0) {
        	throw new IllegalArgumentException("Positive Numbers only");
        }
    }
}

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    @Before("execution(* *.*(..))")
    public void logBefore(JoinPoint joinPoint) {
    	// ...
    }
}

 

위와 같이 두 애스펙트가 존재할 경우 어느 쪽을 먼저 적용해야 할 지 알 수가 없다. 이런 경우 어느 한 애스팩트가 다른 것보다 먼저 실행되게 하기 위해 우선순위를 설정해야 한다. 두 애스펙트 모두 Ordered 인터페이스를 구현하거나 @Order 어노테이션을 활용하면 된다.

 

먼저 Ordered 인터페이스를 구현할 경우, getOrder() 메소드가 반환하는 값이 작을수록 우선순위가 높다. 다음과 같이 작성할 경우 검증 애스펙트가 로깅 애스펙트보다 우선순위가 더 높아진다.

@Aspect
@Component
public class CalculatorVaildationAspect implements Ordered {
	
    ...
    public int getOrder() {
    	return 0;
    }
}

@Aspect
@Component
public class CalculatorLoggingAspect implements Ordered {
	
    ...
    public int getOrder() {
    	return 1;
    }
}

 

아래와 같이 @Order 에 우선순위값을 넣으면 더 깔끔하게 구현이 가능하다.

@Aspect
@Component
@Order(0)
public class CalculatorVaildationAspect { ... }

@Aspect
@Component
@Order(1)
public class CalculatorLoggingAspect { ... }

 

16. 애스펙트 포인트컷 재사용

포인트컷 표현식을 여러 번 되풀이해 사용할 경우에, 어드바이스 어노테이션에 직접 써넣는 것보다 재사용할 방법을 필요로 하게 된다. @Pointcut 어노테이션을 활용하면 포인트컷만 따로 정의해 여러 어드바이스에서 재사용이 가능하다.

 

애스펙트에서 포인트컷은 @Pointcut 을 붙인 단순 메소드로 선언할 수 있다. 포인트컷과 어플리케이션 로직이 뒤섞이는 것은 바람직하지 않으니 메소드 바디는 보통 비워두고 포인트컷의 가시성은 메소드의 수정자로 조정한다. 이렇게 선언한 포인트컷은 다른 어드바이스가 메소드명으로 참조할 수 있다.

 

@Aspect
@Component
public class CalculatorLogginAspect {
	
    ...
    @Pointcut("execution(* *.*(..))")
    private void loggingOperation() {}
    
    @Before("loggingOperation()")
    public void logBefore() { ... }
    
    @After("loggingOperation()")
    public void logAfter() { ... }
}

 

여러 애스펙트가 포인트컷을 공유하는 경우라면 공통 클래스 한 곳에 포인트컷을 모아두는 편이 좋다. 이 때 포인트컷 메소드는 public 으로 선언한다.

@Aspect
public class CalculatorPointcuts {
	
    @Pointcut("execution(* *.*(..))")
    public void loggingOperation() {}
}

 

외부 클래스에 있는 포인트컷을 참조할 때는 클래스명도 함께 적는다. 만약 다른 패키지에 있는 경우는 패키지명까지 기재한다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    @Before("CalculatorPointcuts.loggingOperation()")
    public void logBefore() { ... }
    
    @After("CalculatorPointcuts.loggingOperation()")
    public void logAfter() { ... }
}

 

#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

반응형

스프링 AOP (Aspect-Oriented Programming)

AOP 란, Aspect-Oriented Programming 의 약자로 관점 지향 프로그래밍이라 불린다. 관점 지향은 쉽게 표현하자면 어떤 로직을 핵심적인 관점, 부가적인 관점으로 나누어 보고 그 관점을 기준으로 각각 모듈화하겠다는 것이다. 여기서 모듈화란, 공통한 로직이나 기능을 하나로 묶는 것을 뜻한다.

 

AOP 에서 관점을 기준으로 로직을 모듈화한다는 것은 코드들을 부분적으로 나누어 모듈화하겠다는 뜻이다. 이 때, 소스 코드상 다른 부분에서 반복해서 사용되는 코드들을 발견할 수 있는데 이것을 Crosscutting Concerns (흩어진 관심사) 라고 부른다.

 

Crosscutting Concerns 의 모듈화

위와 같이 흩어진 관심사들을 Aspect 로 모듈화하고 핵심적인 비즈니스 로직에서 분리해 재사용하는 것이 AOP 의 취지이다.

 

OOP 와 AOP 개념 비교

더보기
  • OOP : 비즈니스 로직의 모듈화
    • 모듈화의 핵심 단위는 비즈니스 로직
  • AOP : 인프라 혹은 부가기능의 모듈화
    • 각 모듈들의 주요 목적 외 필요한 부가적인 기능들
    • 대표적 예 : 로깅, 트랜잭션, 보안 등

 

AOP 주요 개념

  • Aspect : 위에서 설명한 흩어진 관심사를 모듈화한 것
  • Target : Aspect 를 적용한 곳 (클래스, 메소드, ...)
  • Advice : 실질적으로 어떤 동작을 해야할 지에 대한 것
  • JoinPoint : 어드바이스가 적용될 위치
  • PointCut : 부가기능이 적용될 대상을 선정하는 방법의 표현
  • Proxy : 타겟을 감싸서 타겟의 요청을 대신 받아주는 래핑 오브젝트
  • Introduction : 타겟 클래스에 코드 변경없이 신규 메소드나 멤버 변수를 추가하는 기능
  • Weaving : 지정된 객체에 Aspect 를 적용해 새로운 프록시 객체를 생성하는 과정
반응형

POJO 구성 방식

11. 스프링 환경 및 프로파일마다 다른 POJO 로드

자바 구성 클래스를 여러 개 생성하고 각 클래스마다 POJO 인스턴스 / 빈을 묶는다. 이렇게 작성한 구성 클래스에 @Profile 을 붙임으로써 프로파일 별로 다른 빈 인스턴스 구성을 할 수 있다.

 

POJO 의 초깃값은 애플리케이션의 제작 단계에 따라 달라질 수 있다. 예를 들어 개발 단계나 운영 단계에서 사용하고자 하는 DB 의 경로가 달라질 수 있고, 이러한 설정들을 달리 적용해야 할 빈들이 존재할 수 있다. 이런 경우에 @Profile 을 사용해 개발자가 시나리오에 맞는 구성 클래스를 읽어 들여 실행하게 할 수 있다.

 

간단한 예시로, 자바 구성 클래스에 @Profile 을 붙여 여러 버전으로 나누는 코드를 작성한다.

@Configuration
@Profile("develop")
public class ConfigurationDevelop {
	
    @Bean
    public Product product() {
    	// ...
    }
}
@Configuration
@Profile("deploy")
public class ConfigurationDeploy {
	
    @Bean
    public Product product() {
    	// ...
    }
}

 

위와 같이 구성 클래스를 @Profile 을 나눠 작성한다. @Profile 의 속성으로 위의 예제에서는 하나의 속성값만을 정의했지만, 두 개 이상의 속성값을 정의하는 것도 가능하고 이 경우는 {} 로 값들을 묶어 정의할 수 있다. 이렇게 작성한 프로파일들을 활성화하기 위해서 어플리케이션 콘텍스트 환경을 가져온 뒤 다음과 같이 setActiveProfiles() 메소드를 호출한다.

AnnotationConfigApplicationContext context = ...;
context.getEnvironment().setActiveProfiles("develop");

혹은 자바 런타임 플래그로 프로파일을 명시하는 것도 가능한데, 'develop' 프로파일을 활성화하려면 다음 플래그를 추가하는 것도 가능하다.

-Dspring.profiles.active=develop

 

어떤 프로파일도 로드되지 않는 불상사를 막기 위해 기본 프로파일을 지정하는 것이 가능하다. 어플리케이션 콘텍스트 환경의 메소드를 통해 이를 지정하려면 setDefaultProfiles() 메소드를 사용할 수 있고, 플래그로 지정하고자 할 경우는 spring.profiles.active 대신 spring.profiles.default 로 사용할 수 있다.

 

12. POJO 에게 IoC 컨테이너 리소스 알려주기

빈이 IoC 컨테이너 리소스를 인지하게 하려면 Aware 인터페이스를 구현한다. 스프링은 이 인터페이스를 구현한 빈을 감지해 대상 리소스를 세터 메소스로 주입한다. 자주 쓰이는 스프링 Aware 인터페이스는 다음과 같다.

Aware 인터페이스 대상 리소스 타입
BeanNameAware IoC 컨테이너에 구성한 인스턴스의 빈 이름
BeanFactoryAware 컨테이너 서비스를 호출하는 데 사용
ApplicationContextAware 컨테이너 서비스를 호출하는 데 사용
MessageSourceAware 텍스트 메시지 해석하는 데 사용
ApplicationEvent
PublisherAware
애플리케이션 이벤트를 발행하는 데 사용
ResourceLoaderAware 외부 리소스를 로드하는 데 사용
EnvironmentAware ApplicationContext 인스턴스에 묶인 org.springframework.core.env.Environment 인스턴스

Aware 인터페이스의 세터 메소드는 스프링이 빈 프로퍼티를 설정한 후, 초기화 콜백 메소드를 호출하기 이전에 호출한다. 순서를 정리하면 다음과 같다.

  1. 생성자나 팩토리 메소드를 호출해 빈 인스턴스 생성
  2. 빈 프로퍼티에 값, 빈 레퍼런스 설정
  3. Aware 인터페이스에 정의한 세터 메소드 호출
  4. 빈 인스턴스를 빈 후처리기의 postProcessBeforeInitialization() 메소드로 넘겨 초기화 콜백 메소드 호출
  5. 빈 인스턴스를 빈 후처리기의 postProcessAfterInitialization() 메소드로 넘김
  6. 컨테이너가 종료되면 폐기 콜백 메소드 호출
NOTE
스프링 최신 버전에서는 @Autowired 를 붙여 ApplicationContext 를 손쉽게 가져올 수 있어 굳이 Aware 인터페이스를 구현할 필요가 없다. 프레임워크나 라이브러리 개발 시에는 Aware 인터페이스를 구현하는 게 나을 수 있지만, 스프링에 종속된 인터페이스를 정말 구현할 필요가 있는지 따져보는 것이 중요하다.

 

13. 어노테이션을 활용한 AOP 적용

AOP (Aspect-Oriented Programming ; 관점 지향 프로그래밍).

AOP 에 대한 개념은 따로 포스팅하도록 하겠다. 우선 스프링에서 어노테이션을 활용해 어떻게 AOP 를 적용하는 지를 먼저 정리하려 한다.

 

자바 클래스에 @Aspect 를 붙이고 메소드 별로 적절한 어노테이션을 붙여 Advice 로 만들어 Aspect 를 정의할 수 있다. 적용 가능한 Advice 어노테이션은 @Before, @After, @AfterReturning, @AfterThrowing, @Around 가 있다. IoC 컨테이너에서 Aspect 어노테이션 기능을 활성화하기 위해서는 구성 클래스 중 하나에 @EnableAspectJAutoProxy 를 붙인다.

 

Aspect 는 여러 타입과 객체에 공통 관심사(예 ; 로깅, 트랜잭션 관리) 를 모듈화한 자바 클래스로, @Aspect 를 붙여 표시한다. AOP 에서 말하는 Aspect 란 어디에서 (PointCut), 무엇을 할 것인지 (Advice) 를 합쳐 놓은 개념이다. 어드바이스는 @Advice 를 붙인 단순 자바 메소드이고, 포인트컷은 어드바이스에 적용할 타입 / 객체를 찾는 표현식이다.

 

@Before 어드바이스

Before 어드바이스는 특정 프로그램 실행 시점 이전의 공통 관심사를 처리하는 메소드이다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Before("execution(* Calculator.add(..))")
    public void logBefore() {
        log.info("The method add() begins");
    }
}

위와 같이 @Before 어노테이션의 값으로 포인트컷 표현식을 적용할 수 있다. 이 포인트컷 표현식은 Calculator 인터페이스의 add() 메소드 실행을 가리킨 것이다. 앞부분의 와일드카드(*) 는 모든 수정자(public, protected, private), 모든 반환형을 매치함을 의미한다. 인수 목록 부분에 쓴 두 점(..) 은 인수 개수는 몇 개든 좋다는 의미이다.

 

NOTE
@Aspect 만 붙여서는 스프링이 자동으로 감지하지 않기 때문에, 해당 POJO 마다 개별적으로 @Component 를 붙여야 한다.

 

포인트컷으로 매치한 실행 지점을 JoinPoint 라고 한다. 포인트컷은 여러 조인포인트를 매치하기 위해 지정한 표현식이고 이렇게 매치된 조인포인트에서 해야 할 일이 바로 어드바이스이다.

 

어드바이스가 현재 조인포인트의 세부 내용에 접근하려면 JoinPoint 형 인수를 어드바이스 메소드에 선언해야 한다. 그러면 메소드명, 인수값 등 자세한 조인포인트 정보를 조회할 수 있다. 다음과 같이 클래스명, 메소드명에 와일드카드를 적용하면 모든 메소드에 예외없이 포인트컷을 적용할 수 있다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Before("execution(* *.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        log.info("The method {}() begins with {} ", 
            joinPoint.getSignature().getName(), 
            Arrays.toString(joinPoint.getArgs()));
    }
}

 

@After 어드바이스

After 어드바이스는 조인포인트가 끝나면 실행되는 메소드이다. 조인포인트가 정상 실행되든, 도중 예외가 발생하든 상관없이 실행된다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @After("execution(* *.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        log.info("The method {}() ends", joinPoint.getSignature().getName());
    }
}

 

@AfterReturning 어드바이스

After 어드바이스는 조인포인트의 실행 성공 여부와 관계없이 동작한다. 조인포인트가 값을 반환할 경우에만 메소드를 실행하고자 한다면, 다음과 같이 AfterReturning 어드바이스로 대체할 수 있다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @AfterReturning(
    	pointcut = "execution(* *.*(..))",
    	returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        log.info("The method {}() ends with {}", joinPoint.getSignature().getName(), result);
    }
}

조인포인트가 반환한 결과값을 받아오려면 @AfterReturning 의 returning 속성으로 지정한 변수명을 어드바이스 메소드의 인자로 지정한다. 스프링 AOP 는 런타임에 조인포인트의 반환값을 이 인수에 넣어 전달한다. 이 때 포인트컷 표현식은 pointcut 속성으로 따로 지정해야 한다.

 

@AfterThrowing 어드바이스

After Throwing 어드바이스는 조인포인트 실행중 예외가 발생할 경우에만 실행된다. 작동 원리는 @AfterReturning 과 같고, 발생한 예외는 @AfterThrowing 의 throwing 속성에 담아 전달할 수 있다. 자바 언어에서 Throwable 은 모든 에러/예외 클래스의 상위 타입이므로 다음과 같이 어드바이스를 적용하면 조인포인트에서 발생한 에러/예외를 모두 가져올 수 있다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @AfterThrwoing(
    	pointcut = "execution(* *.*(..))",
    	throwing = "result")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        log.error("An exception {} has been thrown in {}()", e, joinPoint.getSignature().getName());
    }
}

 

@Around 어드바이스

Around 어드바이스는 가장 강력한 어드바이스이다. 이 어드바이스는 조인포인트를 완전히 장악하기 때문에 앞서 살펴본 어드바이스를 모두 Around 어드바이스로 조합할 수 있다. 심지어 원본 조인포인트를 언제 실행할 지, 실행 자체를 할지 말지, 계속 실행할 지 여부도 제어할 수 있다.

 

다음은 Before, After Returning, After Throwing 어드바이스를 Around 어드바이스로 조합한 예이다. Around 어드바이스의 조인포인트 인자형은 ProceddingJoinPoint 로 고정돼 있고, JoinPoint 하위 인터페이스인 ProceedingJoinPoint 를 이용하면 원본 조인포인트를 언제 진행할 지 그 시점을 제어할 수 있다.

@Aspect
@Component
public class CalculatorLoggingAspect {
	
    private Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Around("execution(* *.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    	log.info("The method {}() begins with {}",
            joinPoint.getSignature().getName(),
            Arrays.toString(joinPoint.getArgs()));
            
        try {
            Object result = joinPoint.proceed();
            log.info("The method {}() ends with {}", joinPoint.getSignature.getName(), result);
            return result;
        } catch (IllegalArgumentException e) {
            log.error("Illegal Argument {} in {}()", Arrays.toString(joinPoint.getArgs()),
                joinPoint.getSignature().getName());
            throw e;
        }
    }
}

 

#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

반응형

POJO 구성 방식

8. 어노테이션을 이용한 POJO 초기화 / 폐기

어떤 POJO 는 사용하기 전 특정한 초기화 작업, 사용한 후 폐기 작업이 필요하다. 예를 들어 파일을 열거나, 네트워크 / DB 에 접속하거나, 메모리를 할당하는 등 선행 작업이 필요한 경우이다. IoC 컨테이너에서 빈을 초기화하거나 폐기하는 로직을 커스터마이징할 수 있다.

 

자바 구성 클래스의 @Bean 정의 시 initMethod, destroyMethod 속성을 설정함으로써 스프링이 각각을 초기화 / 폐기 콜백 메소드로 인지한다. 혹은 POJO 클래스의 메소드에 @PostConstruct / @PreDestroy 를 붙이는 방법이 있다. 또 @Lazy 를 붙임으로써 Lazy Initialization (주어진 시점까지 빈 생성을 미뤄둠) 을 할 수도 있고, @DependsOn 으로 빈을 생성하기 전에 다른 빈을 먼저 생성하도록 강제할 수 있다.

 

다음과 같이 작업을 수행하고 난 기록을 파일로 기록하는 POJO 클래스를 정의한다.

public class Cashier {
	
    private String fileName;
    private String path;
    private BufferedWriter writer;
    
    // Setter
    
    public void openFile() throws IOException {
    	File targetDir = new File(path);
        if (!targetDir.exists()) {
        	targetDir.mkdir();
        }
        
        File checkoutFile = new File(path, fileName + ".txt");
        if (!checkoutFile.exists()) {
        	checkoutFile.createNewFile();
        }
        
        writer = new BufferedWirter(new OutputStreamWriter(
        	new FileOutputStream(checkoutFile, true)));
    }
    
    // Writer Method
    
    public void closeFile() throws IOException {
    	writer.close();
    }
}

openFile() 메소드는 데이터를 저장할 대상 디렉토리와 파일이 존재하는 지 확인한 뒤, 주어진 시스템 경로에 있는 텍스트 파일을 열어 writer 필드에 할당한다. 기록을 모두 마친 뒤 closeFile() 메소드를 통해 파일을 닫고 시스템 리소스를 반납한다. (기록을 하기 위한 메소드는 생략되었다.)

 

Cashier 빈 생성 이전에 openFile() 메소드를, 폐기 직전에 closeFile() 메소드를 각각 실행하도록 자바 구성 클래스에 빈 정의부를 다음과 같이 설정한다.

@Configuration
public class ShopConfiguration {
	
    @Bean(initMethod = "openFile", destroyMethod = "closeFile")
    public Cashier cashier() {
    	Cashier c1 = new Cashier();
        
        // Call Setter ...
        
        return c1;
    }
}

이로써 Cashier 인스턴스를 생성하기 전에 openFile() 메소드를 먼저 트리거한다. 그리고 빈을 폐기할 때 closeFile() 메소드를 자동 실행하게 된다. 이러한 동작은 다음과 같이 POJO 클래스를 작성해도 똑같이 동작한다.

@Component
public class Cashier {
	
    // Fields and Methods ...
    
    @PostConstruct
    public void openFile() throws IOException {
    	// ... Body
    }
    
    @PreDestroy
    public void closeFile() {
    	// ... Body
    }
}

 

기본적으로 스프링은 모든 POJO 를 미리 초기화한다. 하지만 환경에 따라 빈을 처음으로 요청하기 전까지 초기화 과정을 미루는 게 나을 때도 있다. 이렇듯 나중에 초기화하는 방식을 Lazy Initialization 이라 한다.

 

느긋한 초기화는 시동 시점에 리소스를 집중 소모하지 않아도 되므로 전체 시스템 리소스를 절약할 수 있다. 특히 무거운 작업을 처리하는 POJO 는 느긋한 초기화가 더 어울리는 경우가 있다. 사용 방법은 다음과 같이 POJO 클래스에 @Lazy 를 붙임으로써 느긋한 초기화를 적용할 수 있다. @Lazy 덕분에 다음 클래스는 애플리케이션이 요구하거나 다른 POJO 가 참조하기 전까지는 초기화되지 않는다.

@Component
@Scope("prototype"
@Lazy
public class Product {
	
    // ... Class Body
}

 

POJO 가 늘어나면 그에 따라 초기화 횟수도 증가하고, 여러 자바 구성 클래스에 분산 선언된 많은 POJO 가 서로를 참조하게 되면 Race Condition 이 발생하기 쉽다. B, F 라는 빈의 로직이 C 라는 빈에서 필요하다고 할 때, 아직 스프링이 B, F 빈을 초기화하지 않았는데 C 빈이 먼저 초기화되면 원인불명의 에러가 발생하게 된다.

 

@DependsOn 어노테이션은 어떤 POJO 가 다른 POJO 보다 먼저 초기화되도록 강제하며 설사 그 과정에서 에러가 발생한다 하더라도 헤아리기 쉬운 메시지를 보여준다. 또 @DependsOn 은 빈을 초기화하는 순서를 보장한다.

 

@Configuration 
public class SequenceConfiguration {
	
    @Bean
    @DependsOn("prefixGenerator")
    public SequenceGenerator sequenceGenerator() {
    	SequenceGenerator generator = new SequenceGenerator();
        return generator;
    }
}

 

@DependsOn("prefixGenerator") 를 적용했기 때문에 PrefixGenerator 빈은 항상 SequenceGenerator 보다 먼저 생성된다. @DependsOn 의 속성값은 {} 로 감싼 리스트 형태로 여러 개의 의존성을 설정할 수도 있다.

 

9. PostProcessor 를 통한 POJO 검증 / 수정

빈 후처리기를 이용하면 초기화 콜백 메소드 전후에 원하는 로직을 빈에 적용할 수 있다. 빈 후처리기의 주요한 특징은 IoC 컨테이너 내부의 모든 빈 인스턴스를 대상으로 한다는 점이다. 보통 빈 후처리기는 빈 프로퍼티가 올바른지 체크하거나 어떤 기준에 따라 빈 프로퍼티를 변경 또는 전체 빈 인스턴스를 상대로 어떤 작업을 수행하는 용도로 사용된다.

 

@Required 는 스프링에 내장된 후처리기 RequiredAnnotationBeanPostProcessor 가 지원하는 어노테이션이다. 이 후처리기는 @Required 를 붙인 모든 빈 프로퍼티가 설정되었는지 확인한다.

 

빈 후처리기는 BeanPostProcessor 인터페이스를 구현한 객체이다. 이 인터페이스를 구현한 빈을 발견하면 스프링은 자신이 관장하는 모든 빈 인스턴스에 아래의 두 메소드를 적용한다. 따라서 빈 상태를 조사, 수정, 확인하는 등의 어떠한 로직도 이 메소드에 넣을 수 있다. 이 메소드들은 하는 동작이 없어도 반드시 원본 빈 인스턴스를 반환해야 한다.

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
	
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) 
    	throws BeansException {
    
    	return bean;
    }
    
    @Override
    publi Object postProcessAfterInitialization(Object bean, String beanName) 
    	throws BeansException {
    
    	return bean;
    }
}

 

IoC 컨테이너는 자신이 생성한 빈 인스턴스를 모두 하나씩 빈 후처리기에 넘긴다. 만일 특정 타입의 빈만 후처리기를 적용하려면 인스턴스 타입을 체크하는 필터를 이용해 원하는 빈에만 후처리 로직을 적용할 수 있다. 예를 들어 Product 형 빈 인스턴스에만 빈 후처리기를 적용하는 예제는 다음과 같이 작성할 수 있다.

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
	
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) 
    	throws BeansException {
    	if (bean instanceof Product) {
        	// ... Some Code
        }
    	return bean;
    }
    
    @Override
    publi Object postProcessAfterInitialization(Object bean, String beanName) 
    	throws BeansException {
        if (bean instanceof Product) {
        	// ... Some Code
        }
    	return bean;
    }
}

위의 두 메소드는 처리한 빈 인스턴스를 반드시 반환해야 한다. 이를 바꿔 말하면, 원본 빈 인스턴스를 다른 인스턴스로 바꿔치기할 수도 있다는 뜻이다.

 

10. 정적 메소드, 인스턴스 메소드, 스프링 FactoryBean 으로 POJO 생성

자바 구성 클래스의 @Bean 메소드는 정적 팩토리를 호출하거나 인스턴스 팩토리 메소드를 호출해 POJO 를 생성할 수 있다. 다음 ProductCreator 클래스에서 정적 팩토리 메소드 createProduct 는 productId 에 해당하는 상품 객체를 생성한다. 주어진 productId 에 따라 인스턴스화할 실제 상품 클래스를 내부 로직으로 결정한다. 맞는 케이스가 없으면 IllegalArgumentException 예외를 던진다.

public class ProductCreator {
	
    public static Product createProduct(String productId) {
    	if (productId.equals("aaa")) {
        	return new Product("AAA", 2.5);
        } else if (productId.equals("cdrw")) {
        	return new Product("CD-RW", 1.5);
        }
        throw new IllegalArgumentException("Unknown Product ID");
    }
}

 

자바 구성 클래스의 @Bean 메소드에서는 일반 자바 구문으로 정적 팩토리 메소드를 호출해 POJO 를 생성한다.

@Configuration
public class ShopConfiguration {
	
    @Bean
    public Product aaa() {
    	return ProductCreator.createProduct("aaa");
    }
    
    @Bean
    public Product cdrw() {
    	return ProductCreator.createProduct("cdrw");
    }
}

 

다음과 같이 클래스 내에 맵을 구성해 상품 정보를 담아두는 방법도 있다. 인스턴스 팩토리 메소드 createProduct() 는 productId 에 해당하는 상품을 맵에서 찾아 반환한다. 마찬가지로 맞는 케이스가 없으면 예외를 던진다.

public class ProductCreator {
	
    private Map<String, Product> products;
    
    public void setProducts(Map<String, Product> products) {
    	this.products = products;
    }
    
    public Product createProduct(String productId) {
    	Product product = products.get(productId);
        if (product != null) {
        	return product;
        }
        throw new IllegalArgumentException("Unknown Product ID");
    }
}

 

ProductCreator 에서 상품을 생성하려면 먼저 @Bean 을 선언해 팩토리값을 인스턴스화하고 이 팩토리의 퍼사드 역할을 하는 두 번째 빈을 선언한다. 마지막으로 팩토리를 호출하고 createProduct() 메소드를 호출해 다른 빈들을 인스턴스화한다.

@Configuration
public class ShopConfiguration {
	
    @Bean
    public ProductCreator productCreatorFactory() {
    	ProductCreator factory = new ProductCreator();
        Map<String, Product> products = new HashMap<>();
        products.put("aaa", new Product("AAA", 2.5));
        products.put("cdrw", new Product("CD-RW", 1.5));
        factory.setProducts(products);
        return factory;
    }
    
    // ... Bean Methods
}

 

다음과 같이 팩토리 빈을 직접 작성하는 방법도 가능하다. 할인율과 상품이 주어지면 할인가가 적용된 상품을 생성하는 팩토리 빈을 작성해 보자. 이 빈은 product, discount 두 프로퍼티를 받아 주어진 상품에 할인가를 계산해 적용하고 상품 빈을 새로 만들어 반환한다.

public class DiscountFactoryBean extends AbstractFactoryBean<Product> {
	
    private Product product;
    private double discount;
    
    public void setProduct(Product product) {
    	this.product = product;
    }
    
    public void setDiscount(double discount) {
    	this.discount = discount;
    }
    
    @Override
    public Class<?> getObjectType() {
    	return product.getClass();
    }
    
    @Override
    protected Product createInstance() throws Exception {
    	product.setPrice(product.getPrice() * (1 - discount));
        return product;
    }
}

팩토리 빈은 제네릭 클래스인 AbstractFactoryBean<T> 를 상속하고, createInstance() 메소드를 오버라이드해 대상 빈 인스턴스를 생성한다. 또 자동 연결 기능이 작동하도록 getObjectType() 메소드로 대상 빈 타입을 반환한다.

 

이제 상품 인스턴스를 생성하는 팩토리 빈에 @Bean 을 붙여 DiscountFactoryBean 을 적용한다.

@Configuration
@ComponentScan("com.apress.springrecipes.shop")
public class ShopConfiguration {
	
    @Bean
    public Product aaa() {
    	Product aaa = new Product("AAA", 2.5);
        return aaa;
    }
    
    @Bean
    public DiscountFactoryBean discountFactoryBeanAAA() {
    	DiscountFactoryBean factory = new DiscountFactoryBean();
        factory.setProduct(aaa());
        factory.setDiscount(0.2);
        return factory;
    }
}

 

 

#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

반응형

POJO 구성 방식

5. @Scope 를 붙여 POJO 스코프 지정

@Scope 는 빈 스코프를 지정하는 어노테이션이다. 스프링은 기본적으로 IoC 컨테이너에 선언한 빈마다 정확히 하나의 인스턴스를 생성하고 전체 스코프에서 이를 공유한다. 이것이 스프링에서 사용하는 빈의 기본 스코프인 singleton 이다. 전체 스프링 빈 스포크는 다음과 같다.

Scope Description
singleton IoC 컨테이너당 빈 인스턴스 하나를 생성
prototype 요청마다 빈 인스턴스를 새로 생성
request HTTP 요청당 하나의 빈 인스턴스 생성. (WebApplicationContext 만 해당)
session HTTP 세션당 빈 인스턴스 생성. (WebApplicationContext 만 해당)
globalSession 전역 HTTP 세션당 빈 인스턴스 생성. (PortalApplicationContext 만 해당)

 

다음과 같이 동일한 빈에 대해 getBean 을 각각 요청할 경우, 특별히 스코프를 명시하지 않으면 singleton 으로 인스턴스를 생성한다. 고로 (1) 과 (2) 는 모두 "prod2" 를 화면에 출력하게 된다.

Product prod1 = context.getBean("product", Product.class);
Product prod2 = context.getBean("product", Product.class);

prod1.setName("prod1");
prod2.setName("prod2");

// (1)
System.out.println(prod1.getName());
// (2)
System.out.println(prod2.getName());

 

하지만 클래스에 @Scope 를 붙여 해당 빈의 스코프를 명시하게 되면, 해당 클래스의 인스턴스는 스코프에 따라 인스턴스 생성 규칙을 달리 하게 된다. 다음과 같이 클래스에 prototype 스코프임을 명시하게 되면, 위의 예제에서 (1) 은 "prod1" 을, (2) 는 "prod2" 를 출력하게 된다.

@Component
@Scope("prototype")
public class Product { ... }

 

6. 외부 리소스 데이터 사용

스프링이 제공하는 @PropertySource 를 통해 *.properties 파일을 읽어들일 수 있다. 또한 스프링 Resource 인터페이스에 @Value 를 적용하면 어떤 파일이라도 읽어들일 수 있게 된다.

 

예를 들어, 다음과 같이 정의된 option.properties 파일이 존재한다고 하자.

special.discount=0.1
summer.discount=0.15
endofyear.discount=0.2

위와 같은 파일이 존재할 때, 아래와 같이 구성 클래스에 @PropertySource 를 붙여 사용하고자 하는 properties 파일을 명시하면 endOfYearDiscount 라는 필드에 자동으로 파일 내에 정의된 값이 입력되게 된다. @Value 에서 ${key:defaultValue} 와 같이 주입받고자 하는 값을 지정할 수 있으며, defaultValue 는 만약 값을 찾지 못할 경우에 기본으로 갖게 되는 값을 의미한다.

@Configuration
@PropertySource("classpath:option.properties")
public class ProductConfiguration {

    @Value("${endofyear.discount:0}")
    private double enfOfYearDiscount;
}

 

properties 파일 외에 다른 파일 데이터를 사용하고자 할 경우에는 Resource 인터페이스를 사용할 수 있다.

public class BannerLoader {
	
    private Resource banner;
    
    public void setBanner(Resource banner) {
    	this.banner = banner;
    }
    
    @PostConstruct
    public void showBanner() throws IOException {
    	Files.lines(Paths.get(banner.getURI()), Charset.forName("UTF-8"))
        	.ForEachOrdered(System.out::println);
    }
}

위와 같은 BannerLoader 클래스가 존재한다고 하자. showBanner() 메소드는 등록된 banner 를 읽어 파일의 내용을 차례대로 읽어 한 줄씩 출력하는 기능을 수행한다. 이 메소드에 @PostConstruct 를 붙임으로써, 인스턴스가 생성된 뒤 showBanner() 메소드를 호출하게 된다. 아래와 같이 구성 클래스를 작성하면 스프링은 어플리케이션 실행 시 BannerLoader 의 빈 인스턴스를 생성하므로 어플리케이션 실행 시 자동으로 이 메소드를 호출한다.

@Configuration
@PropertySource("classpath:option.properties")
public class ProductConfiguration {
	
    @Value("classpath:banner.txt")
    private Resource banner;
    
    @Bean
    public BannerLoader bannerLoader() {
    	BannerLoader b1 = new BannerLoader();
        b1.setBanner(banner);
        return b1;
    }
}

@Value("classpath:banner.txt") 를 통해 스프링은 banner.txt 파일을 찾고 이를 banner 필드에 주입하고 Resource 빈 객체로 변환할 수 있게 된다.

 

7. 프로퍼티 파일에서 로케일마다 다른 다국어 메시지 해석

MessageSource 인터페이스에는 리소스 번들 메시지를 처리하는 메소드가 정의되어 있다. ResourceBundleMessageSource 는 MessageSource 구현체로, 로케일별로 분릴된 리소스 번들 메시지를 해석할 수 있다.

 

리소스 번들 메시지를 구분 처리하려면 다음과 같이 ReloadableResourceBundleMessageSource 인스턴스를 자바 구성 파일에 정의한다.

@Configuration
public class Configuration {
	
    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
    	ReloadableResourceBundleMessageSource messageSource =
        	new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:messages");
        return messageSource;
    }
}

빈 인스턴스는 반드시 messageSource 라고 명시해야 ApplicationContext 가 알아서 감지할 수 있다. 위의 예에서는 자바 클래스패스 내에서 이름이 messages 로 시작하는 파일들을 찾도록 설정했다. 이렇게 MessageSource 를 정의하고 미국 로케일에서 텍스트 메시지를 찾으면 messages_en_US.properties 리소스 번들 파일을 제일 먼저 찾게 된다. 이런 이름을 가진 파일이 없거나 메시지를 찾지 못한다면 언어에 맞는 messages_en.properties 를 찾고 이 파일마저 없으면 전체 로케일의 기본 파일인 messages.properties 를 선택한다.

 

다음과 같이 ApplicationContext 를 구성해 getMessage() 메소들 메시지를 해석할 수 있게 된다. 이 메소드에서 첫 번째 인자는 메시지 키, 두 번째는 각 메시지의 매개변수 자리에 끼워넣을 값들, 세 번째는 대상 로케일이다.

public static void main(String[] args) throws Exception {
	
    ApplicationContext context = 
    	new AnnotationConfigApplicationContext(Configuration.class);
        
    String alert = context.getMessage("alert.checkout", null, Locale.US);
    
    System.out.println("Message for alert.checkout is " + alert);
}

위와 같이 messages 파일을 클래스패스에 정의하고 어플리케이션 콘텍스트의 getMessage() 메소드를 사용하면 위처럼 메시지를 가져다 쓸 수 있다. 특히 로케일별 메시지를 적용함으로써 여러 지역에서 서비스를 진행할 때 각 지역 별 메시지를 자동으로 출력하도록 할 수 있을 것 같다.

 

#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

반응형

POJO 구성 방식

1. 자바로 POJO 구성

@Configuration, @Bean 을 붙인 자바 클래스를 만들거나 @Component, @Repository, @Service, Controller 를 붙인 자바 컴포넌트를 구성함으로써 POJO 클래스를 설계한다. IoC 컨테이너는 어노테이션이 붙은 자바 클래스를 탐색해 애플리케이션의 일부인 것처럼 POJO 인스턴스/빈을 구성한다.

 

@Configuration
public class ConfigurationClass {
	
    @Bean
    public BeanClass beanClass() {
    	BeanClass bean = new BeanClass();
        bean.setName("Hello");
        bean.setNick("World");
        return bean;
    }
}

 

@Configuration 어노테이션이 붙은 구성 클래스에서 @Bean 메소드를 통해 빈을 생성한다. 그 후 어노테이션을 붙인 자바 클래스를 스캐닝하기 위해 IoC 컨테이너를 인스턴스화 해야 한다. 스프링에서는 기본 구현체인 BeanFactory 와 이와 호환이 가능한 고급 구현체인 ApplicationContext, 두 가지 IoC 컨테이너를 제공한다.

 

ApplicationContext context = new AnnotationConfigApplicationContext(ConfigurationClass.Class);

 

구성 클래스에 선언된 빈을 BeanFactory / ApplicationContext 에서 가져오려면 유일한 빈 이름을 getBean() 메소드의 인자로 호출한다. getBean() 메소드는 Object 타입을 반환하므로 실제 타입에 맞게 캐스팅하거나, 메소드의 인자로 빈 클래스 타입을 명시할 수 있다. 빈 이름은 기본적으로는 메소드명을 사용하고, @Bean(name = "") 과 같이 이름을 명시한 경우는 어노테이션에 명시한 이름을 사용한다. 해당 클래스의 빈이 하나뿐일 때는 빈 이름을 생략할 수도 있다.

 

BeanClass bean = (BeanClass) context.getBean("beanClass");

// 클래스를 명시함으로써 캐스팅을 하지 않을 수 있음
// bean = context.getBean("beanClass", BeanClass.class);

// 빈 클래스가 하나뿐일 때는 빈 이름을 명시하지 않아도 됨
// bean = context.getBean(BeanClass.class);

 

스프링에는 Persistence, Service, Presentation 이렇게 세 계층이 존재하는데 이를 가리키는 어노테이션이 각각 @Repository, @Service, @Controller 이다. 이 외 범용으로 사용할 수 있는 어노테이션으로 @Component 가 있는데, 쓰임새가 명확하지 않을 때는 @Component 를 사용할 수 있지만 구체적으로 명시하는 편이 용도에 맞는 혜택을 누릴 수 있는 장점이 있다고 한다. (@Repository 는 발생한 Exception 을 DataAccessException 으로 감싸 던지는 등 디버깅 시 유리한 점이 있다.)

 

2. 생성자 호출을 통한 POJO 생성

POJO 클래스에 생성자를 하나 이상 정의한 뒤, 구성 클래스에서 IoC 컨테이너가 사용할 POJO 인스턴스값을 생성자로 설정한다. 그 뒤 IoC 컨테이너를 인스턴스화해 어노테이션을 붙인 자바 클래스를 스캐닝하도록 한다. 그리하면 POJO 인스턴스 / 빈을 애플리케이션 일부처럼 접근할 수 있게 된다.

 

// POJO 클래스 선언 (생성자 정의)
public class Product {
	
    private String name;
    private double price;
    
    public Product(String name, double price) {
    	this.name = name;
        this.price = price;
    }
}

// 구성 클래스 정의
public class ShopConfiguration {
	
    @Bean
    public Product aaa() {
    	Product p = new Product("AAA", 2.5);
        return p;
    }
}

 

3. POJO 레퍼런스와 자동 연결을 통한 상호 작용

POJO / Bean 인스턴스들 사이의 참조 관계는 위와 같이 자바 코드로도 연결을 맺어줄 수 있다. 스프링에서는 필드, Setter 메소드, 생성자, 또는 아무 메소드에 @Autowired 를 붙임으로써 POJO 레퍼런스를 자동으로 연결할 수 있다.

 

@Service
public class TemplateService {

    @Autowired
    private BeanDao beanDao;
    
    // Getter, Setter
}

 

서비스 객체를 생성하는 서비스 클래스틑 실제로 자주 쓰이는 Best Practice 로, DAO 에 대한 직접 호출을 하지 않고 일종의 관문을 두는 것이다. 서비스 객체는 내부적으로 DAO 와 연동해 요청받은 작업을 처리하게 된다.

 

위에서는 필드에 @Autowired 를 적용해 의존성을 주입했지만 다음과 같이 생성자나 메소드와도 자동으로 연결할 수 있다. 또한 컬렉션이나 배열에도 @Autowired 를 적용할 경우 매칭되는 빈을 모두 찾아 자동으로 연결해 준다.

 

public class StringGenerator {
	
    // 필드 주입
    @Autowired
    private PrefixGenerator prefixGenerator;
    
    // 배열형, 컬렉션에 Autowired 적용
    @Autowired
    private PrefixGenerator[] prefixGenerators;
    
    // 생성자 주입
    @Autowired
    public StringGenerator(PrefixGenerator prefixGenerator) {
    	this.prefixGenerator = prefixGenerator;
    }
    
    // Setter 주입
    @Autowired
    public void setPrefixGenerator(PrefixGenerator prefixGenerator) {
    	this.prefixGenerator = prefixGenerator;
    }
}

 

스프링은 기본적으로 @Autowired 를 붙인 프로퍼티에 해당하는 빈을 찾지 못하면 예외를 던진다. 선택적으로 프로퍼티를 적용하고자 할 경우에는 @Autowired(required=false) 와 같이 required 속성값을 지정함으로써 빈을 찾지 못하더라도 그냥 지나치도록 할 수 있다.

 

@Autowired 는 타입을 기준으로 자동으로 빈을 연결하는데, 컨테이너에 호환되는 타입이 여러개 존재할 경우 제대로 연결되지 않을 수 있다. 이러한 문제를 해결하기 위해 @Primary, @Qualifier 를 적용할 수 있다.

 

// @Primary 를 활용
@Component
@Primary
public class PrefixGenerator1 implements PrefixGenerator {
	
    // Methods..
}

// @Qualifier 활용
public class StringGenerator {
	
    @Autowired
    @Qualifier("prefixGenerator1")
    private PrefixGenerator prefixGenerator;
}

 

애플리케이션의 규모가 커질수록 POJO 설정들을 하나의 구성 클래스에 담기 어려워지는데, 보통 POJO 의 기능에 따라 여러 구성 클래스로 나누어 관리한다. 그런데 구성 클래스가 여러 개 공존할 경우 상이한 클래스에 정의된 POJO 를 자동으로 연결하거나 참조하는 것이 쉽지 않다.

 

한 가지 방법은, 구성 클래스가 위치한 경로마다 애플리케이션 컨텍스트를 초기화하는 방법이다. 각 자바 구성 클래스에 선언된 POJO 를 컨텍스트와 레퍼런스로 읽으면 POJO 간 자동으로 연결이 가능하다.

 

ApplicationContext context = 
	new AnnotationConfigApplicationContext(Configuration1.class, Configuration2.class);

 

혹은 @Import 로 구성 파일을 나누는 방법도 존재한다.

 

@Configuration
public class Configuration1 {
	
    @Bean
    private PrefixGenerator prefixGenerator() {
    	// ...
    }
}

@Configuration
@Import(Configuration1.class)
public class Configuration2 {

    @Value("#{prefixGenerator}")
    private PrefixGenerator prefixGenerator;
    
    @Bean
    private StringGenerator stringGenerator() {
    	// ...
    }
}

 

StringGenerator 빈을 설정하기 위해 prefixGenerator 빈을 설정해야 하는데, 이 구성 클래스 내에 존재하지 않고 다른 자바 구성 클래스에 정의되어 있는 경우에 @Import 를 사용한다. @Import 를 붙이면 해당 클래스에 정의한 POJO 를 모두 현재 구성 클래스의 스코프로 가져올 수 있는데, 그런 뒤 위와 같이 @Value 와 SpEL (Spring Expression Language) 를 사용해 외부 구성 클래스에 선언된 빈을 필드에 주입할 수 있다.

 

4. @Resource 와 @Inject 를 사용한 POJO 자동 연결

@Resource 와 @Inject 는 자바 표준(JSR) 에 근거한 해법으로, @Autowired 와 거의 유사하게 사용이 가능하다. 

 

@Resource 는 우선적으로 @Autowired 와 같이 타입을 기준으로 POJO 를 찾아 자동으로 연결한다. 하지만 타입이 같은 POJO 가 여럿일 때 @Autowired 는 가리키는 대상이 모호해져 @Qualifier 를 추가적으로 사용해야 하지만, @Resource 는 기능상 @Qualifier 와 @Autowired 를 합한 것과 같아 자체적으로 대상을 명확하게 지정할 수 있다.

 

@Inspect 는 @Autowired, @Resource 와 같이 타입으로 우선 POJO 를 찾지만 타입이 같은 POJO 가 여럿일 경우에는 다음과 같이 커스텀 어노테이션을 작성해 적용해야 한다.

 

@Qualifier
@Target({ElementType.TYPE, ...})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
}

 

이 커스텀 어노테이션에 적용된 @Qualifier 는 스프링에서 사용되는 @Qualifier 가 아닌, javax.inject 패키지에 속한 어노테이션이다. 커스텀 어노테이션을 위와 같이 작성한 뒤, 빈 인스턴스를 생성하는 POJO 클래스에 다음과 같이 어노테이션을 붙인 뒤, @Inject 어노테이션과 같이 커스텀 어노테이션을 사용하면 더이상 모호해지는 상황이 발생하지 않는다.

 

@CustomAnnotation
public class PojoClass {
}

public class ServiceClass {

    @Inject @CustomAnnotation
    private PojoClass pojoClass;
}

 

@Autowired, @Resource, @Inject 중 어떤 것을 사용하더라도 결과는 같다. 차이점은 이름을 기준으로 할 경우에 구문이 가장 단순한 @Resource 가 낫고, 타입을 기준으로 할 경우 셋 중 어느 것을 골라도 간편하게 사용이 가능하다.

 

* 개인적인 생각을 덧붙이자면, 어떤 어노테이션을 쓰느냐는 개인의 마음이라고 볼 수 있겠지만 프로젝트에서 사용함에 있어 하나로 통일해서 사용해야 할 필요는 있을 것 같다. 기능적으로 여러 개를 혼용해 써도 문제가 될 것 같지는 않지만, 추후에 코드를 봤을 경우에 어노테이션이 뒤죽박죽 사용되어 있다면 불필요한 혼란을 가져올 수 있을 것 같다.

 

#Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

 

반응형

Spring Framework 를 사용해 많지는 않은 프로젝트를 진행해 봤는데, 정작 Spring 자체에 대한 공부는 뒷전으로 한 것 같다는 생각이 문득 들었다. 내부적으로 어떻게 동작하는 지 알고 싶어 공부를 하고자 했는데 어떻게 공부를 시작해야 할 지 잘 모르겠어서 일단 책을 구입했다.

 

공부하기 위한 책으로 스프링 5 레시피 라는 책을 구입했는데, 그 내용에 대해서 앞으로 정리하면서 공부를 진행해 보려 한다.

 

Spring Core

Spring IoC / POJO

IoC (Inversion of Control ; 제어의 역전) 는 스프링 프레임워크의 심장부라고 할 수 있는데, IoC 컨테이너는 POJO 를 구성하고 관리하는 역할을 수행한다. IoC 는 DI (Dependency Injection ; 의존성 주입) 이라고도 불리는데 객체 간의 의존성이 발생하는 것을 IoC 컨테이너가 관리하는 것이라 이해했다.

 

일반적으로 객체의 생성과 실행은 다음의 순서를 따른다.

  1. 객체 생성
  2. 의존성 객체 생성
    클래스 내부에서 생성할 객체의 생성자 호출 등을 통해 직접 생성
  3. 의존성 객체의 메소드 호출

하지만 스프링에서는 객체의 생성을 IoC 컨테이너 (Spring 의 BeanFactory, ApplicationContext) 에서 수행하고 생성된 객체를 주입하는 다음과 같은 순서를 따른다.

  1. 객체 생성
  2. 의존성 객체 주입
    스스로 생성하지 않고, 스프링에서 생성한 객체를 주입받음
  3. 의존성 객체의 메소드 호출

이러한 방식으로 객체의 의존성을 주입받는 것을 제어의 역전이라 표현하며, 이를 통해 객체 간의 결합도를 줄이고 유연성을 높인 코드를 작성할 수 있게 해 가독성을 높이고, 코드 중복을 줄이고, 유지보수를 편하게 할 수 있다 한다.

 

특히, 스프링이 모든 의존성 객체를 실행 시 모두 생성하고 필요한 곳에 주입하는 이와 같은 방식은 각각의 Bean 들이 싱글턴 패턴의 특징을 가지도록 하며 제어의 흐름을 개발자가 갖지 않고 스프링이 맡아 작업을 처리하게 된다.

 

# Reference.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

m.hanbit.co.kr

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

[Spring] DI, IoC 정리

DI(Dependency Injection)란 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로,객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.DI(의존성 주입)

velog.io

 

반응형

AJAX 란?

JavaScript 라이브러리 중 하나로 Asynchronous Javascript and XML 의 약자
브라우저가 가지고있는 XMLHttpRequest 객체를 이용해 전체 페이지를 새로 고치지 않고도 페이지의 일부만을 위한 데이터를 로드하는 기법
JavaScript 를 사용한 비동기 통신, 클라이언트와 서버 간에 XML 데이터를 주고받는 기술

 

기본적으로 HTTP 프로토콜은 클라이언트 측에서 Request 를 보내고, 서버 측에서 Response 를 받으면 이어졌던 연결이 끊기게 되고
화면의 내용을 갱신하기 위해서 다시 Request 를 하고 Response 를 받아 페이지 전체를 갱신하게 됨.
이렇게 할 경우, 엄청난 자원 낭비와 시간 낭비로 이어짐.

 

AJAX 를 이용하게 되면, XMLHttpRequest 객체를 통해 서버에 Request 를 보내고
JSON 이나 XML 형태로 필요한 데이터만 받아 갱신하기 때문에 자원과 시간을 아낄 수 있음.

 

AJAX 의 장점과 단점

장점

  • 웹페이지의 속도향상
  • 서버의 처리가 완료될 때까지 기다리지 않고 처리가 가능하다.
  • 서버에서 Data만 전송하면 되므로 전체적인 코딩의 양이 줄어든다.
  • 기존 웹에서는 불가능했던 다양한 UI를 가능하게 해준다. ( Flickr의 경우, 사진의 제목이나 태그를 페이지의 리로드 없이 수정할 수 있다.)

단점

  • 히스토리 관리가 되지 않는다.
  • 페이지 이동없는 통신으로 인한 보안상의 문제가 있다.
  • 연속으로 데이터를 요청하면 서버 부하가 증가할 수 있다.
  • XMLHttpRequest를 통해 통신하는 경우, 사용자에게 아무런 진행 정보가 주어지지 않는다. (요청이 완료되지 않았는데 사용자가 페이지를 떠나거나 오작동할 우려가 발생하게 된다.)
  • AJAX를 쓸 수 없는 브라우저에 대한 문제 이슈가 있다.
  • HTTP 클라이언트의 기능이 한정되어 있다.
  • 지원하는 Charset이 한정되어 있다.
  • Script로 작성되므로 디버깅이 용이하지 않다.
  • 동일-출처 정책으로 인하여 다른 도메인과는 통신이 불가능하다. (Cross-Domain문제)

'Study > Etc' 카테고리의 다른 글

[Web] HTTP 와 HTTPS  (0) 2021.10.12
[REST] REST 란?  (0) 2021.10.10
Build Tool 이란?  (0) 2021.07.02
Zeppelin 에서 external package import 하기  (0) 2018.02.17
반응형

Features Added in MySQL 8.0

 

MySQL :: MySQL 8.0 Reference Manual :: 14 MySQL Data Dictionary

Chapter 14 MySQL Data Dictionary MySQL Server incorporates a transactional data dictionary that stores information about database objects. In previous MySQL releases, dictionary data was stored in metadata files, nontransactional tables, and storage engine

dev.mysql.com

 

MySQL :: MySQL 8.0 Reference Manual :: 13.1.1 Atomic Data Definition Statement Support

13.1.1 Atomic Data Definition Statement Support MySQL 8.0 supports atomic Data Definition Language (DDL) statements. This feature is referred to as atomic DDL. An atomic DDL statement combines the data dictionary updates, storage engine operations, and bi

dev.mysql.com

  • 보안 및 계정 관리
  • 자원 관리
  • 테이블 암호화 관리
  • InnoDB
  • JSON
  • Data Type Support
  • Optimizer

Reference.

[MySQL 8.0 Ref] https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html#mysql-nutshell-additions

 

MySQL :: MySQL 8.0 Reference Manual :: 1.3 What Is New in MySQL 8.0

These functions are removed in favor of the ST_ names: Area(), AsBinary(), AsText(), AsWKB(), AsWKT(), Buffer(), Centroid(), ConvexHull(), Crosses(), Dimension(), Distance(), EndPoint(), Envelope(), ExteriorRing(), GeomCollFromText(), GeomCollFromWKB(), Ge

dev.mysql.com

 

'Study > Database' 카테고리의 다른 글

[DB] 키의 개념과 종류  (1) 2021.10.08
[DB] 정규화 (Normalization) 란?  (0) 2021.10.08
[DB] DB Index  (0) 2021.10.07
관계형 데이터베이스의 정의와 종류  (0) 2021.07.02
반응형

Java 는 Version 별로 새로운 기능을 출시하는 등 큰 변경점이 있다. 버전 중에서도 LTS (Long Term Service) 버전들은 장기적으로 업데이트가 보장되므로 호환성이나 안정성 면에서 장점이 있다고 생각해 Java 8 과 11 의 각 변경점에 대해서 간단하게 정리를 해 보려고 한다.

 

Java 8

Java 8 은 2014 년 발표된 Java 버전으로 다음과 같은 주요 변경사항이 있었다.

  • Lambda 표현식
  • Stream API
  • java.time 패키지

람다 표현식 (Lambda Expression)

우선 람다 표현식이란, 익명 클래스의 한 개 메소드를 식으로 표현한 것이다. 여기서 익명 클래스란, 말 그대로 이름이 없는 클래스로 단 한 개의 객체만을 생성할 수 있는 일회용 클래스이다.

// 익명 클래스의 예
new Object() {
	int min(int x, int y) {
    	return x < y ? x : y;
    }
}
// 람다 표현식의 예
(x, y) -> x < y ? x : y;

이러한 람다 표현식은 매개변수로 전달될 수 있으며, 메소드의 결과값으로 반환될 수도 있다. 따라서 람다 표현식을 사용할 경우 기존의 불필요한 코드를 줄이고 작성된 코드의 가독성을 높일 수 있다. Java 8 부터는 이러한 람다 표현식을 사용해 자바에서도 함수형 프로그래밍이 가능하게 되었다.

스트림 API (Stream API)

자바에서는 여러 데이터를 저장하기 위해 배열 / 컬렉션을 사용한다. 그리고 이렇게 저장된 데이터에 접근하기 위한 방법으로 반복문이나 Iterator 를 사용해 매번 코드를 작성해야 했다.

하지만 이렇게 작성된 코드는 길이가 길고 가독성도 떨어지며, 코드의 재사용이 거의 불가능하다. 또한, 정형화된 처리 패턴을 가지지 못하기 때문에 데이터마다 다른 방법으로 접근해야 했다.

이러한 문제를 극복하기 위해 도입된 방법이 바로 스트림 API 이다. 스트림 API 는 데이터를 추상화해서 다루므로, 다양한 형태로 저장된 데이터를 위한 공통된 방법을 제공한다. 따라서 스트림 API 를 사용하면 배열이나 컬렉션 뿐 아니라, 파일에 저장된 데이터도 모두 같은 방법으로 다룰 수 있다.

 

다음은 스트림 API 를 사용한 예제이다.

// 정수형 배열에서의 스트림 생성
int [] arr1 = new int[]{1, 5, 11, 13, 20, 52};
Stream stream1 = Arrays.stream(arr1);
stream1.map(i -> i * 2);
stream1.filter(i -> i % 2 == 0); // 재사용이 불가능하기 때문에 에러 발생

// 정수형 배열에서 스트림 생성
int [] arr2 = new int[]{1, 5, 11, 13, 20, 52};
Stream stream2;
stream2 = Arrays.stream(arr2)
				.filter(i -> i % 2 != 0)	// {1, 5, 11, 13}
                .map(i -> i * 2);			// {2, 10, 22, 26}

java.time 패키지

java.time 패키지는 기존에 사용하던 Date 클래스나 Calendar 클래스의 문제를 해결하기 위해 새롭게 등장한 날짜/시간 API 패키지이다.

 

Java 11

Java 11 은 2018 년에 발표된 Java 버전으로 Java 8 이후 최초로 공개된 Java 의 LTS 버전이다. Oracle 은 Java 8 에 대한 지원을 2019 년에 중단했고, Java 11 은 많은 변화가 있었다.

 

Local-Variable Syntax for Lambda Parameters

jdk 10 버전에서 지역변수로 사용할 수 있는 var 가 새로운 feature 로 추가되었는데, jdk 11 버전에서는 var 를 람다 표현식을 쓰는 경우에, 전달되는 parameter 들의 타입을 추론할 수 있는 Feature 가 추가되었다.

list.stream()
	.map((var s) -> s.toLowerCase())
    .collect(Collectors.toList());

물론 기존의 람다 표현식에서도 타입을 생략하면, 컴파일러가 컴파일 시 s 의 타입을 String 으로 추론한다. 그렇다면, jdk 8 의 추론 방법이 더 간단한데 왜 이런 feature 가 추가되었을까?

Align the syntax of a formal parameter declaration in an implicitly typed lambda expression with the syntax of a local variable declaration.

JEP 323의 Goals 를 보면, 위와 같이 명시되어있는데 jdk 10 에서 명시적으로 선언되던 구문을 var 로 선언할 수 있게 되었고, 이런 특징을 람다 표현식에도 적용해, 암시적으로 선언되던 형태를 var 로 선언해 표현식을 통일할 수 있게 되었다.

또한, 이렇게 명시적으로 선언하면 람다표현식에서 어노테이션을 사용하는 경우 아래와 같이 조금 더 간단하게 코드를 작성할 수 있다.

list.stream()
	.map((@NotNull var s) -> s.toLowerCase())
    .collect(Collectors.toList());

HTTP Client (Standard)

jdk 9 에서 추가되고 jdk 10 에서 업데이트된 java.incubator.http 패키지가 인큐베이터에서 나와 java.net.http 패키지로 표준화되었다. 이 패키지가 개발된 이유는 아래와 같다.

  • 베이스가 되는 URLConnection API 가 현재는 거의 사용되지 않는 프로토콜을 염두에 두고 설계되었다.
  • HTTP/1.1 보다 너무 추상적이다.
  • 문서화가 잘 되어있지 않아 사용이 어렵다.
  • Blocking 형태로만 동작한다.
  • 유지보수의 어려움이 있다.

java.net.http 로 옮겨진 패키지의 기능은 아래와 같다.

  • Non-Blocking request and response 지원 (with CompletableFuture)
  • Backpressure 지원(java.util.concurrent.Flow 패키지를 통해 RX Flow 를 구현체에 적용)
  • HTTP/2 지원
  • Factory method 형태로 지원

ZGC : A Scalable Low-Latency Garbage Collector (Experimental)

jdk 11 에서 새롭게 등장한 가비지 콜렉터이다. ZGC 라고도 불리는 이 가비지 콜렉터는 아래의 몇 가지 목표를 가지고 개발되었다.

  • GC 일시 중지 시간은 10ms 를 초과하지 않는다.
  • 작은 크기(수백 메가) ~ 매우 큰 크기(수 테라) 범위의 힙을 처리한다.
  • G1 에 비해 어플리케이션 처리량이 15% 이상 감소하지 않는다.
  • 향후 GC 최적화를 위한 기반을 마련한다.
  • 처음에는 Linux / x64 를 지원한다. (향후 추가 플랫폼을 추가 지원할 수 있다.)

알다시피, JVM 으로 구동되는 어플리케이션의 경우는 GC 가 동작할 때 어플리케이션이 멈추는 현상은 성능에서 큰 영향을 끼쳐 왔다. 이러한 정지시간을 줄이거나 없앰으로써 어플리케이션의 성능 향상에 기여할 수 있다.

ZGC 의 주요한 원리는 Load Barrier 와 Colored Object Pointer 를 동시에 사용하는 것이다. 이를 통해 Java 의 어플리케이션 스레드가 동작하는 중간에, ZGC 가 객체 재배치 같은 작업을 수행할 수 있게 해준다.

 

이 외에도, 다양한 성능적 향상이 java 8 버전에 비해 제공된다고 한다.

'Study > Java' 카테고리의 다른 글

[Java] Annotation 이란?  (0) 2021.11.03
[Java] 서블릿 (Servlet) 과 JSP (Java Server Page)  (0) 2021.09.30
JPA 란?  (0) 2021.06.18

+ Recent posts