spring Aware 순서

spring Aware order

이건 메모 추후에 다신 확인

AbstractApplicationContext finishBeanFactoryInitialization(beanFactory)

AbstractAutowireCapableBeanFactory 클래스에서 아래와 같이 셋팅

BeanNameAware
BeanClassLoaderAware
BeanFactoryAware
    private void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
        }
    }

ApplicationContextAwareProcessor 클래스에서 아래와 같이 셋팅

EnvironmentAware
EmbeddedValueResolverAware
ResourceLoaderAware
ApplicationEventPublisherAware
MessageSourceAware
ApplicationContextAware
    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof EnvironmentAware) {
                ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
            }
            if (bean instanceof EmbeddedValueResolverAware) {
                ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                        new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
            }
            if (bean instanceof ResourceLoaderAware) {
                ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
            }
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            if (bean instanceof MessageSourceAware) {
                ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
            }
        }
    }

XXXXXAwareProcessor를 참고 하면 됨 BeanPostProcessor를 구현한걸로 보임. 모두다 그렇지는 않음

Spring 4.2.5의 전체 목록. 일단 기본적인것만 확인 했다.

ApplicationContextAware, ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, BeanNameAware, BootstrapContextAware, EmbeddedValueResolverAware, EnvironmentAware, ImportAware, LoadTimeWeaverAware, MessageSourceAware, NotificationPublisherAware, PortletConfigAware, PortletContextAware, ResourceLoaderAware, SchedulerContextAware, ServletConfigAware, ServletContextAware

spring mock test 에 대해 알아보자

spring mock test

이번엔 spring mock test에 대해서 알아 볼 것이다.
mock 으로 테스트를 잘 하지 않아서 익숙하지 않다.
그래서 이제부터는 mock test를 사용 하도록 노력 할라고 하는 중이다.
일단 spring boot로 할 것이다.
그래서 아래와 같이 메이븐을 추가하자.

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

일단 기본적으로 테스트에 필요한 라이브러리다.
첫번째는 json-path 여기에 자세히 나와있다.
두번째는 spring test를 위한 mock 라이브러리다.
이번에도 스칼라도 했다. 흠하

일단 테스트 클래스에 아래와 같이 어노테이션을 추가 한다.

@RunWith(classOf[SpringJUnit4ClassRunner])
@SpringApplicationConfiguration(Array(classOf[SpringBootConfig]))
@WebAppConfiguration
@FixMethodOrder(MethodSorters.JVM)

첫번째는 Spring에서 제공하는 Runner다
두번째는 Spring Boot를 사용해서 저 어노테이션을 쓴거다. 빈 들을 관리해준다.
Boot를 쓰지 않았을때는 @ContextConfiguration 어노테이션을 썼다. 물론 SpringApplicationConfiguration 안에 ContextConfiguration 포함되어 있다.
세번째는 WebApplicationContext를 생성해주는 아이다.
네번째는 필수 사항이 아니다. 테스트 순서를 정하는 거다.
테스트를 위해 아래와 같이 셋팅 해주자.

var objectMapper: ObjectMapper = _

var mockMvc: MockMvc = _

@Autowired
var wac: WebApplicationContext = _

@Before
def before: Unit = {
  objectMapper = new ObjectMapper
  mockMvc = MockMvcBuilders.webAppContextSetup(wac).build
}

objectMapper는 다들 아시다 시피 json으로 값을 넘길때 사용 할거다.
mockMvc를 선언한다.
그리고 WebApplicationContext 는 mockMvc를 생성하기 위해서 필요하다.
before 메소드에 objectMapper와 mockMvc 생성해준다.

일단 코드 부터 보자

@Test
@Test
def mockTest: Unit = {
  mockMvc.perform(get("/accounts") header ("Accept","application/json")  contentType(MediaType.APPLICATION_JSON))
    .andDo(print())
    .andExpect(status isOk)
    .andExpect(handler handlerType (classOf[AccountController]))
    .andExpect(handler methodName ("accounts"))
    .andExpect(content contentType(MediaType.APPLICATION_JSON_UTF8))
    .andExpect(jsonPath("$.content[0].name", is("wonwoo")))
    .andExpect(jsonPath("$.content[1].name", is("kevin")))
}

