Por que meu campo Spring @Autowired é nulo?

608

Nota: Esta pretende ser uma resposta canônica para um problema comum.

Eu tenho uma @Serviceclasse Spring ( MileageFeeCalculator) que possui um @Autowiredcampo ( rateService), mas o campo é nullquando tento usá-lo. Os logs mostram que o MileageFeeCalculatorbean e o MileageRateServicebean estão sendo criados, mas recebo um NullPointerExceptionsempre que tento chamar o mileageChargemétodo no meu bean de serviço. Por que o Spring não liga automaticamente 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 conectado automaticamente, MileageFeeCalculatormas não é:

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

Quando tento 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)
    ...
chrylis -autiouslyoptimistic-
fonte
3
Outro cenário pode ser quando o bean Fé chamado dentro do construtor de outro bean S. Nesse caso, passe o bean necessário Fcomo um parâmetro para o outro Sconstrutor de beans e anote o construtor de Swith @Autowire. Lembre-se de anotar a classe do primeiro bean Fcom @Component.
111115 aliopi
Eu codifiquei alguns exemplos muito semelhantes a este usando Gradle aqui: github.com/swimorsink/spring-aspectj-examples . Espero que alguém ache útil.
Ross117

Respostas:

649

O campo anotado @Autowiredé nullporque o Spring não sabe sobre a cópia MileageFeeCalculatorque você criou newe não sabia para conectá-la automaticamente.

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

O contêiner de 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ê liga new, a JVM instancia uma cópia do novo objeto e a entrega diretamente para você - ela nunca passa pelo processo de configuração. Existem três maneiras de configurar seus beans.

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

Injete seus grãos

A opção mais preferível é deixar o Spring conectar automaticamente todos os seus grãos; isso requer a menor quantidade de código e é o mais sustentável. Para fazer a fiação automática funcionar como você queria, também faça a fiação automática da MileageFeeCalculatorseguinte maneira:

@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 do seu objeto de serviço para pedidos diferentes, ainda poderá usar a injeção usando os escopos do bean Spring .

Etiqueta que funciona injetando o @MileageFeeCalculatorobjeto de serviço:working-inject-bean

Use @Configurable

Se você realmente precisa que os objetos criados newsejam conectados automaticamente, é possível usar a @Configurableanotação Spring junto com o tecelagem em tempo de compilação AspectJ para injetar seus objetos. Essa abordagem insere código no construtor do seu objeto que alerta o Spring que ele 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 ( @EnableSpringConfiguredcom a sintaxe JavaConfig). Essa abordagem é usada pelo sistema Roo Active Record para permitir que newinstâ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 @Configurableo objeto de serviço:working-configurable

Pesquisa manual de bean: não recomendado

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

Para fazer isso, você precisa de uma classe à qual o Spring possa dar uma referência ao ApplicationContextobjeto:

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

Em seguida, seu código legado pode chamar getContext()e recuperar os beans necessários:

@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 pesquisando manualmente o objeto de serviço no contexto do Spring: working-manual-lookup

chrylis -autiouslyoptimistic-
fonte
1
A outra coisa a observar é criar objetos para beans em um @Configurationbean, onde o método para criar uma instância de uma classe de bean específica é anotado @Bean.
Donal Fellows
@DonalFellows Não tenho muita certeza do que você está falando ("fazer" é ambíguo). Você está falando de um problema com várias chamadas para @Beanmétodos ao usar o Spring Proxy AOP?
chrylis -cautiouslyoptimistic-
1
Olá, Estou com um problema semelhante. No entanto, quando uso sua primeira sugestão, meu aplicativo pensa que "calc" é nulo ao chamar o método "mileageFee". É como se nunca inicializasse o @Autowired MileageFeeCalculator calc. Alguma ideia?
Theo
Eu acho que você deve adicionar uma entrada na parte superior da sua resposta que explique que a recuperação do primeiro bean, a raiz da qual você faz tudo, deve ser feita através do ApplicationContext. Alguns usuários (para os quais fechei como duplicados) não entendem isso.
Sotirios Delimanolis
@SotiriosDelimanolis Por favor, explique a questão; Não tenho certeza exatamente de que ponto você está falando.
chrylis -cautiouslyoptimistic-
59

Se você não estiver codificando um aplicativo Web, verifique se a classe na qual o @Autowiring é feito é um bean de primavera. Normalmente, o contêiner de mola não estará ciente da classe que poderíamos considerar um feijão de primavera. Temos que contar ao contêiner Spring sobre nossas aulas de primavera.

Isso pode ser conseguido configurando no appln-contxt ou a melhor maneira é anotar a classe como @Component e não crie a classe anotada usando o novo operador. Certifique-se de obtê-lo no contexto Appln como abaixo.

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

    }

}
Shirish Coolkarni
fonte
oi, eu passei por sua solução, está correto. E aqui eu gostaria de saber "Por que não criamos uma instância de classe anotada usando um novo operador, posso saber o motivo por trás disso."
Ashish
3
se você criar o objeto usando new, estará lidando com o ciclo de vida do bean que contradiz o conceito de COI. Precisamos perguntar o recipiente para fazê-lo, o que o faz de uma maneira melhor
Shirish Coolkarni
41

