문제

나는 일반적인 봄 질문을 물었다. 자동 캐스트 스프링 콩 그리고 여러 사람이 스프링을 부르는 것에 응답하도록했습니다 ApplicationContext.getBean() 가능한 한 많이 피해야합니다. 왜 그런 겁니까?

스프링을 구성하기 위해 구성한 콩에 어떻게 접근해야합니까?

비 WEB 응용 프로그램에서 Spring을 사용하고 있으며 공유에 액세스 할 계획이었습니다. ApplicationContext 물체 Liorh가 설명한대로.

개정

아래의 답을 받아들이지 만 여기에 Martin Fowler의 대안이 있습니다. 서비스 로케이터 사용 대 종속성 주입의 장점에 대해 논의합니다. (본질적으로 포장 된 것을 부르는 것과 동일합니다. ApplicationContext.getBean()).

파울러는 부분적으로 말합니다. "서비스 로케이터를 사용하면 애플리케이션 클래스가 로케이터에게 메시지를 통해 명시 적으로 [서비스]를 요구합니다. 주입으로 명시적인 요청이 없으므로 서비스는 응용 프로그램 클래스에 나타나므로 제어의 역전이 나타납니다. 통제의 역전은 프레임 워크의 일반적인 특징이지만 가격에 따른 것입니다. 이해하기 어려운 경향이 있으며 디버그를 시도 할 때 문제가 발생합니다. 따라서 전체적으로 나는 그것을 필요로하지 않는 한 그것을 피하는 것을 선호합니다. 이것은 나쁜 일이라고 말하는 것이 아니라, 더 간단한 대안에 대해 스스로를 정당화해야한다고 생각합니다."

도움이 되었습니까?

해결책

나는 이것을 다른 질문에 대한 의견으로 언급했지만 통제의 역전에 대한 전체 아이디어는 당신의 수업 중 어느 것도 그들이 의존하는 물건을 얻는 방법을 알고 있거나 신경 쓰지 않습니다.. 이를 통해 언제든지 사용하는 주어진 의존성의 구현 유형을 쉽게 변경할 수 있습니다. 또한 종속성의 모의 구현을 제공 할 수 있으므로 클래스를 쉽게 테스트 할 수 있습니다. 마지막으로 수업을 만듭니다 더 간단합니다 그리고 그들의 핵심 책임에 더 집중했습니다.

부름 ApplicationContext.getBean() 통제의 역전이 아닙니다! 주어진 Bean 이름에 대해 구현 된 내용을 변경하기는 쉽지만, 클래스는 이제 Spring에 직접 의존하여 해당 의존성을 제공하고 다른 방법으로는 얻을 수 없습니다. 당신은 단지 테스트 클래스에서 자신의 모의 구현을 만들 수없고 그것을 직접 전달할 수는 없습니다. 이것은 기본적으로 종속성 주입 컨테이너로서의 봄의 목적을 물리칩니다.

당신이 말하고 싶은 곳마다 :

MyClass myClass = applicationContext.getBean("myClass");

예를 들어 방법을 선언해야합니다.

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

그런 다음 구성에서 :

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

그런 다음 스프링이 자동으로 주입됩니다 myClass ~ 안으로 myOtherClass.

이런 식으로 모든 것을 선언하면 그 근원에는 다음과 같은 것이 있습니다.

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication 가장 중앙 클래스이며 프로그램의 다른 모든 서비스에 적어도 간접적으로 의존합니다. 부트 스트랩을 할 때, 당신의 main 방법, 호출 할 수 있습니다 applicationContext.getBean("myApplication") 그러나 전화 할 필요는 없습니다 getBean() 다른 곳!

다른 팁

