[spring] static에서 spring bean 가져오기

먼저 목적은 아래와 같은 코드가 작동하게 하는 것이다.

@Component
public class Foo {
    public void doA(){
        System.out.println("doA");
    }
}

public class Boo {
    @Autowired
    public static Foo foo;
}

public class ApplicationTests {
    @Test
    public void contextLoads() {
        Boo.foo.doA();
    }
}

물론 디자인 상으로는 클래스에 저장되는 static 자원에서 객체를 가리키게 하는 것은 좋지 않지만, 이런 상황에서 그나마 깔끔하게 처리하는 방법은 없을까?

문제점 :

  1. 먼저 @Autowired는 Spring에 의해서 관리되야 주입을 해주는데, Boo는 현재 그렇지 않다.
  2. 아무리 Boo에서는 클래스 레벨에서 Foo를 사용하고 싶다고 하더라도 객체는 클래스 로딩된 후에 생성되기 때문에 Boo 클래스가 로딩될 당시에 Foo 객체는 존재하지 않는다.

해결책 :

  • Boo 도 싱글턴 객체로 스프링에서 관리되게 등록해준다.
  • Boo 객체가 생성될 때 Foo 객체를 가져오고, static 필드에 넘겨준다.

먼저 첫번째 방법은 @PostContruct를 이용해서 static 필드에 값을 넘겨주는 방법이다.

@Component
public class Boo {

    public static Foo foo;

    @Autowired
    private Foo fooInstance;

    @PostConstruct
    private void init() {
        foo = fooInstance;
    }
}

두번째 방법은 생성자에 @Autowired를 사용하는 방법이다.

@Component
public class Boo {

    public static Foo foo;

    @Autowired
    private Boo(Foo foo) {
        this.foo = foo;
    }
}

생성자나 init 같은 함수들은 private으로 하더라도 Spring이 알아서 접근해서 호출해준다. 생성자에 @Autowired는 파라미터로 들어오는 bean을 주입시켜 준다.

참고 : http://stackoverflow.com/questions/17659875/autowired-and-static-method

ApplicationContext vs DispatcherServlet 2 : 컨텍스트 간의 bean 공유?

ApplicationContext와 DispatcherServlet은 부모와 자식 관계로 연결되어 있는데, 왜 component-scan은 양쪽 다 해야할까? 두 컨텍스트는 같은 빈들을 가리키는 걸까? 밑에 예제에서는 ApplicationContextHolder 라는 클래스를 만들어서 두 컨텍스트를 가지고 있다가, 두 컨텍스트가 가지고 있는 빈의 인스턴스가 같은지 비교해보았다.


application-context.xml


<?xml version="1.0" encoding="UTF-8"?>


<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.tistory"></context:component-scan>

    

    <bean class="com.tistory.jekalmin.common.ApplicationContextHolder"/>

<bean class="com.tistory.jekalmin.dao.TestJdbcTemplate"/>

</beans>

TestJdbcTemplate은 applicationContext에만 등록하고, dispatcherServlet에는 등록하지 않기 위해 어노테이션을 사용하지않고 application-context.xml에서 bean으로 등록하였다.


spring-servlet.xml


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"

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

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.tistory.jekalmin"/>

<context:annotation-config/>

<bean class="com.tistory.jekalmin.common.ApplicationContextHolder"/>

<bean class="com.tistory.jekalmin.dao.TestJdbcTemplate"/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/view/"/>

<property name="suffix" value=".jsp"/>

</bean>

</beans>

application-context.xml과 spring-servlet.xml 양쪽에 component-scan을 하였다.


ApplicationContextHolder.java


package com.tistory.jekalmin.common;


import java.util.ArrayList;

import java.util.List;


import org.springframework.beans.BeansException;

import org.springframework.context.ApplicationContext;

import org.springframework.context.ApplicationContextAware;


public class ApplicationContextHolder implements ApplicationContextAware{

public static List<ApplicationContext> list = new ArrayList<ApplicationContext>();

@Override

public void setApplicationContext(ApplicationContext ctx)

throws BeansException {

list.add(ctx);

}


}

두 컨텍스트에서 ApplicationContextHolder 빈을 각각 생성할 때, ApplicationContextHolder 클래스에서 컨텍스트에 접근할 수 있도록 컨텍스트를 등록하였다.


TestDao.java


package com.tistory.jekalmin.dao;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

@Repository

public class TestDao {

@Autowired

public TestJdbcTemplate jdbcTemplate;

}

TestDao는 어노테이션을 이용해 등록했으므로 양쪽 컨텍스트에서 component-scan을 이용해 등록될 것이다.


TestJdbcTemplate.java


package com.tistory.jekalmin.dao;

public class TestJdbcTemplate {

}

TestJdbcTemplate은 위에 application-context.xml에서만 등록하였다.


TestController.java


package com.tistory.jekalmin.controller;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationContext;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import com.tistory.jekalmin.common.ApplicationContextHolder;

import com.tistory.jekalmin.dao.TestDao;

@Controller

public class TestController {

@Autowired

private TestDao testDao;

@RequestMapping("/")

public void index(){

for(ApplicationContext ctx : ApplicationContextHolder.list){

System.out.format("[%s] testController hashCode : %d\n", ctx.getId(), ctx.getBean("testController").hashCode());

System.out.format("[%s] testDao hashCode : %d\n", ctx.getId(), ctx.getBean("testDao").hashCode());

System.out.format("[%s] testJdbcTemplate hashCode : %d\n", ctx.getId(), testDao.jdbcTemplate.hashCode());

}

}

}

이제 url을 호출하여 테스트 해보자. 테스트 내용은 기존에 ApplicationContext에서 가져온 빈과 DispatcherServlet에서 가져온 빈의 hashCode를 비교해 인스턴스가 같은지 다른지 확인하였다.

url 호출 결과는 아래와 같다.


[org.springframework.web.context.WebApplicationContext:] testController hashCode : 631271355

[org.springframework.web.context.WebApplicationContext:] testDao hashCode : 1666005388

[org.springframework.web.context.WebApplicationContext:] testJdbcTemplate hashCode : 1060031077

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testController hashCode : 319851274

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testDao hashCode : 896263012

[org.springframework.web.context.WebApplicationContext:/dispatcherServlet] testJdbcTemplate hashCode : 1060031077

결과를 보면 testJdbcTemplate만 hashCode가 같고, testController와 testDao는 다른 것을 확인 할 수 있다.

결론 : ApplicationContext와 DispatcherServlet에서 component-scan을 이용해서 등록된 빈들은 다른 인스턴스이다. 다만 ApplicationContext에만 등록된 빈의 경우 DispatcherServlet에서 검색해보고 없으면 부모인 ApplicationContext에서 가져온다.