Na verdade, você deve usar objetos gerenciados pela JVM ou objetos gerenciados por Spring para chamar métodos. do código acima em sua classe de controlador, você está criando um novo objeto para chamar sua classe de serviço que possui um objeto com conexão automática.

MileageFeeCalculator calc = new MileageFeeCalculator();

para que não funcione dessa maneira.

A solução torna esse MileageFeeCalculator como um objeto com conexão automática no próprio controlador.

Mude sua classe de controlador como abaixo.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        return calc.mileageCharge(miles);
    }
}
Ravi Durairaj
fonte
4
Essa é a resposta. Como você está instanciando um novo MilageFeeCalculator por conta própria, o Spring não está envolvido na instanciação; portanto, o Spring spring não tem conhecimento de que o objeto existe. Portanto, ele não pode fazer nada, como injetar dependências.
Robert Greathouse
26

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

A causa raiz é que, em vez de usar o bean criado automaticamente mantido pelo contêiner Spring IoC (cujo @Autowiredcampo foi indeedinjetado corretamente), sou newingminha própria instância desse tipo de bean e o uso. É claro que esse @Autowiredcampo é nulo porque o Spring não tem chance de injetá-lo.

smwikipedia
fonte
22

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

MileageFeeCalculator calc = new MileageFeeCalculator();

Com anotação @Service, @Component, @Configurationfeijão são criados no
contexto de aplicação de Primavera quando o servidor é iniciado. Mas quando criamos objetos usando o novo operador, o objeto não é registrado no contexto do aplicativo que já foi criado. Por exemplo, classe Employee.java que 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());
        }
    }
}

}
Deepak
fonte
12

Eu sou novo no Spring, mas descobri esta solução funcional. Por favor, diga-me se é uma maneira obsoleta.

Eu faço o Spring injetar applicationContextneste bean:

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

Você pode colocar esse código também na classe principal do aplicativo, se desejar.

Outras classes podem usá-lo assim:

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

Dessa maneira, qualquer bean pode ser obtido por qualquer objeto no aplicativo (também intantiado com new) e de maneira estática .

azulado
fonte
1
Esse padrão é necessário para tornar os Spring beans acessíveis ao código legado, mas deve ser evitado no novo código.
chrylis -cautiouslyoptimistic-
2
Você não é novo no Spring. Você é um profissional. :)
sapy 23/01
você me salvou ...
Govind Singh 16/04
No meu caso, eu exigi isso porque havia poucas classes de terceiros. Spring (IOC) não tinha o controle sobre eles. Essas classes nunca foram chamadas no meu aplicativo de inicialização da primavera. Eu segui essa abordagem e funcionou para mim.
Joginder Malik
12

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

Em @Injectvez disso, usamos o @Autowiredpadrão javaee suportado pelo Spring. Todos os lugares funcionavam bem e os grãos eram injetados corretamente, em vez de um só lugar. A injeção de feijão parece a mesma

@Inject
Calculator myCalculator

Por fim, descobrimos que o erro foi que nós (na verdade, o recurso de conclusão automática do Eclipse) importamos em com.opensymphony.xwork2.Injectvez dejavax.inject.Inject !

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

Alireza Fattahi
fonte
5

Acho que você deixou de instruir a primavera a verificar as aulas com anotação.

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

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

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

As classes marcadas com anotação precisam ser identificadas na primavera antes da injeção, @ComponentScaninstrua a primavera a procurar as classes marcadas com anotação. Quando o Spring o encontra @Autowired, procura pelo bean relacionado e injeta a instância necessária.

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

msucil
fonte
correu para isso quando eu esqueci de adicionar <context:component-scan base-package="com.mypackage"/>ao meu beans.xmlarquivo
Ralph Callaway
5

Se isso estiver acontecendo em uma classe de teste, verifique se você não esqueceu de anotar a classe.

Por exemplo, no Spring Boot :

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

Passa algum tempo ...

Spring Boot continua a evoluir . Não é mais necessário usar @RunWith se você usar a versão correta do JUnit .

Para @SpringBootTesta obra ficar sozinho, você precisa uso @Testde JUnit5 vez de JUnit4 .

//import org.junit.Test; // JUnit4
import org.junit.jupiter.api.Test; // JUnit5

@SpringBootTest
public class MyTests {
    ....

Se você errar nessa configuração, seus testes serão compilados, mas @Autowired e @Valuecampos (por exemplo) será null. Como o Spring Boot opera por mágica, você pode ter poucos caminhos para depurar diretamente essa falha.

nobar
fonte
Nota: @Valueserá nulo quando usado com staticcampos.
nobar 9/04
O Spring fornece várias maneiras de falhar (sem a ajuda do compilador). Quando as coisas dão errado, sua melhor aposta é voltar à estaca zero - usando apenas a combinação de anotações que você sabe que trabalharão juntas.
nobar
4

Outra solução seria fazer uma chamada: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Para o construtor MileageFeeCalculator, como este:

@Service
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService; // <--- will be autowired when constructor is called

