Springの@Autowiredフィールドがnullになるのはなぜですか?


608

注:これは、一般的な問題に対する標準的な回答となることを目的としています。

フィールド@ServiceMileageFeeCalculator)を持つSpring クラス()があり@AutowiredますrateServiceが、フィールドはnull使用しようとしたときのものです。ログには、MileageFeeCalculatorBeanとMileageRateServiceBeanの両方が作成されていることが示されていますが、サービスBeanでメソッドNullPointerExceptionを呼び出そうとすると、常にを取得しmileageChargeます。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; // <--- 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)
    ...

3
別のシナリオはF、別のBeanのコンストラクター内でBean が呼び出された場合Sです。この場合、必要なBean Fをパラメーターとして他のBean Sコンストラクターに渡し、コンストラクターにSwith アノテーションを付け@Autowireます。最初のBeanのクラスの注釈を付けることを忘れないFでを@Component
aliopi 2015

ここでGradleを使用して、これに非常に似たいくつかの例をコード化しました:github.com/swimorsink/spring-aspectj-examples。うまくいけば、誰かがそれが役に立つと思うでしょう。
Ross117 2018年

回答:


649

注釈@Autowiredが付けられたフィールドはnull、SpringがMileageFeeCalculator、あなたが作成したもののコピーを認識しnewておらず、自動配線することを知らなかったためです。

Spring Inversion of Control(IoC)コンテナには、3つの主要な論理コンポーネントApplicationContextがあります。アプリケーションで使用できるコンポーネント(beans)のレジストリ(と呼ばれます)、コンテキスト内のBeanとの依存関係、および多くの異なるBeanの構成を調べ、必要な順序でそれらをインスタンス化および構成する方法を決定できる依存関係ソルバー。

IoCコンテナは魔法ではなく、何らかの形で通知しない限り、Javaオブジェクトについて知る方法はありません。を呼び出すnewと、JVMは新しいオブジェクトのコピーをインスタンス化して直接渡します。構成プロセスは行われません。Beanを構成するには、3つの方法があります。

このGitHubプロジェクトで、Spring Bootを使用して起動するすべてのコードを投稿しました。各アプローチの完全に実行中のプロジェクトを見て、それを機能させるために必要なすべてを確認できます。でタグ付けNullPointerExceptionnonworking

あなたの豆を注入する

最も好ましいオプションは、SpringにすべてのBeanを自動ワイヤリングさせることです。これは、必要なコードが最も少なく、最も保守しやすいコードです。自動配線を希望どおりに機能させるには、次の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自動配線する必要がある場合は、Spring @ConfigurableアノテーションとAspectJコンパイル時ウィービングを使用してオブジェクトを挿入できます。このアプローチでは、オブジェクトのコンストラクターにコードが挿入され、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


1
もう1つ注目すべき点は、@ConfigurationBeanでBeanのオブジェクトを作成することです。この場合、特定のBeanクラスのインスタンスを作成するメソッドには、アノテーションが付けられてい@Beanます。
ドナルフェロー

@DonalFellows私はあなたが何を話しているのか完全にはわかりません( "作る"は曖昧です)。@BeanSpring Proxy AOPを使用しているときのメソッドの複数呼び出しの問題について話しているのですか?
chrylis -cautiouslyoptimistic- 2013

1
こんにちは、私は同様の問題に遭遇していますが、最初の提案を使用すると、アプリケーションは「mileageFee」メソッドを呼び出すときに「calc」がnullと見なします。これは、を初期化しないかのようです@Autowired MileageFeeCalculator calc。何かご意見は?
Theo

最初のBean、つまりすべてを実行するルートの取得はを通じて行う必要があることを説明するエントリを回答の上部に追加する必要があると思いますApplicationContext。一部のユーザー(重複として閉じた)はこれを理解していません。
Sotirios Delimanolis 2014年

@SotiriosDelimanolis問題について説明してください。何を言っているのか正確にはわかりません。
クリリス

59

Webアプリケーションをコーディングしていない場合は、@ Autowiringが実行されるクラスがSpring Beanであることを確認してください。通常、Springコンテナは、Spring Beanと考えるクラスを認識しません。Springコンテナに、Springクラスについて通知する必要があります。

これは、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();

    }

}

こんにちは、私はあなたの解決策を通り抜けました、それは正しいです。ここで、「new演算子を使用して注釈付きクラスのインスタンスを作成しないのはなぜですか、その理由がわかりますか?
Ashish

