it-swarm.asia

لماذا لا يكون حقل SpringAutowired باطلاً؟

ملاحظة: هذا يهدف إلى أن يكون إجابة قانونية لمشكلة شائعة.

لدي فصل ربيع @Service (MileageFeeCalculator) يحتوي على حقل @Autowired (rateService) ، لكن الحقل هو null عندما أحاول استخدامه. تُظهر السجلات أنه يتم إنشاء كل من MileageFeeCalculator bean و MileageRateService bean ، لكن أحصل على NullPointerException عندما أحاول الاتصال بأسلوب mileageCharge في وحدة الخدمة الخاصة بي. لماذا لا الربيع autowiring المجال؟

فئة تحكم:

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

فاصوليا الخدمة التي يجب أن تكون autowired في 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)
    ...
512
chrylis

الحقل المشروح @Autowired هو null لأن Spring لا يعرف نسخة MileageFeeCalculator التي قمت بإنشائها باستخدام new ولم يكن يعرف أنه يشغلها تلقائيًا.

تحتوي حاوية Spring Inversion of Control (IoC) على ثلاثة مكونات منطقية رئيسية: سجل (يُسمى ApplicationContext) من المكونات (الفاصوليا) المتوفرة ليتم استخدامها من قبل التطبيق ، ونظام التوصيف الذي يقوم بحقن تبعيات الكائنات في لهم من خلال مطابقة التبعيات مع الفول في السياق ، ومحلل التبعية الذي يمكنه النظر في تكوين العديد من الفول المختلفة وتحديد كيفية إنشاء مثيل لها وتكوينها بالترتيب اللازم.

إن حاوية IoC ليست سحرية ، وليس لها أي طريقة لمعرفة كائنات Java إلا إذا أبلغتها بطريقة أو بأخرى. عندما تقوم بالاتصال بـ new ، تقوم JVM بإنشاء نسخة من الكائن الجديد وتسليمها لك مباشرة - لا تمر عملية التكوين أبدًا. هناك ثلاث طرق يمكنك من خلالها تكوين حبوبك.

لقد نشرت كل هذا الرمز ، باستخدام Spring Boot لإطلاق ، في مشروع GitHub هذا ؛ يمكنك إلقاء نظرة على مشروع تشغيل كامل لكل طريقة لمعرفة كل ما تحتاجه لإنجاحه. علامة مع NullPointerException: nonworking

حقن حبوبك

الخيار الأكثر تفضيلاً هو السماح لـ Spring autowire بجميع حبوبك ؛ هذا يتطلب أقل قدر من الكود وهو الأكثر قابلية للصيانة. لجعل autowiring تعمل كما تريد ، autowire أيضًا MileageFeeCalculator مثل هذا:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

إذا كنت بحاجة إلى إنشاء مثيل جديد لكائن الخدمة الخاص بك لطلبات مختلفة ، فلا يزال بإمكانك استخدام الحقن باستخدام نطاقات حبة الربيع .

العلامة التي تعمل عن طريق حقن كائن الخدمة @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 لفاصوليا.

للقيام بذلك ، تحتاج إلى فصل يمكن لـ 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

559
chrylis

إذا كنت لا تقوم بترميز تطبيق ويب ، فتأكد من أن فصلك الدراسي الذي تم فيهAutowiring هو فاصوليا زنبركية. عادة ، لن تكون حاوية الزنبرك على دراية بالصف الذي قد نفكر فيه كحبة زنبركية. علينا أن نقول حاوية الربيع عن فصول الربيع لدينا.

يمكن تحقيق ذلك من خلال تكوين appln-contxt أو الطريقة الأفضل هو إضافة تعليق توضيحي إلى الفصل كـComponent والرجاء عدم إنشاء الفصل المشروح باستخدام عامل تشغيل جديد. تأكد من الحصول عليها من سياق Appln كما هو موضح أدناه.

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

    }

}
50
Shirish Coolkarni

في الواقع ، يجب عليك استخدام كائنات JVM المدارة أو كائن مدار الربيع لاستدعاء الأساليب. من التعليمات البرمجية أعلاه في فئة وحدة التحكم الخاصة بك ، تقوم بإنشاء كائن جديد للاتصال بفئة الخدمة التي تحتوي على كائن سلكي تلقائي.

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);
    }
}
31
Ravi Durairaj

واجهت ذات مرة نفس المشكلة عندما لم أكن معتادًا على the life in the IoC world. حقل @Autowired الخاص بأحد حبوبي فارغ في وقت التشغيل.

السبب الرئيسي هو ، بدلاً من استخدام الحبة التي تم إنشاؤها تلقائيًا والتي تحتفظ بها حاويات Spring IoC (حقل @Autowired الخاص به indeed حقنه بشكل صحيح) ، فأنا newing مثلي الخاص بنوع الحبة هذا واستخدامه. بالطبع ، يعد حقل @Autowired الخاص بهذا الحقل فارغًا لأن Spring ليس لديه فرصة لحقنه.

23
smwikipedia

مشكلتك جديدة (إنشاء كائن في نمط Java)