    public MileageFeeCalculator() {
        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
    }

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile()); 
    }
}
Ondrej Bozek
fonte
Isso usa publicação não segura.
chrylis -cautiouslyoptimistic-
3

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

RESPOSTA ORIGINAL:

Não sei se isso ajuda alguém, mas fiquei com o mesmo problema, mesmo fazendo as coisas aparentemente corretas. No meu método Main, 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 token.xmlarquivo eu tive uma linha

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

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

E depois disso, o NPE começou a entrar. Em um, pep-config.xmleu tinha apenas 2 feijões:

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

e a classe SomeAbac possui uma propriedade declarada como

@Autowired private Settings settings;

por algum motivo desconhecido, settings é nulo em init (), quando o <context:component-scan/>elemento não está presente, mas quando está presente e possui alguns bs como basePackage, tudo funciona bem. Essa linha agora fica assim:

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

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

62mkv
fonte
5
Essa resposta é a explicação. <context:component-scan/>implica implicitamente <context:annotation-config/>necessário para o @Autowiredtrabalho.
ForNeVeR
3

Esse é o culpado de fornecer NullPointerException. MileageFeeCalculator calc = new MileageFeeCalculator();Estamos usando o Spring - não é necessário criar o objeto manualmente. A criação do objeto será tratada pelo contêiner IoC.

Atul Jain
fonte
2

Você também pode corrigir esse problema usando a anotação @Service na classe de serviço e passando o bean classA necessário como um parâmetro para o construtor classB de outros beans e anotar o construtor da classB com @Autowired. Exemplo de trecho aqui:

@Service
public class ClassB {

    private ClassA classA;

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

    public void useClassAObjectHere(){
        classA.callMethodOnObjectA();
    }
}
Abhishek
fonte
isso funcionou para mim, mas você pode explicar como isso está resolvendo o problema?
CruelEngine #
1
@CruelEngine, veja isso é injeção de construtor (onde você define explicitamente um objeto) em vez de apenas usar injeção de campo (isso é feito principalmente pela configuração de mola). Portanto, se você estiver criando um objeto da ClassB usando o operador "new", existe algum outro escopo que não seria visível ou configurado automaticamente para a ClassA. Portanto, ao chamar classB.useClassAObjectHere () lançaria o NPE como o objeto classA não seria automaticamente conectado se você declarar o campo Injection. Leia chrylis está tentando explicar o mesmo. E é por isso que a injeção de construtor é recomendada sobre a injeção de campo. Faz sentido agora ?
Abhishek
1

O que não foi mencionado aqui está descrito neste artigo no parágrafo "Ordem de execução".

Depois de "aprender" que eu tinha que anotar uma classe com @Component ou os derivados @Service ou @Repository (acho que há mais), para conectar automaticamente outros componentes dentro deles, percebi que esses outros componentes ainda eram nulos dentro do construtor do componente pai.

O uso do @PostConstruct resolve isso:

@SpringBootApplication
public class Application {
    @Autowired MyComponent comp;
}

e:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

    public MyComponent() {
        // dao is null here
    }

    @PostConstruct
    public void init() {
        // dao is initialized here
    }
}
JackLeEmmerdeur
fonte
1

Isso é válido apenas no caso de teste de unidade.

Minha classe de serviço tinha uma anotação de serviço e era @autowiredoutra classe de componente. Quando testei, a classe de componente estava ficando nula. Porque para a classe de serviço eu estava criando o objeto usandonew

Se você estiver escrevendo teste de unidade, verifique se não está criando objetos usando new object(). Use injectMock.

Isso corrigiu meu problema. Aqui está um link útil

Rishabh Agarwal
fonte
0

Observe também que, por qualquer motivo, você fizer um método em um @Serviceas final, os beans com conexão automática que você acessará a partir dele sempre serão null.

yglodt
fonte
0

Em palavras simples, existem principalmente duas razões para um @Autowiredcampo sernull

  • SUA CLASSE NÃO É UM FEIJÃO DE MOLA.

  • O CAMPO NÃO É UM FEIJÃO.

samuelj90
fonte
0

Não totalmente relacionado à questão, mas se a injeção de campo for nula, a injeção baseada no construtor continuará funcionando bem.

    private OrderingClient orderingClient;
    private Sales2Client sales2Client;
    private Settings2Client settings2Client;

    @Autowired
    public BrinkWebTool(OrderingClient orderingClient, Sales2Client sales2Client, Settings2Client settings2Client) {
        this.orderingClient = orderingClient;
        this.sales2Client = sales2Client;
        this.settings2Client = settings2Client;
    }
Chaklader Asfak Arefe
fonte