java - use - Por que meu campo Spring @Autowired é nulo?




spring use autowired (9)

Nota: Esta destina-se a ser uma resposta canônica para um problema comum.

Eu tenho uma classe Spring @Service ( MileageFeeCalculator ) que tem um campo rateService ( rateService ), mas o campo é null quando tento usá-lo. Os logs mostram que o bean MileageFeeCalculator e o bean MileageRateService estão sendo criados, mas recebo um NullPointerException sempre que tento chamar o método mileageCharge no meu bean de serviço. Por que o Spring não está autowiring o campo?

Classe do controlador:

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

Classe de serviço:

@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
    }
}

Bean de serviço que deve ser autowired no MileageFeeCalculator mas não é:

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

Quando tento obter GET /mileage/3 , recebo esta exceção:

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

Certa vez, encontrei o mesmo problema quando não estava acostumado com the life in the IoC world . O campo @Autowired de um dos meus beans é nulo no tempo de execução.

A causa raiz é, em vez de usar o bean auto-criado mantido pelo contêiner Spring IoC (cujo campo @Autowired é indeed injetado corretamente), estou newing minha própria instância desse tipo de bean e usando-o. É claro que o campo @Autowired é nulo porque a Spring não tem chance de injetá-lo.


Eu acho que você perdeu para instruir a primavera para digitalizar as aulas com anotações.

Você pode usar @ComponentScan("packageToScan") na classe de configuração do seu aplicativo de mola para instruir a mola a escanear.

@Service, @Component etc. adicionam meta descrição.

O Spring apenas injeta instâncias daquelas classes que são criadas como bean ou marcadas com anotação.

As classes marcadas com anotação precisam ser identificadas pela mola antes da injeção, @ComponentScan instruct spring procura pelas classes marcadas com anotação. Quando o Spring encontra o @Autowired ele procura o bean relacionado e injeta a instância necessária.

Adicionando somente anotações, não corrige ou facilita a injeção de dependência, o Spring precisa saber onde procurar.


O campo anotado @Autowired é null porque o Spring não sabe sobre a cópia do MileageFeeCalculator que você criou com o new e não sabia para o autowire.

O contêiner Spring Inversion of Control (IoC) possui três componentes lógicos principais: um registro (chamado de ApplicationContext ) de componentes (beans) que estão disponíveis para serem usados ​​pelo aplicativo, um sistema configurador que injeta dependências de objetos neles fazendo a correspondência as dependências com beans no contexto e um resolvedor de dependências que pode examinar uma configuração de muitos beans diferentes e determinar como instanciar e configurá-los na ordem necessária.

O contêiner IoC não é mágico e não tem como saber sobre objetos Java, a menos que você os informe de alguma forma. Quando você chama new , a JVM instancia uma cópia do novo objeto e o entrega diretamente a você - ele nunca passa pelo processo de configuração. Existem três maneiras de configurar seus beans.

Eu publiquei todo este código, usando o Spring Boot para iniciar, neste projeto do GitHub ; Você pode olhar para um projeto completo em execução para cada abordagem para ver tudo o que você precisa para fazê-lo funcionar. Marcar com o NullPointerException : nonworking

Injete seus feijões

A opção mais preferível é permitir que a Spring autowire todos os seus grãos; isso requer a menor quantidade de código e é o mais sustentável. Para fazer a autowiring funcionar como você queria, também autowire o MileageFeeCalculator assim:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Se você precisar criar uma nova instância de seu objeto de serviço para solicitações diferentes, ainda poderá usar a injeção usando os escopos do bean Spring .

Tag que funciona injetando o objeto de serviço @MileageFeeCalculator : working-inject-bean

Use @Configurable