3
uがnewを使用してオブジェクトを作成する場合、uはIOCの概念と矛盾するBeanのライフサイクルを処理します。コンテナーにそれを実行するように依頼する必要があります。これにより、より良い方法で実行されます
Shirish Coolkarni 2015

41

実際には、メソッドを呼び出すには、JVM管理オブジェクトまたはSpring管理オブジェクトのいずれかを使用する必要があります。コントローラークラスの上記のコードから、自動ワイヤードオブジェクトを持つサービスクラスを呼び出す新しいオブジェクトを作成しています。

MileageFeeCalculator calc = new MileageFeeCalculator();

そのようには機能しません。

ソリューションは、このMileageFeeCalculatorをコントローラー自体の自動ワイヤードオブジェクトとして作成します。

以下のようにControllerクラスを変更します。

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

4
これが答えです。自分で新しいMilageFeeCalculatorをインスタンス化しているため、Springはインスタンス化に関与していないため、Spring Springはオブジェクトが存在することを認識していません。したがって、依存関係を挿入するなど、何もできません。
ロバートグレートハウス'19

26

慣れていないときに同じ問題に出会ったことがありますthe life in the IoC world@AutowiredいずれかのBean のフィールドが実行時にnullになります。

根本的な原因は、Spring IoCコンテナー(@Autowiredフィールドがindeed適切に挿入される)によって維持される自動作成されたBeanを使用する代わりにnewing、そのBeanタイプの私自身のインスタンスであり、それを使用していることです。もちろん@Autowired、Springにはフィールドを挿入する機会がないため、このフィールドはnullです。


22

あなたの問題は新しいです(Javaスタイルでのオブジェクトの作成)

MileageFeeCalculator calc = new MileageFeeCalculator();

アノテーションを使用する@Service@Component@ConfigurationBeanは
サーバーの起動時にSpringアプリケーションコンテキストでます。しかし、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());
        }
    }
}

}

12

私は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);

このようにして、任意のBeanはアプリケーション内の任意のオブジェクト(と連動するnew)と静的な方法で取得できます


1
このパターンは、Spring Beanをレガシーコードからアクセスできるようにするために必要ですが、新しいコードでは回避する必要があります。
クリリス

2
春は初めてです。あなたはプロです。:)
sap

あなたは私を救った...
Govind Singh

私の場合、サードパーティのクラスが少なかったので、これが必要でした。Spring(IOC)はそれらを制御できませんでした。これらのクラスは、私のスプリングブートアプリから呼び出されたことはありません。私はこのアプローチに従って、それは私のために働いた。
Joginder Malik

12

まれなケースのようですが、これが私に起こったことです:

@Inject代わりに@Autowired、Springがサポートするjavaee標準を使用しました。1つの場所ではなく、すべての場所で問題なく動作し、豆が正しく注入されました。豆の注入は同じようです

@Inject
Calculator myCalculator

ようやくエラーが見つかりました。com.opensymphony.xwork2.Inject代わりに、私たち(実際にはEclipseのオートコンプリート機能)がインポートされました。javax.inject.Inject!の。

だから、要約すると、ことを確認してくださいあなたの注釈(@Autowired@Inject@Service、...)正しいパッケージを持っています!


5

Springにアノテーション付きのクラスをスキャンするように指示するのを逃したと思います。

使用できます @ComponentScan("packageToScan")アプリケーションの構成クラスでをして、Springにスキャンを指示。

@Service, @Component etcアノテーションはメタ記述を追加します。

Springは、Beanとして作成されたクラスまたはアノテーションでマークされたクラスのインスタンスのみを注入します。

アノテーションでマークされたクラスは、注入する前にスプリングで識別される必要があります@ComponentScan。アノテーションでマークされたクラスをスプリングルックに指示します。Springが@Autowiredそれを見つけると、関連するBeanを検索し、必要なインスタンスを注入します。

アノテーションを追加するだけで、依存性注入は修正または促進されません。Springはどこを探すべきかを知る必要があります。


ファイルに追加するの<context:component-scan base-package="com.mypackage"/>を忘れたときにこれに遭遇しましたbeans.xml
Ralph Callaway

5

これがテストクラスで発生している場合は、クラスに注釈を付けることを忘れていないことを確認してください。

たとえば、Spring Bootでは

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

しばらく時間が経ちます...

Spring Boot は進化を続けています@RunWith 正しいバージョンのJUnitを使用している場合は、使用する必要がなくなりました。

以下のために@SpringBootTest仕事に一人で立って、あなたが使用する必要がある@TestからJUnit5の代わりに、JUnit4

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

