7

Почему поле с @Autowired в Spring оказывается null?

8

Примечание: Это предназначено быть каноническим ответом на распространённую проблему.

У меня есть класс Spring с аннотацией @Service (MileageFeeCalculator), который имеет поле с аннотацией @Autowired (rateService), однако это поле оказывается null, когда я пытаюсь его использовать. Логи показывают, что оба бина MileageFeeCalculator и MileageRateService создаются, но при попытке вызвать метод mileageCharge на моём сервис-бине возникает NullPointerException. Почему Spring не выполняет автозаполнение данного поля?

Класс контроллера:

@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; // <--- должен быть автозаполнен, но равен null

public float mileageCharge(final int miles) {
    return (miles * rateService.ratePerMile()); // <--- вызывает NPE
}

}

Сервис-бин, который должен быть автозаполнен в MileageFeeCalculator, но это не происходит:

@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)
    ...

5 ответ(ов)

0

Если вы не разрабатываете веб-приложение, убедитесь, что ваш класс, в котором используется аннотация @Autowired, является бином Spring. Обычно контейнер Spring не осведомлён о классе, который мы можем считатьSpring-биным. Нам нужно сообщить контейнеру Spring о наших классах.

Это можно сделать, настроив файл конфигурации в application-context, или лучший способ — аннотировать класс как @Component и, пожалуйста, не создавайте аннотированный класс с помощью оператора new. Убедитесь, что вы получаете его из контекста приложения, как показано ниже:

@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();
    }
}

Таким образом, убедитесь, что класс MyDemo правильно аннотирован и получает зависимости через контейнер Spring, чтобы избежать возможных проблем с инъекцией зависимостей.

0

На самом деле, вам следует использовать либо объекты, управляемые JVM, либо объекты, управляемые Spring, для вызова методов. В вашем коде контроллера вы создаете новый объект для вызова вашего сервисного класса, который имеет авто-распределенный объект.

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);
    }
}

Таким образом, Spring позаботится о создании и управлении жизненным циклом объекта MileageFeeCalculator, что обеспечит правильную работу автозаполненных зависимостей.

0

Я тоже столкнулся с этой проблемой, когда еще не привык к жизни в мире IoC. Поле @Autowired одного из моих бинов оказалось нулевым во время выполнения.

Корень проблемы в том, что вместо использования авто-созданного бина, который поддерживается контейнером Spring IoC (где поле @Autowired правильно инъектируется), я создал свой собственный экземпляр этого типа бина с помощью оператора new и использовал его. Конечно, поле @Autowired в этом случае будет нулевым, потому что у Spring не было возможности выполнить его инъекцию.

0

Ваш вопрос касается создания объектов в стиле Java (с использованием оператора new) и их регистрации в контексте приложения Spring.

Когда вы используете аннотации, такие как @Service, @Component или @Configuration, Spring автоматически создает бины и регистрирует их в контексте приложения при старте сервера. Однако, когда вы создаете объекты с помощью оператора new, эти объекты не регистрируются в уже созданном контексте приложения.

В приведённом вами примере, вы пытаетесь зарегистрировать новый объект типа Employee в контексте приложения через BeanFactoryPostProcessor. Однако важно отметить, что использование оператора new для создания экземпляров классов (например, new Employee()) делает их обычными объектами Java, которые не под управлением Spring и, следовательно, не попадают в контекст Spring.

Чтобы корректно зарегистрировать Employee как бин в контексте Spring, вам нужно либо использовать аннотации, либо программно добавлять бины через BeanDefinitionRegistry. В вашем коде вы начинаете с создания Employee, что является неверным с точки зрения регистрации в Spring. Вместо этого, вам нужно создавать его как Spring-бин:

@Bean
public Employee employee() {
    return new Employee();
}

Или использовать @Component вместе с дальнейшей конфигурацией для управления областью видимости.

Если вы хотите зарегистрировать бины в конкретной области, вы можете использовать уже имеющийся код, но создайте ваши объекты с помощью Spring-методов, чтобы они были полностью интегрированы в его контейнер и управление.

0

Вы можете использовать предложенное решение, но стоит отметить, что внедрение ApplicationContext в статическую переменную не является лучшей практикой в Spring. Дело в том, что это нарушает принципы инверсии контроля и управления зависимостями, которые являются основополагающими для Spring.

Хотя ваш код будет работать, и с его помощью можно получать любые бины из контекста, использование статического контекста может привести к трудностям в тестировании и увеличивает связанность компонентов. Рекомендуется избегать использования статических методов и переменных для доступа к бинам.

Вместо этого, лучше всего использовать зависимости через инъекции непосредственно в классы, где они необходимы. Это делает код более чистым и легче тестируемым.

Если вам нужно получить доступ к ApplicationContext, рассмотрите возможность внедрения его в нужные классы через конструктор или поля, а не через статические ссылки. Это поможет сохранить принципы SOLID и повысить гибкость вашего приложения.

Если вы хотите знать, как можно улучшить вашу реализацию, я с радостью помогу.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь