เหตุใดฟิลด์ @Autowired ของฉันจึงเป็นโมฆะ


609

หมายเหตุ: นี่มีวัตถุประสงค์เพื่อเป็นคำตอบที่ยอมรับได้สำหรับปัญหาทั่วไป

ฉันมี@ServiceคลาสSpring ( MileageFeeCalculator) ที่มี@Autowiredฟิลด์ ( rateService) แต่ฟิลด์นั้นคือnullเมื่อฉันพยายามใช้ บันทึกแสดงให้เห็นว่าทั้งMileageFeeCalculatorถั่วและMileageRateServiceถั่วกำลังถูกสร้างขึ้น แต่ฉันได้รับNullPointerExceptionเมื่อใดก็ตามที่ฉันพยายามที่จะเรียกmileageChargeวิธีการในบริการถั่วของฉัน เหตุใดสปริงจึงไม่ยอมฟิลด์

ชั้นควบคุม:

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

Service bean ที่ควรเป็นอัตโนมัติ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)
    ...

3
สถานการณ์อีกได้เมื่อถั่วที่เรียกว่าภายในสร้างของถั่วอื่นF Sในกรณีนี้ผ่านถั่วที่ต้องการFเป็นพารามิเตอร์ถั่วอื่น ๆSคอนสตรัคคอนสตรัคและอธิบายของกับS @Autowireอย่าลืมใส่คำอธิบายประกอบการเรียนของถั่วครั้งแรกด้วยF @Component
aliopi

ฉันรหัสขึ้นไม่กี่ตัวอย่างมากคล้ายกับหน้านี้ใช้ Gradle ที่นี่: github.com/swimorsink/spring-aspectj-examples หวังว่าบางคนจะพบว่ามีประโยชน์
Ross117

คำตอบ:


649

คำอธิบายประกอบฟิลด์@Autowiredเป็นnullเพราะฤดูใบไม้ผลิไม่ทราบเกี่ยวกับสำเนาMileageFeeCalculatorที่คุณสร้างขึ้นด้วยnewและไม่ทราบว่าจะได้รับมันอัตโนมัติ

คอนเทนเนอร์ Spring Inversion of Control (IoC)มีสามองค์ประกอบหลักตรรกะ: รีจิสตรี (เรียกว่าApplicationContext) ของส่วนประกอบ (beans) ที่พร้อมใช้งานโดยแอปพลิเคชันระบบกำหนดค่าที่แทรกการพึ่งพาของวัตถุลงไป การพึ่งพากับ beans ในบริบทและตัวแก้ปัญหาการพึ่งพาที่สามารถดูการกำหนดค่าของถั่วที่แตกต่างกันจำนวนมากและกำหนดวิธีการสร้างอินสแตนซ์และกำหนดค่าในลำดับที่จำเป็น

คอนเทนเนอร์ IoC ไม่ใช่เวทมนต์และมันไม่มีทางรู้เกี่ยวกับวัตถุ Java เว้นแต่คุณจะแจ้งให้พวกเขาทราบ เมื่อคุณโทรnewJVM จะเร่งการคัดลอกวัตถุใหม่และส่งตรงถึงคุณ - มันจะไม่ผ่านกระบวนการกำหนดค่า มีสามวิธีที่คุณสามารถกำหนดค่า bean ของคุณได้

ผมได้โพสต์ทั้งหมดของรหัสนี้ใช้ Spring Boot ที่จะเปิดตัวในโครงการ GitHub นี้ ; คุณสามารถดูโครงการที่ดำเนินการเต็มรูปแบบสำหรับแต่ละวิธีเพื่อดูทุกสิ่งที่คุณต้องการเพื่อให้มันทำงาน ติดแท็กด้วยNullPointerException:nonworking

ฉีดถั่วของคุณ

ตัวเลือกที่ดีที่สุดคือการปล่อยให้สปริงทำการดึงถั่วออกทั้งหมด สิ่งนี้ต้องการรหัสน้อยที่สุดและสามารถบำรุงรักษาได้มากที่สุด ในการทำให้ 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

ใช้ @Configurable