첫번째줄은 Http method와 url, 헤더 정보를 셋팅 할 수 있다. contentType와 accpet를 셋팅 했다. contentType처럼 메소드를 사용 할 수 있고
커스텀 header 도 설정 할 수 있다.
이 외 에도 accept, cookie, locale, sessionAttr, session, principal 등 여러가지 메소드가 존재 한다.
print() 는 request response 정보를 콘솔창에 출력해준다.
status.isOk 는 http code가 200일 경우를 체크 하는거다.
이 외 에도 isCreated, isNoContent, isBadRequest, isUnauthorized, isNotFound 등 이 있다. 자주 쓰는 것만 넣어뒀다.
웬만한 http code가 다 있는 듯하다.
handler.handlerType은 요청 컨트롤러이다.
handler methodName은 요청 메소드이다.
content.contentType은 response의 미디어 타입이다.
일단 여기 까지 성공 되었다면 다음은 데이터를 확인할 차례이다.
json-path 라이브러리를 추가한 이유이다.
문법은 저기 링크에 자세히 나와있다.
content 키를 갖고 있는 배열의 첫번째 name이 wonwoo와 같은지 비교하는거다.
만약 틀린다면 에러를 내뱉는다.

코드를 좀더 보자.

@Test
def mockTest1: Unit = {
  mockMvc.perform(get("/account/{id}", 2.asInstanceOf[Object]) contentType (MediaType.APPLICATION_JSON))
    .andDo(print())
    .andExpect(status isOk)
    .andExpect(handler handlerType (classOf[AccountController]))
    .andExpect(handler methodName ("account"))
    .andExpect(jsonPath("$.name", is("kevin")))
}

urlTemplate 처럼 만들 수도 있다.
나머지는 같지만 단일 데이터이기 때문에 $.name 이렇게 했다.

