[java] Spring @Autowired 필드가 왜 null입니까?

참고 : 이것은 일반적인 문제에 대한 정식 답변입니다.

필드 ( ) 가있는 Spring @Service클래스 ( MileageFeeCalculator)가 있지만 필드는 사용하려고합니다. 로그는 Bean과 Bean이 모두 생성되고 있음을 보여 주지만 서비스 Bean 에서 메소드 를 호출하려고 할 때마다 메시지가 표시 됩니다. Spring이 필드를 자동 배선하지 않는 이유는 무엇입니까?@AutowiredrateServicenullMileageFeeCalculatorMileageRateServiceNullPointerExceptionmileageCharge

컨트롤러 클래스 :

@Controller
public class MileageFeeController {
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

서비스 클래스 :

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- should be autowired, is null

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); // <--- throws NPE
    }
}

자동 연결되어야 MileageFeeCalculator하지만 그렇지 않은 서비스 Bean :

@Service
public class MileageRateService {
    public float ratePerMile() {
        return 0.565f;
    }
}

시도 할 GET /mileage/3때이 예외가 발생합니다.

java.lang.NullPointerException: null
    at com.chrylis.example.spring_autowired_npe.MileageFeeCalculator.mileageCharge(MileageFeeCalculator.java:13)
    at com.chrylis.example.spring_autowired_npe.MileageFeeController.mileageFee(MileageFeeController.java:14)
    ...



답변

주석 @Autowired이 달린 필드 는 nullSpring이 MileageFeeCalculator생성 한 사본에 대해 Spring이 알지 new못하고 자동 와이어 링을 알지 못했기 때문입니다.

IoC (Spring Inversion of Control) 컨테이너 에는 3 가지 주요 논리 구성 요소 ApplicationContext가 있습니다. 컨텍스트에서 Bean과의 종속성 및 여러 다른 Bean의 구성을보고 필요한 순서로 인스턴스화하고 구성하는 방법을 결정할 수있는 종속성 솔버.

IoC 컨테이너는 마법이 아니며 Java 객체에 대해 알지 못하는 한 Java 객체를 알 수있는 방법이 없습니다. 를 호출 new하면 JVM이 새 객체의 사본을 인스턴스화하여 바로 전달합니다. 구성 프로세스를 거치지 않습니다. Bean을 구성 할 수있는 세 가지 방법이 있습니다.

이 GitHub 프로젝트 에서 Spring Boot를 사용하여이 코드를 모두 게시 했습니다 . 각 접근 방식에 대해 전체 실행 프로젝트를 살펴보고 작동하는 데 필요한 모든 것을 볼 수 있습니다. 태그 NullPointerException:nonworking

콩을 주입

가장 바람직한 옵션은 Spring이 모든 빈을 자동으로 연결하도록하는 것입니다. 이것은 가장 적은 양의 코드를 요구하며 가장 유지 보수가 쉽습니다. 원하는대로 자동 배선을 수행하려면 다음 MileageFeeCalculator과 같이 자동 배선하십시오 .

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}

다른 요청에 대해 서비스 오브젝트의 새 인스턴스를 작성해야하는 경우 Spring Bean 범위를 사용하여 여전히 주입을 사용할 수 있습니다 .

@MileageFeeCalculator서비스 객체 를 주입하여 작동하는 태그 :working-inject-bean

@Configurable 사용

자동 new와이어 링으로 생성 된 객체가 실제로 필요한 경우 AspectJ 컴파일 타임 직조와 함께 Spring @Configurable주석을 사용 하여 객체를 주입 할 수 있습니다. 이 접근법은 Spring이 새 인스턴스를 구성 할 수 있도록 Spring이 작성되고 있음을 경고하는 코드를 객체의 생성자에 삽입합니다. 이를 위해서는 빌드에서 약간의 구성 (예 : 컴파일 ajc) 및 Spring의 런타임 구성 핸들러 ( @EnableSpringConfiguredJavaConfig 구문 사용)가 필요합니다. 이 접근 방식은 Roo Active Record 시스템에서 new엔티티 인스턴스가 필요한 지속성 정보를 가져올 수 있도록 사용 합니다.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

@Configurable서비스 객체 를 사용하여 작동하는 태그 :working-configurable

수동 Bean 조회 : 권장하지 않음

이 방법은 특별한 상황에서 레거시 코드와의 인터페이스에만 적합합니다. Spring이 자동 와이어 링하고 레거시 코드가 호출 할 수있는 싱글 톤 어댑터 클래스를 작성하는 것이 거의 항상 바람직하지만 Spring 애플리케이션 컨텍스트에 Bean을 직접 요청할 수 있습니다.

이를 위해서는 Spring이 ApplicationContext객체에 대한 참조를 제공 할 수있는 클래스가 필요 합니다.

@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static ApplicationContext getContext() {
        return context;
    }
}

그런 다음 레거시 코드는 getContext()필요한 Bean을 호출 하고 검색 할 수 있습니다 .

@Controller
public class MileageFeeController {
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = ApplicationContextHolder.getContext().getBean(MileageFeeCalculator.class);
        return calc.mileageCharge(miles);
    }
}