หากคุณต้องการให้วัตถุที่สร้างขึ้นnewเพื่อเป็น autowired จริงๆคุณสามารถใช้@ConfigurableคำอธิบายประกอบSpring พร้อมกับ 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

การค้นหา bean ด้วยตนเอง: ไม่แนะนำ

วิธีนี้เหมาะสำหรับการเชื่อมต่อกับรหัสดั้งเดิมในสถานการณ์พิเศษเท่านั้น เกือบจะดีกว่าเสมอในการสร้างคลาสอะแด็ปเตอร์แบบซิงเกิลที่ Spring สามารถ autowire และรหัสดั้งเดิมสามารถเรียกใช้ได้

ในการทำเช่นนี้คุณต้องมีคลาสที่ 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);
    }
}

แท็กที่ทำงานโดยการค้นหาวัตถุบริการด้วยตนเองในบริบทสปริง: working-manual-lookup


1
สิ่งอื่น ๆ ที่จะมองไปที่จะทำให้วัตถุถั่วในถั่วที่วิธีการที่จะทำให้อินสแตนซ์ของชั้นถั่วโดยเฉพาะอย่างยิ่งมีการกำกับด้วย@Configuration @Bean
Donal Fellows

@ DonalFellows ฉันไม่แน่ใจว่าสิ่งที่คุณกำลังพูดถึง (การทำ "ไม่ชัดเจน) คุณกำลังพูดถึงปัญหาเกี่ยวกับ@Beanวิธีการโทรหลายวิธีเมื่อใช้ Spring Proxy AOP หรือไม่?
chrylis -cautiouslyoptimistic-

1
สวัสดีฉันพบปัญหาที่คล้ายกัน แต่เมื่อฉันใช้คำแนะนำแรกของคุณแอปพลิเคชันของฉันคิดว่า "คำนวณ" เป็นโมฆะเมื่อเรียกวิธีการ "mileageFee" @Autowired MileageFeeCalculator calcมันเหมือนกับว่ามันไม่เคยทำการเริ่มต้น ความคิดใด ๆ
Theo

ApplicationContextฉันคิดว่าคุณควรจะเพิ่มรายการที่ด้านบนของคำตอบของคุณที่อธิบายว่าการเรียกถั่วแรกรากจากการที่คุณทำทุกอย่างที่ควรจะทำผ่านการ ผู้ใช้บางคน (ซึ่งฉันปิดเหมือนกัน) ไม่เข้าใจสิ่งนี้
Sotirios Delimanolis

@SotiriosDelimanolis โปรดอธิบายปัญหา; ฉันไม่แน่ใจว่าคุณกำลังทำอะไรอยู่
chrylis -cautiouslyoptimistic-

59

หากคุณไม่ได้เข้ารหัสโปรแกรมประยุกต์บนเว็บตรวจสอบให้แน่ใจว่าคลาสของคุณที่ @Autowiring เสร็จสิ้นแล้วเป็นสปริงบีน โดยปกติแล้วภาชนะสปริงจะไม่รับรู้ถึงระดับที่เราอาจคิดว่าเป็นสปริงถั่ว เราต้องบอก Spring container เกี่ยวกับคลาส 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();

    }

}

สวัสดีฉันผ่านวิธีการแก้ปัญหาของคุณถูกต้องแล้ว และที่นี่ฉันอยากจะรู้ว่า "ทำไมเราไม่สร้างอินสแตนซ์ของคลาสที่มีคำอธิบายประกอบโดยใช้โอเปอเรเตอร์ใหม่ฉันขอทราบเหตุผลเบื้องหลังได้เลย
Ashish

3
ถ้าคุณสร้างวัตถุโดยใช้ใหม่คุณจะจัดการวงจรชีวิตของถั่วซึ่งขัดแย้งกับแนวคิดของ IOC เราต้องขอให้ภาชนะทำมันซึ่งทำได้ในทางที่ดีขึ้น
Shirish Coolkarni

41