mockMvc.perform(get("/account/search") param("name", "wonwoo")

이렇게 파라미터로 보낼 수도 있다.

@Test
def mockTest4: Unit = {

  val account = new Account()
  account.setId(3L);
  account.setName("mockTest")
  account.setPassword("pwMockTest")

  mockMvc.perform(post("/account")
    contentType (MediaType.APPLICATION_JSON)
    content (objectMapper.writeValueAsString(account)))
    .andDo(print())
    .andExpect(status isCreated)
    .andExpect(jsonPath("$.name", is("mockTest")))
    .andExpect(jsonPath("$.password", is("pwMockTest")))
}

이번엔 requestbody로 보내는 데이터를 만들었다.
objectMapper 를 이용해서 엔티티빈을 json String으로 만들었다.
http code는 201로 생성 했다고 코드를 받았다.

이렇게 테스트 케이스를 만들어서 사용할 예정이다.
예전엔 그냥 크롬 확장프로그램에서 Advanced REST client를 썼는데 이젠 테스트 케이스를 만들어서 사용 해야 겠다.

AspectJ

AspectJ

포인트컷(@Pointcut)

포인트컷은 @Pointcut 어노테이션이 달린 메소드를 이용해 선언. 선택 로직은 @Pointcut 안에 포인터컷 표현식을 넣어서 정의한다. 메소드의 내부는 코드를 작성할 필요는 없다.

@Pointcut("execution(* hello(..))")
private void all() { 

}

어드바이스(@Before, @AfterRetuning, @AfterThrowing, @After, @Around)

어드바이스도 포인터컷과 마찬가지로 어노테이션이 붙은 메소드를 이용해 정의한다. AspectJ에서는 다섯가지 종류의 어드바이스를 사용할 수 있다.

  • @Around : Methodlnterceptor 인터페이스 메소드와 비슷하다. 실제로 @Around 와 Methodlnterceptor는 개발 방식이 다를뿐 같은 종류의 어드바이스를 개발할 때 사용한다. 프록시를 통해서 타깃 오브젝트의 메소드가 호출되는 전 과장을 모두 담을 수 있는 어드바이스다.
@Around("all()")
public Object printParametersAndReturnVal(ProceedingJoinPoint pjp) throws Throwable {
//...
Object ret =pjp.proceed(); 
...
return ret;
}
  • @Before : 이름 그대로 타깃 오브젝트의 메소드가 실행되기 전에 사용되는 어드바이스다. @Before 어드바이스로는 타깃 오브젝트 메소드를 호출하는 방식을 제어할 수 없다. @Before를 적용해도 타깃 오브젝트 메소드 호출은 정상적으로 일어난다.

@Before("myPointcut()") public void logJoinPoint(JoinPoint jp) { System.out.println(jp.getSignature().getDeclaringTypeName()); System.out.println(jp.getSignature().getName()); for(Object arg: jp.getArgs()) { System.out.println(arg); } }
  • @AfterReturning : 타깃 오브젝트의 메소드가 실행을 마친뒤에 실행되는 어드바이스다. 단 예외가 발생하지 않고 정상적으로 종료한 경우에만 해당된다. 따라서 메소드에서 예외가 던져졌다면 이 어드바이스는 적용되지 않는다. 종료된 후에 호출되기 때문에 메소드의 리턴 값을 참조할 수 있다. 리턴 값을 참조할 때는 어노테이션의 returning 속성을 이용해서 리턴 값을 담을 파라미터 이름을 지정해야 한다. 리턴 값 자체를 바꿀 수는 없다. 리턴값을 변경 하려면 @Around 를 사용해야 된다. 하지만 리턴 값이 레퍼런스 타입이라면 참조하는 오브젝트를 조작할 수는 있다. 전달받은 파라미터의 타입을 구체적으로 지정해주면 리턴 값의 타입이 일치하는 경우에만 어드바이스가 실행된다.
@AfterReturning(pointcut="myPointcut()", returning="ret")
public void logReturnValue(Object ret) {
//...
}
  • @AfterThrowing : 타깃 오브젝트의 메소드를 호출했을 때 예외가 발생하면 실행되는 어드바이스다. 속성중 throwing을 이용해서 예외를 전달받을 메소드 파라미터 이름을 지정할 수 있다. throwing으로 지정한 파라미터의 타입이 발생한 예외와 일치할 경우에만 어드바이스가 호출된다. 모든 예외를 다 전달받으려면 Throwable로 파라미터 타입을 지정하면 된다.

@AfterThrowing(pointcut="daoLayer()" throwing="ex") public void logDAException(DataAccessException ex) { //... }
  • @After : 메소드 실행이 정상 종료 되었을 때와 예외가 발생했을 때 모두 실행되는 어드바이스다. 코드에서 finally를 사용했을 때와 비슷한 용도라 생각하면 된다. 반드시 반환돼야 하는 리소스가 있거나 메소드 실행 결과를 항상 로그로 남겨야 하는 경우에 사용 할 수 있다. 하지만 리턴 값이나 예외를 직접 전달받을 수는 없다.

다음은 @target 포인트컷 표현식을 직접 사용한 어드바이스 코드를 살펴보자.

@Before("@target(com.epril.myproject.special.annotation.BatchJob)")
public void beforeBatch () { 
//...
}

타깃 오브젝트에 @BatchJob 이라는 어노테이션이 붙은 것을 선정해서 @Before 어드바이스를 적용한 코드다. args, target, this, @target, @within, @annotation, @args 등에서 타입을 직접 지정하려면 위와 같이 패키지 이름을 포함한 클래스 이름전체를 적어야 한다. 문자열로 구성된 포인트컷에 타입 이름을 모두 적는 건 매우 번거롭고 실수 하기 쉽다. 이런 경우 메소드 파라미터를 이용해서 타입정보를 자바 코드로 작정하면 훨씬 깔끔하다.

import com.epril.myproject.speCla1.annotation.BatchJob;
@Before("@target(bj)")
public void beforeBatch(BatchJob bj) { 
//...
}

포인트컷 표현식 내의 파라미터는 포인트컷을 따로 정의할 때도 다음과 같이 사용할 수 있다.

@Pointcut("@target(bj)")
private void batchJob(BatchJob bj) {
}

이렇게 정의된 포인트컷을 다음과 같이 어드바이스에 적용하면 어드바이스 메소드가 파라미터로 지정된 정보를 제공받을 수 있다.

@Before("batchJob(bj)")
public void beforeBatch(BatchJob bj) { 
//... 
}

포인트컷 표현식 내의 파라미터 이름은 포인트컷 메소드 또는 어드바이스 메소드의 파라미터 이름과 일치 해야 한다. BatchJob 애노테이션에 속성값으로 여러 가지 정보를 지정할 수 있도록 되어 있다면, 어드바이스 메소드에서 어노테이션 오브젝트를 받아 이를 참조하거나 활용할 수 있다. 마찬가지로 args() 를 이용해 파라미터 값을 바인딩한다거나, @annotation을 이용해 메소드의 애노테이션을 파라미터로 받을 수도 있다.

@Configurable

IoC의 원리를 다시 생각해보자. IoC는 오브젝트의 생성과 관계설정, 사용, 제거까지의 모든 과정을 컨테이너에게 위임하는 방식을 사용하는, DI의 기반이 되는 프로그래밍모델이다. DI를 적용하려면 IoC도 역시 적용돼야 한다. 따라서 이 둘은 뗄 수 없고, 그래서 보통 이 둘을 합쳐서 IoC/DI라고 부른다.

User 도메인 오브젝트다. 따라서 애플리케이션 코드를 비롯해서 @MVC, ORM 프레ㅐ임워크 등 다양한 곳에서 생성된다. User 도메인 오브젝트 안에 비즈니스 로직을 넣기 위해서 DI를 통해 UserPolicyDaoEmailService 빈은 제공받아야 한다고 해보자. 이런 빈들을 DI 받아서 사용할 수 있다면 사용자 레벨이 업그레이드되는 로직을 User 오브젝트의 메소드에 넣을 수 있다.

public class User (
  private UserPolicyDao userPolicyDao; 
  private EmailService emailService;
  public void setUserPolicyDao(UserPolicyDao userPolicyDao) { 
  //... 
  } 
  public void setEmailService(EmailService emailService) { 
  //... 
  }
  public void upgradeToNextLevel() {
    UserPolicy userPolicy = userPolicyDao.get(PolicyType.UPGRADE);
    emailService.sendMail(this.email , upgradeMessage);
  }
}

그런데 이 User 클래스는 빈으로 등록돼서 만들어지고 DI를 받는 것이 아니라 직접 코드 내에서 생성하거나 ORM 프레임 워크 등에서 오브젝트가 만들어 진다.

User user = new User();

이렇게 만들어진 User 오브젝트의 UserPolicyDaoEmailService 필드에는 null 값이 들어 있을 것이다. 당연히 NullPointerException이 발생한다.

이제 @Configurable을 이용해 DI 애스펙트를 적용해보자. 아주 간단하다.


@Configurable public class User { //... }

어드바이스 자동 DI를 수행할 때 이떤 방식을 사용할지 결정하는 일이다. 세가지가 있는데 살펴보자.

설정

@Configurable이 붙은 클래스는 스프링의 빈이 아니고, 빈으로 등록될 필요도, 할수도 없다. 태그를 사용했다고 해서 빈으로 만들어지는 것은 아니다. 빈 오브젝트가 불필요하게 만들어지는일을 피하기 위해 abstract="true"를 넣어서 추상 빈으로 등록하면 편이 좋다.


<bean class="springbook ... User" abstract="true" > <property name="userPolicyDao" ref=" userPolicyDao " /> <property name="emailService" ref="emailService" /> </bean>

자동 와이어링

수정자 메소드를 준비해놨다면 자동와이어링 방식을 적용할 수도 있다. 이때는 다음과 같이 @Configurable 애노테이션의 autowire 속성에 자동와이어링 방식을 지정해주면 된다.
BY_NAME 대신 BY_TYPE을 사용할 수도 있다.

@Configurable(autowire=Autowire.BY_NAME) 
public class User {
//...
}

자동 와이어링을 사용할 때는 XML 설정이 필요 없다. 보통 도메인 오브젝트는 많은 수정자 메소드를 갖고 있기 마련이라서, 자칫 원하지 않는 정보가 주입될 수도 있고, 자동와이어링 작업에 불필요한 시간을 소모한다는 단점도 있다.

어노테이션 의존 관계 설정

어노테이션 방식의 의존관계를 설정하게 되어 있다면 @Autowired@Resource를 이용할 수도 있다. 필드 주입 방식을 이용해 DI하도록 User 클래스 코드를 작성했다면 수정자 메소드도 생략 가능하다. 도메인 오브젝트에 스프링 어노테이션을 사용하는 데 불만이 없다면 가장 단순하고 적용하기 쉬운 방법이다. 단 필드 주입 방식을 선택했다고 해도 단위 테스트를 위해서라면 수정자 메소드는 생략하지 않는게 좋다.

public class User {
@Autowired private UserPolicyDao userPolicyDao; 
@Autowired private EmailService emailService;
}

User를 <bean> 으로 등록할 필요는 없지만, 어노테이션을 이용한 DI를 적용하기 위한 <context:annotation-config>와 같은 설정은 반드시 필요하다.