Se você realmente precisa de objetos criados com o new para ser autowired, você pode usar a anotação Spring @Configurable juntamente com tecelagem em tempo de compilação do AspectJ para injetar seus objetos. Essa abordagem insere código no construtor do seu objeto que alerta o Spring que está sendo criado para que o Spring possa configurar a nova instância. Isso requer um pouco de configuração em sua compilação (como compilar com ajc ) e ativar os manipuladores de configuração de tempo de execução do Spring ( @EnableSpringConfigured com a sintaxe JavaConfig). Essa abordagem é usada pelo sistema Roo Active Record para permitir que new instâncias de suas entidades obtenham as informações de persistência necessárias injetadas.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

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

Tag que funciona usando @Configurable no objeto de serviço: working-configurable

Pesquisa de bean manual: não recomendado

Essa abordagem é adequada apenas para interface com código legado em situações especiais. Quase sempre é preferível criar uma classe de adaptadores singleton que o Spring possa reproduzir automaticamente e o código legado possa chamar, mas é possível perguntar diretamente ao contexto do aplicativo Spring um bean.

Para fazer isso, você precisa de uma classe para a qual o Spring possa fornecer uma referência ao objeto 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;
    }
}

Então seu código legado pode chamar getContext() e recuperar os beans de que precisa:

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

Tag que funciona procurando manualmente o objeto de serviço no contexto Spring: working-manual-lookup


Observe também que se, por qualquer motivo, você fizer um método em um @Service como final , os beans autocuados que você acessará dele serão sempre null .


Parece ser um caso raro, mas aqui está o que aconteceu comigo:

Usamos o @Inject vez do @Autowired que é o padrão @Autowired suportado pelo Spring. Todos os lugares funcionavam bem e os grãos eram injetados corretamente, em vez de um lugar. A injeção de feijão parece a mesma

@Inject
Calculator myCalculator

Finalmente descobrimos que o erro foi que nós (na verdade, o recurso auto complete do Eclipse) importamos com.opensymphony.xwork2.Inject vez de javax.inject.Inject !

Então, para resumir, certifique-se de que suas anotações ( @Autowired , @Inject , @Service , ...) tenham pacotes corretos!


Se isso estiver acontecendo em uma classe de teste, não esqueça de anotar a classe.

Por exemplo, no Spring Boot :

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
    ....

Seu problema é novo (criação de objetos no estilo java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Com a anotação @Service , @Component , @Configuration beans são criados no
contexto de aplicação do Spring quando o servidor é iniciado. Mas quando criamos objetos usando o novo operador, o objeto não é registrado no contexto do aplicativo que já está criado. Por exemplo, a classe Employee.java eu ​​usei.

Veja isso:

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

}

Você também pode corrigir esse problema usando a anotação @Service na classe de serviço e passando o bean obrigatório classeA como um parâmetro para o outro construtor de classesB e anotar o construtor de classB com @Autowired. Exemplo de fragmento aqui:

@Service
public class ClassB {

    private ClassA classA;

    @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}

ATUALIZAÇÃO: pessoas realmente inteligentes foram rápidas em apontar this resposta, o que explica a estranheza, descrita abaixo

RESPOSTA ORIGINAL:

Eu não sei se isso ajuda alguém, mas eu estava preso com o mesmo problema, mesmo fazendo as coisas aparentemente certas. No meu método principal, eu tenho um código como este:

ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {
        "common.xml",
        "token.xml",
        "pep-config.xml" });
    TokenInitializer ti = context.getBean(TokenInitializer.class);

e em um arquivo token.xml eu tive uma linha

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

Notei que o package.path não existe mais, então acabei de deixar a linha para sempre.

E depois disso, a NPE começou a entrar. Em um pep-config.xml eu tinha apenas 2 beans:

<bean id="someAbac" class="com.pep.SomeAbac" init-method="init"/>
<bean id="settings" class="com.pep.Settings"/>

e SomeAbac classe tem uma propriedade declarada como

@Autowired private Settings settings;

por alguma razão desconhecida, settings é null em init (), quando o elemento <context:component-scan/> não está presente, mas quando está presente e tem alguns bs como basePackage, tudo funciona bem. Esta linha agora se parece com isso:

<context:component-scan base-package="some.shit"/>

e funciona. Pode ser que alguém possa fornecer uma explicação, mas para mim já é suficiente







autowired