ที่จริงแล้วคุณควรใช้วัตถุที่มีการจัดการ JVM หรือวัตถุที่มีการจัดการสปริงเพื่อเรียกใช้เมธอด จากโค้ดด้านบนในคลาสคอนโทรลเลอร์คุณกำลังสร้างวัตถุใหม่เพื่อเรียกคลาสบริการของคุณซึ่งมีวัตถุแบบใช้สายอัตโนมัติ

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 จึงไม่มีความรู้เกี่ยวกับวัตถุที่มีอยู่ ดังนั้นจึงไม่สามารถทำอะไรกับมันได้เช่นการพึ่งพาการฉีด
Robert Greathouse

26

the life in the IoC worldผมเคยเจอปัญหาเดียวกันเมื่อผมไม่ได้ค่อนข้างใช้ในการ @Autowiredฟิลด์ของหนึ่งในถั่วของฉันเป็นโมฆะที่รันไทม์

สาเหตุหลักคือแทนที่จะใช้ bean ที่สร้างอัตโนมัติซึ่งดูแลโดยคอนเทนเนอร์ Spring IoC (ซึ่งมีการแทรก@Autowiredฟิลด์indeedอย่างเหมาะสม) ฉันnewingเป็นอินสแตนซ์ของฉันเองของประเภทถั่วนั้นและใช้งาน แน่นอนว่า@Autowiredสาขานี้ไม่มีค่าเพราะสปริงไม่มีโอกาสฉีด


22

ปัญหาของคุณคือใหม่ (การสร้างวัตถุในสไตล์จาวา)

MileageFeeCalculator calc = new MileageFeeCalculator();

ด้วยคำอธิบายประกอบ@Service, @Component, @Configurationถั่วจะถูกสร้างขึ้นใน
บริบทของแอพลิเคชันของฤดูใบไม้ผลิเมื่อเซิร์ฟเวอร์จะเริ่มต้น แต่เมื่อเราสร้างวัตถุโดยใช้ตัวดำเนินการใหม่วัตถุจะไม่ถูกลงทะเบียนในบริบทของแอปพลิเคชั่นซึ่งถูกสร้างขึ้นแล้ว ตัวอย่างเช่นคลาส 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

ฉันใหม่สำหรับสปริง แต่ฉันค้นพบวิธีแก้ปัญหาการทำงานนี้ โปรดบอกฉันว่ามันเป็นวิธีที่ไม่อาจปฏิเสธได้

ฉันทำสปริงฉีด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);

ด้วยวิธีนี้ถั่วใด ๆ ที่สามารถรับได้โดยวัตถุใด ๆ ในการสมัคร (ยัง intantiated ด้วยnew) และในทางที่คงที่


1
รูปแบบนี้มีความจำเป็นเพื่อให้ Spring beans สามารถเข้าถึงรหัสดั้งเดิมได้ แต่ควรหลีกเลี่ยงในรหัสใหม่
chrylis -cautiouslyoptimistic-

2
คุณไม่ใช่คนใหม่สำหรับฤดูใบไม้ผลิ คุณเป็นมืออาชีพ :)
sapy

คุณช่วยฉัน ...
Govind Singh

ในกรณีของฉันฉันต้องการสิ่งนี้เพราะมีชั้นเรียนของบุคคลที่สามไม่กี่ Spring (IOC) ไม่มีการควบคุม คลาสเหล่านี้ไม่เคยถูกเรียกจากแอพ boot boot ของฉัน ฉันทำตามวิธีนี้แล้วมันก็ใช้ได้ผลสำหรับฉัน
Joginder Malik

12

ดูเหมือนจะเป็นกรณีที่หายาก แต่นี่คือสิ่งที่เกิดขึ้นกับฉัน:

เราใช้@Injectแทน@Autowiredมาตรฐาน javaee ที่สปริงสนับสนุน ทุกสถานที่ทำงานได้ดีและถั่วฉีดถูกต้องแทนที่จะเป็นที่เดียว การฉีดถั่วดูเหมือนกัน

@Inject
Calculator myCalculator

ในที่สุดเราพบว่าข้อผิดพลาดคือเรา (ที่จริงแล้วคุณสมบัติ Eclipse auto complete) นำเข้าcom.opensymphony.xwork2.Injectแทนjavax.inject.Inject !