Spring 컨텍스트에서 서비스 오브젝트를 수동으로 조회하여 작동하는 태그 : working-manual-lookup


답변

웹 애플리케이션을 코딩하지 않는 경우 @Autowiring이 수행되는 클래스가 스프링 빈인지 확인하십시오. 일반적으로 스프링 컨테이너는 스프링 빈으로 생각할 수있는 클래스를 인식하지 못합니다. 스프링 클래스에 대해 스프링 컨테이너에 알려줘야합니다.

appln-contxt에서 구성하여 얻을 수 있거나 클래스에 @Component 로 주석을 달고 더 나은 방법 은 새 연산자를 사용하여 주석이 달린 클래스를 만들지 마십시오. 아래와 같이 Appln-context에서 가져와야합니다.

@Component
public class MyDemo {


    @Autowired
    private MyService  myService;

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            System.out.println("test");
            ApplicationContext ctx=new ClassPathXmlApplicationContext("spring.xml");
            System.out.println("ctx>>"+ctx);

            Customer c1=null;
            MyDemo myDemo=ctx.getBean(MyDemo.class);
            System.out.println(myDemo);
            myDemo.callService(ctx);


    }

    public void callService(ApplicationContext ctx) {
        // TODO Auto-generated method stub
        System.out.println("---callService---");
        System.out.println(myService);
        myService.callMydao();

    }

}


답변

실제로, JVM 관리 오브젝트 또는 스프링 관리 오브젝트를 사용하여 메소드를 호출해야합니다. 컨트롤러 클래스의 위 코드에서 자동 유선 객체가있는 서비스 클래스를 호출하는 새 객체를 만들고 있습니다.

MileageFeeCalculator calc = new MileageFeeCalculator();

그렇게 작동하지 않습니다.

솔루션은이 MileageFeeCalculator를 컨트롤러 자체의 자동 유선 객체로 만듭니다.

아래와 같이 컨트롤러 클래스를 변경하십시오.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}


답변

내가 익숙하지 않을 때 같은 문제가 발생했습니다 the life in the IoC world. @Autowired내 콩의 하나의 필드는 런타임에 null입니다.

근본 원인은 Spring IoC 컨테이너에 의해 유지 관리되는 자동 작성 Bean ( @Autowired필드가 indeed올바르게 주입 됨) 을 사용하는 대신 newing해당 Bean 유형의 고유 한 인스턴스입니다. 물론 @Autowired스프링은 필드를 주입 ​​할 기회가 없기 때문에이 필드는 null입니다.


답변

당신의 문제는 새로운 것입니다 (자바 스타일의 객체 생성)

MileageFeeCalculator calc = new MileageFeeCalculator();

주석으로 @Service, @Component, @Configuration콩이 만들어집니다
서버가 시작될 때 스프링의 애플리케이션 컨텍스트. 그러나 new 연산자를 사용하여 객체를 만들면 이미 만들어진 응용 프로그램 컨텍스트에 객체가 등록되지 않습니다. 예를 들어 Employee.java 클래스를 사용했습니다.

이것 좀 봐:

public class ConfiguredTenantScopedBeanProcessor implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    String name = "tenant";
    System.out.println("Bean factory post processor is initialized");
    beanFactory.registerScope("employee", new Employee());

    Assert.state(beanFactory instanceof BeanDefinitionRegistry,
            "BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used.");
    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
        if (name.equals(definition.getScope())) {
            BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, true);
            registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
        }
    }
}

}


답변

나는 Spring을 처음 사용하지만이 작업 솔루션을 발견했습니다. 그것이 혐오스러운 방법인지 알려주세요.

applicationContext이 빈에 Spring을 주입합니다 .

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils {

    public static ApplicationContext ctx;

    /**
     * Make Spring inject the application context
     * and save it on a static variable,
     * so that it can be accessed from any point in the application.
     */
    @Autowired
    private void setApplicationContext(ApplicationContext applicationContext) {
        ctx = applicationContext;
    }
}

원하는 경우이 코드를 기본 응용 프로그램 클래스에도 넣을 수 있습니다.

다른 클래스는 다음과 같이 사용할 수 있습니다.

MyBean myBean = (MyBean)SpringUtils.ctx.getBean(MyBean.class);

이런 식으로 응용 프로그램의 모든 객체 (와 함께 new)와 정적 방식으로 Bean을 얻을 수 있습니다 .


답변

드문 경우이지만 여기에 나에게 일어난 일이 있습니다.

@Inject대신 @AutowiredSpring에서 지원하는 javaee 표준을 사용 했습니다 . 모든 장소는 잘 작동했고 콩은 한곳이 아닌 올바르게 주입되었습니다. 콩 주입은 같은 것 같습니다

@Inject
Calculator myCalculator

마침내 우리는 (실제로 Eclipse 자동 완성 기능) com.opensymphony.xwork2.Inject대신에 가져온 오류라는 것을 발견했습니다.javax.inject.Inject !

그래서 반드시 주석 (즉, 메이크업을 요약하면 @Autowired, @Inject, @Service, …) 올바른 패키지가!