제어 역전 (IOC)보다 서비스 로케이터를 선호하는 이유는 다음과 같습니다.

  1. 서비스 로케이터는 다른 사람들이 코드를 따르는 것이 훨씬 쉽습니다. IOC는 '마법'이지만 유지 보수 프로그래머는 복잡한 스프링 구성과 모든 무수한 위치를 이해하여 객체를 어떻게 연결하는지 알아 내야합니다.

  2. IOC는 구성 문제를 디버깅하는 데 끔찍합니다. 특정 클래스의 응용 프로그램에서 오해 할 때 응용 프로그램이 시작되지 않으며 디버거로 진행되는 일을 밟을 기회가 없을 수 있습니다.

  3. IOC는 주로 XML 기반입니다 (주석은 물건을 개선하지만 여전히 많은 XML이 있습니다). 즉, 개발자는 스프링으로 정의 된 모든 마법 태그를 알지 못하면 프로그램에서 작업 할 수 없습니다. 더 이상 Java를 아는 것만으로는 충분하지 않습니다. 이것은 경험 프로그래머를 덜 방해합니다 (즉, 서비스 로케이터와 같은 더 간단한 솔루션이 동일한 요구 사항을 충족 할 때 더 복잡한 솔루션을 사용하는 것은 실제로 설계가 좋지 않습니다). 또한 XML 문제 진단에 대한 지원은 Java 문제에 대한 지원보다 훨씬 약합니다.

  4. 의존성 주입은 더 큰 프로그램에 더 적합합니다. 대부분의 경우 추가 복잡성은 그만한 가치가 없습니다.

  5. "나중에 구현을 변경하고 싶을 수도있는 경우"경우 종종 스프링이 사용됩니다. 스프링 IOC의 복잡성없이이를 달성하는 다른 방법이 있습니다.

  6. 웹 애플리케이션 (Java EE Wars)의 경우 봄 컨텍스트는 컴파일 시간에 효과적으로 구속됩니다 (폭발 된 전쟁에서 운영자가 맥락을 중심으로 운영자가 원하지 않는 한). Spring 사용 속성 파일을 만들 수 있지만 Servlets를 사용하면 속성 파일이 사전 결정된 위치에 있어야하므로 동일한 상자에 동시에 여러 서블릿을 배포 할 수 없습니다. Servlet 시작 시간에 Spring과 함께 Spring을 사용하여 Servlet 시작 시간에 속성을 변경할 수 있지만 관리자 수용 가능한 매개 변수에 JNDI를 사용하는 경우 스프링 자체의 필요성이 줄어 듭니다 (JNDI는 효과적으로 서비스 로케이터이므로).

  7. 스프링을 사용하면 스프링이 방법으로 파견되면 프로그램 제어를 잃을 수 있습니다. 이것은 편리하고 많은 유형의 응용 프로그램에서 작동하지만 전부는 아닙니다. 초기화 중에 작업 (스레드 등)을 만들어야 할 때 프로그램 흐름을 제어하거나 컨텐츠가 전쟁에 묶여있는시기에 대해 Spring이 알지 못하는 수정 가능한 리소스가 필요할 수 있습니다.

Spring은 거래 관리에 매우 유용하며 몇 가지 장점이 있습니다. 그것은 단지 많은 상황에서 IOC가 과도하게 엔지니어링 할 수 있고 유지 관리자에게는 부당한 복잡성을 도입 할 수 있다는 것입니다. 먼저 사용하지 않는 방법을 생각하지 않고 IOC를 자동으로 사용하지 마십시오.

Application-context.xml에 클래스를 포함 시키면 GetBean을 사용할 필요가 없습니다. 그러나 그조차도 실제로는 불필요합니다. 독립형 애플리케이션을 작성하고 있고 Application-context.xml에 드라이버 클래스를 포함시키지 않으려면 다음 코드를 사용하여 Spring Autowire가 운전자의 종속성을 갖도록 할 수 있습니다.

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

내 앱의 일부 측면 (예 : 테스트를 위해)을 사용해야하는 독립형 클래스가있을 때는 두 번해야했지만 응용 프로그램 컨텍스트에 포함시키고 싶지는 않습니다. 실제로 앱의 일부. 또한 이것은 항상 추악하다고 생각한 문자열 이름을 사용하여 Bean을 찾아야 할 필요가 없습니다.

Spring과 같은 것을 사용하면 가장 멋진 이점 중 하나는 물체를 함께 연결하지 않아도된다는 것입니다. Zeus의 헤드가 열리고 수업이 나타나고 필요에 따라 모든 종속성이 생성되고 유선으로 완전히 형성됩니다. 마법적이고 환상적입니다.

더 많이 말할 수 있습니다 ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");, 당신이 얻는 마술이 적습니다. 코드는 거의 항상 더 좋습니다. 수업이 정말로 분류 된 콩이 필요하다면 왜 그냥 연결하지 않았습니까?

즉, 첫 번째 객체를 만들려면 분명히 무언가가 필요합니다. GetBean ()을 통해 콩을 획득하는 주요 방법에는 아무런 문제가 없지만, 사용할 때마다 실제로 스프링의 마법을 사용하지 않기 때문에 피해야합니다.

동기는 봄에 명시 적으로 의존하지 않는 코드를 작성하는 것입니다. 이렇게하면 컨테이너를 전환하기로 선택한 경우 코드를 다시 작성할 필요가 없습니다.

컨테이너를 코드에 보이지 않는 것으로 생각하며, 요구받지 않고 그 요구를 마술처럼 제공합니다.

의존성 주입은 "서비스 로케이터"패턴에 대한 대응책입니다. 이름으로 의존성을 조회하려면 DI 컨테이너를 제거하고 JNDI와 같은 것을 사용할 수 있습니다.

사용 @Autowired 또는 ApplicationContext.getBean() 정말 똑같습니다. 두 가지 방법 모두 컨텍스트와 두 가지 방식으로 구성된 콩을 얻습니다. 코드는 스프링에 의존합니다. 피해야 할 유일한 것은 ApplicationContext를 인스턴스화하는 것입니다. 한 번만 해! 다시 말해, 선과 같은 줄

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

응용 프로그램에서 한 번만 사용해야합니다.

아이디어는 의존성 주입에 의존한다는 것입니다 (제어의 역전, 또는 IOC). 즉, 구성 요소는 필요한 구성 요소로 구성됩니다. 이러한 종속성은 다음과 같습니다 주사 (생성자 또는 세터를 통해) - 당신은 자신을 얻지 못합니다.