ดังนั้นเพื่อสรุปให้แน่ใจว่าคำอธิบายประกอบ ( @Autowired, @Inject, @Service, ... ) มีแพคเกจที่ถูกต้อง!


5

ฉันคิดว่าคุณพลาดที่จะสั่งการสปริงให้สแกนคลาสพร้อมคำอธิบายประกอบ

คุณสามารถใช้@ComponentScan("packageToScan")กับคลาสการกำหนดค่าของแอปพลิเคชันสปริงของคุณเพื่อสั่งให้สปริงสแกน

@Service, @Component คำอธิบายประกอบอื่น ๆ เพิ่มคำอธิบายเมตา

Spring จะแทรกอินสแตนซ์ของคลาสเหล่านั้นเท่านั้นซึ่งถูกสร้างเป็น bean หรือทำเครื่องหมายด้วยคำอธิบายประกอบ

คลาสที่ทำเครื่องหมายด้วยคำอธิบายประกอบจำเป็นต้องระบุโดยสปริงก่อนฉีดให้@ComponentScanสั่งให้มองหาคลาสที่ทำเครื่องหมายด้วยคำอธิบายประกอบ เมื่อ Spring พบ@Autowiredว่าค้นหา bean ที่เกี่ยวข้องและฉีดอินสแตนซ์ที่ต้องการ

การเพิ่มคำอธิบายประกอบเท่านั้นไม่สามารถแก้ไขหรืออำนวยความสะดวกในการฉีดพึ่งพาสปริงต้องทราบว่าจะมองหาที่ใด


วิ่งเข้าไปในนี้เมื่อฉันลืมที่จะเพิ่มลง<context:component-scan base-package="com.mypackage"/>ในbeans.xmlไฟล์ของฉัน
Ralph Callaway

5

หากสิ่งนี้เกิดขึ้นในคลาสทดสอบตรวจสอบให้แน่ใจว่าคุณไม่ลืมที่จะใส่คำอธิบายประกอบในชั้นเรียน

ตัวอย่างเช่นในSpring Boot :

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

เวลาผ่านไป ...

ฤดูใบไม้ผลิ 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 ดำเนินการด้วยเวทมนต์คุณอาจมีช่องทางเล็กน้อยสำหรับการดีบักความล้มเหลวนี้โดยตรง


ดูเพิ่มเติมที่: stackoverflow.com/questions/4130486/…
nobar

หมายเหตุ: @Valueจะเป็นโมฆะเมื่อใช้กับstaticฟิลด์
nobar

ฤดูใบไม้ผลิมีหลายวิธีที่จะล้มเหลว (โดยไม่มีความช่วยเหลือจากคอมไพเลอร์) เมื่อสิ่งต่าง ๆ ผิดปกติทางออกที่ดีที่สุดของคุณคือกลับไปที่สี่เหลี่ยมจัตุรัสโดยใช้การรวมคำอธิบายประกอบที่คุณรู้ว่าจะทำงานร่วมกันเท่านั้น
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()); 
    }
}

สิ่งนี้ใช้สิ่งพิมพ์ที่ไม่ปลอดภัย
chrylis -cautiouslyoptimistic-

3

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ฉันได้แค่ 2 เม็ด:

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

และคลาส SomeAbac มีคุณสมบัติประกาศเป็น

@Autowired private Settings settings;

ด้วยเหตุผลที่ไม่ทราบสาเหตุการตั้งค่าเป็นโมฆะใน init () เมื่อ<context:component-scan/>องค์ประกอบไม่ปรากฏเลย แต่เมื่อมีและมี bs เป็น basePackage ทุกอย่างทำงานได้ดี ตอนนี้บรรทัดนี้มีลักษณะดังนี้:

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

และมันใช้งานได้ อาจเป็นคนที่สามารถให้คำอธิบายได้ แต่สำหรับฉันแล้วตอนนี้ก็เพียงพอแล้ว)


5
คำตอบนั้นคือคำอธิบาย <context:component-scan/>โดยนัยทำให้<context:annotation-config/>จำเป็นสำหรับการ@Autowiredทำงาน
ForNeVeR

