1. ホーム
  2. java

[解決済み] Spring の @Autowired フィールドが NULL になっているのはなぜですか?

2022-03-14 20:15:34

質問

注:これはよくある問題に対する定型的な回答であることを意図しています。

私は、Springの @Service クラス ( MileageFeeCalculator を持つ)。 @Autowired フィールド( rateService ) が、そのフィールドは null を使おうとすると ログを見ると、どちらも MileageFeeCalculator ビーンと MileageRateService ビーンが作成されるのですが 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 が、そうなっていない。

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

解決方法は?

アノテーションされたフィールド @Autowirednull のコピーについてSpringが知らないからです。 MileageFeeCalculator で作成した new で、自動配線することを知りませんでした。

SpringのIoC(Inversion of Control)コンテナ は3つの主要な論理的なコンポーネントを持っています: レジストリ (これは ApplicationContext )のコンポーネント(Bean)をアプリケーションで使用できるようにするシステム、オブジェクトの依存関係をコンテキスト内のBeanと照合して注入するconfigurerシステム、多くの異なるBeanの構成を見て、必要な順序でインスタンス化し構成する方法を決定できる依存関係ソルバーがあります。

IoCコンテナは魔法ではないので、何らかの方法で知らせない限り、Javaオブジェクトについて知る術がない。あなたが new JVMは新しいオブジェクトのコピーをインスタンス化し、それをそのままあなたに渡しますが、それは設定プロセスを経ることはありません。ビーンを構成する方法は3つあります。

Spring Boot を使って起動するこのコードは、すべて このGitHubのプロジェクト 各アプローチの完全な実行プロジェクトを見れば、それを動作させるために必要なすべてを確認できます。 タグに 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

コンフィギュラブルを使用する

で作成されたオブジェクトがどうしても必要な場合は new を自動配線にするには は、Springの @Configurable アノテーションと AspectJ のコンパイル時のウィービング を使用してオブジェクトをインジェクトします。この方法では、オブジェクトのコンストラクタにコードを挿入して、オブジェクトが作成されることをSpringに通知し、Springが新しいインスタンスを設定できるようにします。これはビルドの際に少し設定する必要があります(例えば ajc ) とSpringの実行時設定ハンドラ ( @EnableSpringConfigured をJavaConfigの構文で)。この方法は、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

手動によるビーン検索:推奨しません

この方法は、特殊な状況下でレガシーコードとのインターフェイスにのみ適しています。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() を作成し、必要なビーンを取得します。

@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