ApplicationContext.getBean() 구성 요소 내에서 Bean의 이름을 명시 적으로 이름을 지정해야합니다. 대신 IOC를 사용하면 구성이 사용될 구성 요소를 결정할 수 있습니다.

이를 통해 다양한 구성 요소 구현으로 응용 프로그램을 다시 사용하거나 조롱 된 변형을 제공하여 간단한 방식으로 테스트하기위한 객체를 구성 할 수 있습니다 (예 : 조롱 된 DAO가 있으므로 테스트 중에 데이터베이스를 칠하지 않음).

다른 사람들은 일반적인 문제를 지적했지만 (그리고 유효한 답변) 하나의 추가 의견을 제시 할 것입니다. 절대로하지 말아야 할 것이 아니라 가능한 한 적은 일을해야한다는 것입니다.

일반적으로 이것은 정확히 한 번 수행되었음을 의미합니다. 부트 스트래핑 중에. 그런 다음 다른 종속성을 해결할 수있는 "루트"콩에 액세스하는 것입니다. 기본 서블릿 (웹 앱을 개발하는 경우)과 같은 재사용 가능한 코드 일 수 있습니다.

봄 구내 중 하나는 피하는 것입니다 커플 링. 인터페이스, DI, AOP를 정의하고 사용하고 ApplicationContext.getBean () :-) 사용하지 마십시오.

이유 중 하나는 테스트 가능성입니다. 이 수업이 있다고 말합니다.

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

이 콩을 어떻게 테스트 할 수 있습니까? 예 : 이렇게 :

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

쉽지?

여전히 봄에 의존하지만 (주석으로 인해) 코드를 변경하지 않고 스프링에 대한 의존성을 제거 할 수 있으며 (주석 정의 만) 테스트 개발자는 스프링의 작동 방식에 대해 아무것도 알 필요가 없습니다 (어쨌든 그는 어쨌든해야 할 것입니다. 스프링과 별도로 코드를 검토하고 테스트 할 수 있습니다).

ApplicationContext를 사용할 때도 여전히 동일한 작업을 수행 할 수 있습니다. 그러나 당신은 조롱해야합니다 ApplicationContext 큰 인터페이스입니다. 더미 구현이 필요하거나 Mockito와 같은 조롱 프레임 워크를 사용할 수 있습니다.

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

이것은 가능성이 있지만, 대부분의 사람들은 첫 번째 옵션이 더 우아하고 테스트를 더 간단하게 만든다는 데 동의 할 것입니다.

실제로 문제인 유일한 옵션은 이것입니다.

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

테스트하려면 큰 노력이 필요하거나 콩이 각 테스트마다 stackoverflow에 연결하려고 시도 할 것입니다. 네트워크 고장 (또는 StackoverFlow의 관리자가 과도한 액세스 속도로 인해 블록)이있는 즉시 무작위로 실패한 테스트를 받게됩니다.

결론적으로 나는 ApplicationContext 직접적으로 자동으로 잘못되며 모든 비용으로 피해야합니다. 그러나 더 나은 옵션이 있고 (대부분의 경우) 더 나은 옵션을 사용하십시오.

getBean ()가 필요한 두 가지 상황 만 발견했습니다.

다른 사람들은 main ()에서 getBean ()을 사용하여 독립형 프로그램의 "메인"콩을 가져 오는 것을 언급했습니다.

내가 getBean ()로 만든 또 다른 용도는 대화식 사용자 구성이 특정 상황에 대한 Bean 메이크업을 결정하는 상황입니다. 예를 들어, 부팅 시스템의 일부는 scope = 'prototype'bean 정의가있는 getBean ()을 사용하여 데이터베이스 테이블을 통해 루프를하고 추가 속성을 설정합니다. 아마도 응용 프로그램 컨텍스트 XML을 작성하는 것보다 더 친근한 데이터베이스 테이블을 조정하는 UI가있을 것입니다.

GetBean을 사용하는 것이 의미가 있습니다. 이미 존재하는 시스템을 재구성하는 경우 스프링 컨텍스트 파일에서 종속성이 명시 적으로 호출되지 않은 경우. GetBean에 전화를 걸어 프로세스를 시작할 수 있으므로 한 번에 모두 연결하지 않아도됩니다. 이렇게하면 시간이 지남에 따라 각 조각을 제자리에 놓고 비트를 올바르게 정리하는 스프링 구성을 천천히 구축 할 수 있습니다. GetBean에 대한 호출은 결국 교체되지만 코드의 구조를 이해하거나 부족한 경우 점점 더 많은 콩을 배선하고 GetBean에 대한 호출을 줄이고 적은 수의 호출을 시작할 수 있습니다.

그러나 서비스 로케이터 패턴이 필요한 경우가 여전히 있습니다. 예를 들어, 컨트롤러 빈이 있고이 컨트롤러에는 기본 서비스 Bean이있을 수 있으며 구성에 따라 종속성을 주입 할 수 있습니다. 이 컨트롤러가 지금 또는 나중에 호출 할 수있는 많은 추가 또는 새로운 서비스가있을 수 있지만 서비스 로케이터는 서비스 Bean을 검색해야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top