3

นี่คือสาเหตุของการให้ NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();เราใช้ Spring - ไม่จำเป็นต้องสร้างวัตถุด้วยตนเอง การสร้างวัตถุจะได้รับการดูแลโดยคอนเทนเนอร์ IoC


2

คุณยังสามารถแก้ไขปัญหานี้ได้โดยใช้คำอธิบายประกอบ @Service ในคลาสบริการและส่ง bean classA ที่ต้องการเป็นพารามิเตอร์ไปยัง beans classB constructor อื่น ๆ และใส่หมายเหตุประกอบ Constructor ของ classB ด้วย @Autowired ตัวอย่างโค้ดที่นี่:

@Service
public class ClassB {

    private ClassA classA;

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

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

สิ่งนี้ใช้ได้ผลกับฉันแล้วคุณช่วยอธิบายรายละเอียดเกี่ยวกับวิธีการแก้ไขปัญหาได้ไหม?
CruelEngine

1
@ CruelEngine ดูนี่คือการฉีดคอนสตรัคเตอร์ (ที่คุณตั้งค่าวัตถุอย่างชัดเจน) แทนที่จะใช้เพียงแค่การฉีดภาคสนาม ดังนั้นหากคุณกำลังสร้างวัตถุของ ClassB โดยใช้ตัวดำเนินการ "ใหม่" เป็นขอบเขตอื่น ๆ ดังนั้นจะไม่สามารถมองเห็นหรือตั้งค่าอัตโนมัติสำหรับ ClassA ดังนั้นในขณะที่เรียก classB.useClassAObjectHere () จะโยน NPE เป็นวัตถุ classA ไม่ได้รับการตอบรับอัตโนมัติถ้าคุณเพิ่งประกาศการฉีดภาคสนาม อ่าน chrylis พยายามอธิบายเหมือนกัน และนี่คือสาเหตุที่การฉีดคอนสตรัคเตอร์แนะนำให้ใช้กับการฉีดภาคสนาม มันสมเหตุสมผลแล้วหรือยัง?
Abhishek

1

สิ่งที่ยังไม่ได้รับการกล่าวถึงที่นี่ได้อธิบายไว้ในนี้บทความในวรรค "สั่งซื้อของการดำเนินการ"

หลังจาก "การเรียนรู้" ที่ฉันต้องใส่คำอธิบายประกอบคลาสด้วย @Component หรืออนุพันธ์ @Service หรือ @Repository (ฉันเดาว่ามีมากขึ้น) เพื่อที่จะได้รับส่วนประกอบอื่น ๆ ภายในพวกเขามันทำให้ฉันเห็นว่าองค์ประกอบอื่น ๆ เหล่านี้ยังคงเป็นโมฆะ ขององค์ประกอบหลัก

ใช้ @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คลาสคอมโพเนนต์อื่น เมื่อฉันทดสอบคลาสส่วนประกอบกำลังจะเป็นโมฆะ เพราะชั้นบริการฉันสร้างวัตถุโดยใช้new

new object()ถ้าคุณเขียนหน่วยทดสอบให้แน่ใจว่าคุณไม่ได้สร้างวัตถุโดยใช้ ใช้แทน injectMock

นี่เป็นการแก้ไขปัญหาของฉัน นี่คือลิงค์ที่มีประโยชน์


0

นอกจากนี้ยังทราบว่าถ้าด้วยเหตุผลใดก็ตามที่คุณทำวิธีการใน@Serviceขณะที่finalถั่ว autowired nullที่คุณจะเข้าถึงได้จากมันจะเป็น


0

ในคำง่าย ๆ มีสองเหตุผลหลักสำหรับ@Autowiredฟิลด์ที่จะเป็นnull

  • คลาสของคุณไม่ใช่ถั่วฤดูใบไม้ผลิ

  • สนามไม่ใช่ถั่ว


0

ไม่เกี่ยวข้องกับคำถามทั้งหมด แต่ถ้าการฉีดภาคสนามเป็นโมฆะการฉีดตามคอนสตรัคเตอร์จะยังทำงานได้ดี

    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.