MileageFeeCalculator calc = new MileageFeeCalculator();

مع التعليق التوضيحي ، يتم إنشاء الفاصوليا @Service ، @Component ، @Configuration في
سياق تطبيق Spring عند بدء تشغيل الخادم. ولكن عندما ننشئ كائنات باستخدام عامل تشغيل جديد ، لا يتم تسجيل الكائن في سياق التطبيق الذي تم إنشاؤه بالفعل. على سبيل المثال فئة 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());
        }
    }
}

}
19
Deepak

يبدو أنها حالة نادرة ولكن هنا ما حدث لي:

استخدمنا @Inject بدلاً من @Autowired وهو javaee القياسي الذي يدعمه Spring. كل الأماكن كانت تعمل بشكل جيد وحقن الفاصوليا بشكل صحيح ، بدلا من مكان واحد. حقن الفاصوليا يبدو هو نفسه

@Inject
Calculator myCalculator

أخيرًا ، وجدنا أن الخطأ هو أننا (في الحقيقة ، ميزة Eclipse auto Complete) ، استوردنا com.opensymphony.xwork2.Inject بدلاً من javax.inject.Inject!

لتلخيص ذلك ، تأكد من أن تعليقاتك التوضيحية (@Autowired ، @Inject ، @Service ، ...) تحتوي على حزم صحيحة!

9
Alireza Fattahi

أنا جديد على Spring ، لكنني اكتشفت هذا الحل العملي. من فضلك قل لي ما إذا كانت طريقة مستهجنة.

أنا أجعل ربيع حقن applicationContext في هذه الحبة:

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

وبهذه الطريقة يمكن الحصول على أي فاصوليا من قبل أي كائن في التطبيق (أيضًا مهتم بـ new) و بطريقة ثابتة .

7
bluish

حل آخر هو إجراء مكالمة: 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()); 
    }
}
4
Ondrej Bozek

أعتقد أنك قد فاتتك توجيه الربيع لمسح الطبقات بالتعليقات التوضيحية.

يمكنك استخدام @ComponentScan("packageToScan") في فئة التكوين الخاصة بتطبيق spring الخاص بك لتوجيه الربيع للمسح الضوئي.

@Service, @Component الخ شروح إضافة وصف ميتا.

يعمل Spring فقط على حقن مثيلات تلك الفئات التي يتم إنشاؤها إما كحبة أو يتم تعليمها بعلامات توضيحية.

يجب تحديد الفصول التي تحمل علامة توضيحية بحلول فصل الربيع قبل الحقن ، @ComponentScan اطلب نظرة الربيع للفصول التي تحمل علامة توضيحية. عندما يجد Spring @Autowired ، يبحث عن الحبة ذات الصلة ، ويحقن المثيل المطلوب.

إضافة تعليق توضيحي فقط ، لا يصلح أو يسهل حقن التبعية ، يحتاج Spring إلى معرفة مكان البحث.

4
msucil

إذا كان هذا يحدث في فصل اختبار ، فتأكد من أنك لم تنسَ التعليق على الفصل.

على سبيل المثال ، في Spring Spring :

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

يمكنك أيضًا إصلاح هذه المشكلة باستخدام تعليق توضيحي علىService في فئة الخدمة وتمرير فئة الحبة المطلوبة كمعلمة إلى مُنشئ الفاصوليا classB الأخرى وتعليق مُنشئ classB باستخدامAutowired. نموذج مقتطف هنا:

@Service
public class ClassB {

    private ClassA classA;

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

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

UPDATE: كان الأشخاص الأذكياء حقًا في الإشارة إلى هذه الإجابة ، التي تشرح الغرابة الموضحة أدناه

الإجابة الأصلية:

لا أعرف ما إذا كان ذلك يساعد أي شخص ، لكنني كنت متورطًا في نفس المشكلة حتى أثناء القيام بالأمور على ما يبدو. في طريقتي الرئيسية ، لدي رمز مثل هذا:

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 كان لديّ فاصوليا اثنين فقط:

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

وفئة SomeAbac لها خاصية معلن عنها

@Autowired private Settings settings;

لسبب غير معروف ، تكون الإعدادات null في init () ، عندما لا يكون عنصر <context:component-scan/> موجودًا على الإطلاق ، ولكن عندما يكون موجودًا ويحتوي على بعض bs كقاعدة ، فإن كل شيء يعمل بشكل جيد. يبدو هذا السطر الآن كالتالي:

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

ويعمل. قد يكون شخص ما يستطيع تقديم تفسير ، لكن بالنسبة لي يكفي الآن

2
62mkv

لاحظ أيضًا أنه إذا ، لأي سبب من الأسباب ، قمت بإعداد طريقة في @Service كـ final ، فستصبح الحبوب المطبوخة تلقائيًا التي يمكنك الوصول إليها دائمًا null.

0
yglodt

هذا هو السبب في إعطاء NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator(); نحن نستخدم Spring - لا تحتاج إلى إنشاء كائن يدويًا. إنشاء كائن سوف تأخذ الرعاية من قبل حاوية IoC.

0
Atul Jain