@SpringBootTest
public class MyTests {
    ....

この設定を間違って取得した場合、テストはコンパイルされますが@Autowired@Valueフィールド(たとえば)はになりますnull。Spring Bootは魔法で動作するため、この障害を直接デバッグする方法がほとんどない場合があります。



注:フィールドで@Value使用するとnullになりますstatic
nobar

Springは失敗する多くの方法を提供します(コンパイラーの助けなしで)。問題が発生した場合、最善の策は正方形に戻ることです-一緒に機能することがわかっている注釈の組み合わせのみを使用します。
nobar

4

別の解決策はSpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
、次のような呼び出しを置くことです: MileageFeeCalculatorコンストラクターに次のような

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

これは安全でない公開を使用しています。
クリリス

3

更新:本当に賢い人はこれをすぐに指摘しました答えは以下で説明される奇妙さを説明しています

元の回答:

それが誰かに役立つかどうかはわかりませんが、一見正しいことをやっていても同じ問題に悩まされました。私のMainメソッドには、次のようなコードがあります。

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

とにtoken.xmlファイル私はラインを持っていました

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

package.pathがもう存在しないことに気付いたので、私は行を削除しました。

その後、NPEは入って来始めて。pep-config.xml私はちょうど2豆を持っていました:

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

そして、SomeAbacクラスには、次のように宣言されたプロパティがあります。

@Autowired private Settings settings;

何らかの未知の理由により、要素がまったく存在しない場合、init()の設定はnull<context:component-scan/>ですが、要素が存在し、basePackageとしていくつかのbsがある場合、すべてが正常に機能します。この行は次のようになります。

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

そしてそれは動作します。誰かが説明を提供できるかもしれませんが、私にとっては今のところ十分です)


5
その答えが説明です。<context:component-scan/>が機能する<context:annotation-config/>ために必要なことを暗黙的に有効にします@Autowired
ForNeVeR 2017年

3

これがNullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();を与える原因です。Spring を使用しています。手動でオブジェクトを作成する必要はありません。オブジェクトの作成はIoCコンテナーによって処理されます。


2

また、サービスクラスの@Serviceアノテーションを使用してこの問題を修正し、必要なBean classAをパラメーターとして他のBeanのclassBコンストラクターに渡し、@ AutowiredでclassBのコンストラクターに注釈を付けることもできます。ここにサンプルスニペット:

@Service
public class ClassB {

    private ClassA classA;

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

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

これは私にとってうまくいきましたが、これが問題をどのように解決しているかについて詳しく説明していただけますか?
CruelEngine 2018年

1
@CruelEngine、フィールドインジェクションを使用するのではなく、これをコンストラクタインジェクション(オブジェクトを明示的に設定する場所)にしてください(これは主にSpring構成によって行われます)。したがって、「new」演算子を使用してClassBのオブジェクトを作成している場合、それは他のスコープであり、ClassAに対して表示または自動配線されません。したがって、classB.useClassAObjectHere()を呼び出すと、フィールドインジェクションを宣言しただけではclassAオブジェクトが自動配線されなかったため、NPEがスローされます。クリリスは同じことを説明しようとしています。そして、このため、フィールドインジェクションよりもコンストラクタインジェクションが推奨されます。今では意味がありますか?
Abhishek

1

ここで言及されていないことは、この記事の「実行の順序」の段落で説明されています。

クラスに@Componentまたは@Serviceまたは@Repository(他にもあると思います)で注釈を付ける必要があることを「学習」した後、それらの内部に他のコンポーネントを自動配線するには、これらの他のコンポーネントがコンストラクター内でまだnullであることに気付きました親コンポーネントの。

@PostConstructを使用すると、それが解決します。

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

そして:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

1

これは単体テストの場合にのみ有効です。

私のサービスクラスにはサービスの注釈があり、それは@autowired別のコンポーネントクラスでした。テストしたとき、コンポーネントクラスはnullになりました。サービスクラスのために私はオブジェクトを使用して作成していたのでnew

単体テストを作成している場合は、を使用してオブジェクトを作成していないことを確認してくださいnew object()。代わりにinjectMockを使用してください。

これで問題が解決しました。ここに便利なリンクがあります


0

また、何らかの理由で@Serviceas finalでメソッドを作成した場合、そこからアクセスするautowired Beanは常にになることに注意してくださいnull


0

簡単に言えば、@Autowiredフィールドが存在する主な理由は2つあります。null

  • あなたのクラスは春の豆ではありません。

  • フィールドは豆ではありません。


0

質問に完全に関連しているわけではありませんが、フィールドインジェクションがnullの場合でも、コンストラクタベースのインジェクションは正常に機能します。

    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;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.