Mengapa bidang Spring @ Aututired saya nol?


608

Catatan: Ini dimaksudkan sebagai jawaban kanonik untuk masalah umum.

Saya memiliki @Servicekelas Spring ( MileageFeeCalculator) yang memiliki @Autowiredbidang ( rateService), tetapi bidang tersebut adalah nullketika saya mencoba menggunakannya. Log menunjukkan bahwa baik MileageFeeCalculatorkacang dan MileageRateServicekacang sedang dibuat, tetapi saya mendapatkan NullPointerExceptionsetiap kali saya mencoba memanggil mileageChargemetode pada kacang layanan saya. Mengapa Spring tidak mengotomasi lapangan?

Kelas pengontrol:

@Controller
public class MileageFeeController {    
    @RequestMapping("/mileage/{miles}")
    @ResponseBody
    public float mileageFee(@PathVariable int miles) {
        MileageFeeCalculator calc = new MileageFeeCalculator();
        return calc.mileageCharge(miles);
    }
}

Kelas layanan:

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

Kacang layanan yang harus diotomatiskan MileageFeeCalculatortetapi tidak:

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

Ketika saya mencoba GET /mileage/3, saya mendapatkan pengecualian ini:

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
Skenario lain dapat terjadi ketika kacang Fdisebut di dalam konstruktor kacang lain S. Dalam hal ini, lewati kacang yang diperlukan Fsebagai parameter untuk Skonstruktor kacang lainnya dan beri keterangan pada konstruktor Sdengan @Autowire. Ingat untuk membubuhi keterangan kelas kacang pertama Fdengan @Component.
aliopi

Saya mengkodekan beberapa contoh yang sangat mirip dengan yang ini menggunakan Gradle di sini: github.com/swimorsink/spring-aspectj-examples . Semoga seseorang akan merasakan manfaatnya.
Ross117

Jawaban:


649

Bidang yang dianotasi @Autowiredadalah nullkarena Spring tidak tahu tentang salinan MileageFeeCalculatoryang Anda buat dengan newdan tidak tahu untuk autowire itu.

Kontainer Spring Inversion of Control (IoC) memiliki tiga komponen logis utama: registri (disebut Windows 7)ApplicationContext ) komponen (kacang) yang tersedia untuk digunakan oleh aplikasi, sistem konfigurator yang menyuntikkan dependensi objek ke dalamnya dengan mencocokkan dependensi dengan kacang dalam konteks, dan pemecah ketergantungan yang dapat melihat konfigurasi banyak kacang yang berbeda dan menentukan bagaimana membuat instantiate dan mengkonfigurasinya dalam urutan yang diperlukan.

Kontainer IoC bukanlah sihir, dan tidak memiliki cara untuk mengetahui tentang objek-objek Java kecuali jika Anda memberitahukannya. Ketika Anda menelepon new, JVM instantiate salinan objek baru dan menyerahkannya langsung kepada Anda - tidak pernah melewati proses konfigurasi. Ada tiga cara untuk mengonfigurasi kacang Anda.

Saya telah memposting semua kode ini, menggunakan Spring Boot untuk diluncurkan, di proyek GitHub ini ; Anda dapat melihat proyek yang berjalan penuh untuk setiap pendekatan untuk melihat semua yang Anda butuhkan untuk membuatnya bekerja. Tag dengan NullPointerException:nonworking

Suntikkan kacang Anda

Opsi yang paling disukai adalah membiarkan Spring mengotomatiskan semua kacang Anda; ini membutuhkan jumlah kode paling sedikit dan merupakan yang paling bisa dikelola. Untuk membuat autowiring berfungsi seperti yang Anda inginkan, autowire juga MileageFeeCalculatorseperti ini:

@Controller
public class MileageFeeController {

    @Autowired
    private MileageFeeCalculator calc;

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

Jika Anda perlu membuat instance baru objek layanan Anda untuk permintaan yang berbeda, Anda masih bisa menggunakan injeksi dengan menggunakan lingkup bean Spring .

Tag yang berfungsi dengan menyuntikkan @MileageFeeCalculatorobjek layanan:working-inject-bean

Gunakan @Configurable

Jika Anda benar-benar membutuhkan objek yang dibuat dengan newautowired, Anda dapat menggunakan @Configurableanotasi Spring bersama dengan tenun waktu kompilasi AspectJ untuk menyuntikkan objek Anda. Pendekatan ini menyisipkan kode ke konstruktor objek Anda yang memberitahu Spring bahwa itu dibuat sehingga Spring dapat mengkonfigurasi instance baru. Ini membutuhkan sedikit konfigurasi dalam build Anda (seperti kompilasi dengan ajc) dan menyalakan penangan konfigurasi runtime Spring ( @EnableSpringConfigureddengan sintaks JavaConfig). Pendekatan ini digunakan oleh sistem Rekaman Aktif Roo untuk memungkinkan newinstance dari entitas Anda untuk mendapatkan informasi persistensi yang diperlukan disuntikkan.

@Service
@Configurable
public class MileageFeeCalculator {

    @Autowired
    private MileageRateService rateService;

    public float mileageCharge(final int miles) {
        return (miles * rateService.ratePerMile());
    }
}

Tag yang berfungsi dengan menggunakan @Configurablepada objek layanan:working-configurable

Pencarian kacang manual: tidak disarankan

Pendekatan ini hanya cocok untuk berinteraksi dengan kode lawas dalam situasi khusus. Hampir selalu lebih disukai untuk membuat kelas adaptor tunggal yang dapat diautow oleh autovire dan kode lawas dapat dipanggil, tetapi dimungkinkan untuk secara langsung menanyakan konteks aplikasi Spring untuk sebuah kacang.

Untuk melakukan ini, Anda memerlukan kelas yang Spring dapat memberikan referensi ke ApplicationContextobjek:

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

Kemudian kode lawas Anda dapat memanggil getContext()dan mengambil kacang yang dibutuhkan:

@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 yang berfungsi secara manual mencari objek layanan dalam konteks Spring: working-manual-lookup


1
Hal lain untuk dilihat adalah membuat objek untuk kacang dalam @Configurationkacang, di mana metode untuk membuat turunan dari kelas kacang tertentu dijelaskan dengan @Bean.
Donal Fellows

@ DonalFellows Saya tidak sepenuhnya yakin apa yang Anda bicarakan ("membuat" adalah ambigu). Apakah Anda berbicara tentang masalah dengan beberapa panggilan ke @Beanmetode saat menggunakan Spring Proxy AOP?
chrylis -cautiouslyoptimistic-

1
Halo, saya mengalami masalah serupa, namun ketika saya menggunakan saran pertama Anda, aplikasi saya menganggap "calc" adalah nol saat memanggil metode "mileageFee". Seolah-olah itu tidak pernah menginisialisasi @Autowired MileageFeeCalculator calc. Adakah pikiran?
Theo

Saya pikir Anda harus menambahkan entri di bagian atas jawaban Anda yang menjelaskan bahwa mengambil kacang pertama, root dari mana Anda melakukan segalanya, harus dilakukan melalui ApplicationContext. Beberapa pengguna (yang saya tutup sebagai duplikat) tidak memahami ini.
Sotirios Delimanolis

@SotiriosDelimanolis Tolong jelaskan masalahnya; Saya tidak yakin apa maksud Anda.
chrylis -cautiouslyoptimistic-

59

Jika Anda tidak membuat kode aplikasi web, pastikan kelas Anda di mana @Autowiring dilakukan adalah spring bean. Biasanya, wadah pegas tidak akan menyadari kelas yang mungkin kita anggap sebagai kacang pegas. Kita harus memberi tahu wadah Musim Semi tentang kelas musim semi kita.

Ini dapat dicapai dengan mengkonfigurasi dalam appln-contxt atau cara yang lebih baik adalah dengan membubuhi keterangan kelas sebagai @Component dan tolong jangan membuat kelas beranotasi menggunakan operator baru. Pastikan Anda mendapatkannya dari Appln-context seperti di bawah ini.

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

    }

}

hai, saya telah melalui solusi Anda, itu benar. Dan di sini saya ingin tahu, "Mengapa kita tidak membuat instance kelas beranotasi menggunakan operator baru, bolehkah saya tahu alasan di balik itu.
Ashish

3
jika Anda membuat objek menggunakan yang baru, Anda akan menangani siklus hidup kacang yang bertentangan dengan konsep IOC. Kita perlu meminta wadah untuk melakukannya, yang melakukannya dengan cara yang lebih baik
Shirish Coolkarni

41

Sebenarnya, Anda harus menggunakan Object yang dikelola JVM atau Object yang dikelola Spring untuk memanggil metode. dari kode di atas di kelas controller Anda, Anda membuat objek baru untuk memanggil kelas layanan Anda yang memiliki objek kabel otomatis.

MileageFeeCalculator calc = new MileageFeeCalculator();

jadi tidak akan bekerja seperti itu.

Solusinya menjadikan MileageFeeCalculator ini sebagai objek kabel otomatis di Kontroler itu sendiri.

Ubah kelas Controller Anda seperti di bawah ini.

@Controller
public class MileageFeeController {

    @Autowired
    MileageFeeCalculator calc;  

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

4
Ini jawabannya. Karena Anda membuat Instansiasi MilageFeeCalculator baru Anda sendiri, Spring tidak terlibat dalam instantiasi, jadi Spring spring tidak memiliki pengetahuan objek yang ada. Dengan demikian, tidak dapat melakukan apa pun untuk itu, seperti menyuntikkan dependensi.
Robert Greathouse

26

Saya pernah mengalami masalah yang sama ketika saya tidak terbiasa the life in the IoC world. The @Autowiredbidang salah satu dari kacang saya adalah nol pada saat runtime.

Akar penyebabnya adalah, alih-alih menggunakan kacang yang dibuat secara otomatis yang dikelola oleh wadah Spring IoC (yang @Autowiredbidangnya indeeddisuntikkan dengan benar), saya adalah newingcontoh saya sendiri dari jenis kacang itu dan menggunakannya. Tentu saja @Autowiredbidang ini nol karena Spring tidak punya kesempatan untuk menyuntikkannya.


22

Masalah Anda baru (pembuatan objek dalam gaya java)

MileageFeeCalculator calc = new MileageFeeCalculator();

Dengan penjelasan @Service, @Component, @Configurationkacang diciptakan dalam
konteks aplikasi Spring ketika server dimulai. Tetapi ketika kita membuat objek menggunakan operator baru objek tidak terdaftar dalam konteks aplikasi yang sudah dibuat. Sebagai contoh kelas Employee.java yang saya gunakan.

Lihat ini:

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

Saya baru ke Spring, tetapi saya menemukan solusi yang berfungsi ini. Tolong beritahu saya jika itu cara yang tidak pantas.

Saya membuat Spring menyuntikkan applicationContextkacang ini:

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

Anda dapat meletakkan kode ini juga di kelas aplikasi utama jika Anda mau.

Kelas-kelas lain dapat menggunakannya seperti ini:

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

Dengan cara ini setiap kacang dapat diperoleh oleh objek apa pun dalam aplikasi (juga diperkuat dengan new) dan dengan cara statis .


1
Pola ini diperlukan untuk membuat kacang Spring dapat diakses oleh kode lama tetapi harus dihindari dalam kode baru.
chrylis -cautiouslyoptimistic-

2
Anda bukan orang baru di Spring. Anda seorang profesional. :)
sapy

Anda menyelamatkan saya ...
Govind Singh

Dalam kasus saya, saya memerlukan ini karena ada beberapa kelas pihak ketiga. Spring (IOC) tidak memiliki kendali atas mereka. Kelas-kelas ini tidak pernah dipanggil dari aplikasi spring boot saya. Saya mengikuti pendekatan ini dan itu berhasil untuk saya.
Joginder Malik

12

Sepertinya ini adalah kasus yang jarang terjadi, tetapi inilah yang terjadi pada saya:

Kami menggunakan @Injectalih-alih @Autowiredyang standar javaee didukung oleh Spring. Setiap tempat itu berfungsi dengan baik dan kacang disuntikkan dengan benar, bukan satu tempat. Suntikan kacang tampaknya sama

@Inject
Calculator myCalculator

Akhirnya kami menemukan bahwa kesalahannya adalah kami (sebenarnya, fitur lengkap otomatis Eclipse) diimpor com.opensymphony.xwork2.Injectalih - alih javax.inject.Inject!

Jadi untuk meringkas, make yakin bahwa penjelasan Anda ( @Autowired, @Inject, @Service, ...) memiliki paket yang benar!


5

Saya pikir Anda telah melewatkan untuk menginstruksikan musim semi untuk memindai kelas dengan anotasi.

Anda dapat menggunakan @ComponentScan("packageToScan")pada kelas konfigurasi aplikasi pegas Anda untuk menginstruksikan pegas untuk memindai.

@Service, @Component anotasi dll menambahkan deskripsi meta.

Spring hanya menyuntikkan instance dari kelas-kelas yang dibuat sebagai kacang atau ditandai dengan anotasi.

Kelas-kelas yang ditandai dengan anotasi perlu diidentifikasi oleh pegas sebelum menyuntikkan, @ComponentScanmenginstruksikan mencari pegas untuk kelas-kelas yang ditandai dengan anotasi. Ketika Spring menemukannya @Autowiredmencari kacang terkait, dan menyuntikkan contoh yang diperlukan.

Menambahkan anotasi saja, tidak memperbaiki atau memfasilitasi injeksi ketergantungan, Spring perlu tahu ke mana harus mencari.


Saya menemukan ini ketika saya lupa untuk menambahkan <context:component-scan base-package="com.mypackage"/>ke beans.xmlfile saya
Ralph Callaway

5

Jika ini terjadi di kelas tes, pastikan Anda tidak lupa memberi anotasi pada kelas.

Misalnya, di Spring Boot :

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

Beberapa waktu berlalu ...

Spring Boot terus berkembang . Tidak lagi diperlukan untuk menggunakan @RunWith jika Anda menggunakan versi JUnit yang benar .

Agar @SpringBootTestdapat bekerja sendiri, Anda harus menggunakan @Testdari JUnit5 bukan JUnit4 .

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

@SpringBootTest
public class MyTests {
    ....

Jika Anda mendapatkan konfigurasi ini salah, pengujian Anda akan dikompilasi, tetapi @Autowireddan @Valuebidang (misalnya) akan null. Karena Spring Boot beroperasi dengan sihir, Anda mungkin memiliki beberapa jalan untuk secara langsung men-debug kegagalan ini.



Catatan: @Valueakan menjadi nol saat digunakan dengan staticbidang.
Nobar

Spring menyediakan banyak cara untuk gagal (tanpa bantuan dari kompiler). Ketika ada masalah, taruhan terbaik Anda adalah kembali ke titik awal - hanya menggunakan kombinasi anotasi yang Anda tahu akan bekerja bersama.
Nobar

4

Solusi lain akan menelepon: SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this)
Ke konstruktor MileageFeeCalculator seperti ini:

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

Ini menggunakan publikasi yang tidak aman.
chrylis -cautiouslyoptimistic-

3

UPDATE: Orang-orang yang benar-benar pintar dengan cepat menunjukkan jawaban ini , yang menjelaskan keanehan, yang dijelaskan di bawah ini

JAWABAN ASLI:

Saya tidak tahu apakah itu membantu siapa pun, tetapi saya terjebak dengan masalah yang sama bahkan ketika melakukan hal-hal yang tampaknya benar. Dalam metode Utama saya, saya memiliki kode seperti ini:

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

dan dalam token.xmlfile saya sudah punya garis

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

Saya perhatikan bahwa package.path tidak ada lagi, jadi saya baru saja memutuskan untuk selamanya.

Dan setelah itu, NPE mulai masuk. Dalam pep-config.xmlaku punya hanya 2 kacang:

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

dan kelas SomeAbac memiliki properti yang dinyatakan sebagai

@Autowired private Settings settings;

untuk beberapa alasan yang tidak diketahui, pengaturan adalah null di init (), ketika <context:component-scan/>elemen tidak ada sama sekali, tetapi ketika itu ada dan memiliki beberapa bs sebagai basePackage, semuanya berfungsi dengan baik. Baris ini sekarang terlihat seperti ini:

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

dan itu berhasil. Mungkin seseorang dapat memberikan penjelasan, tetapi bagi saya itu sudah cukup sekarang)


5
Jawaban itu adalah penjelasannya. <context:component-scan/>secara implisit memungkinkan yang <context:annotation-config/>diperlukan untuk @Autowiredbekerja.
ForNeVeR

3

Ini adalah biang kerok dari memberikan NullPointerException MileageFeeCalculator calc = new MileageFeeCalculator();Kami menggunakan Spring - tidak perlu membuat objek secara manual. Pembuatan objek akan diurus oleh wadah IoC.


2

Anda juga bisa memperbaiki masalah ini menggunakan anotasi @Service pada kelas layanan dan meneruskan bean classA yang diperlukan sebagai parameter ke konstruktor kacang classB lainnya dan menjelaskan konstruktor classB dengan @Autowired. Cuplikan sampel di sini:

@Service
public class ClassB {

    private ClassA classA;

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

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

ini bekerja untuk saya tapi bisakah tolong jelaskan bagaimana ini menyelesaikan masalah?
CruelEngine

1
@CruelEngine, lihat ini adalah injeksi konstruktor (di mana Anda secara eksplisit mengatur objek) daripada hanya menggunakan injeksi lapangan (ini sebagian besar dilakukan oleh sebagian besar konfigurasi pegas). Jadi jika Anda membuat objek ClassB menggunakan operator "baru" adalah beberapa ruang lingkup lain maka itu tidak akan terlihat atau ditetapkan secara otomatis untuk ClassA. Oleh karena itu, ketika memanggil classB.useClassAObjectHere () akan melempar NPE karena objek classA tidak diotomasi jika Anda hanya mendeklarasikan bidang Injeksi. Baca chrylis sedang mencoba menjelaskan hal yang sama. Dan ini sebabnya injeksi konstruktor direkomendasikan daripada injeksi lapangan. Apakah ini masuk akal sekarang?
Abhishek

1

Apa yang belum disebutkan di sini dijelaskan dalam artikel ini di paragraf "Urutan eksekusi".

Setelah "mengetahui" bahwa saya harus membubuhi keterangan kelas dengan @Component atau turunannya @Service atau @Repository (saya kira ada lebih banyak), untuk mengotomatiskan komponen lain di dalamnya, saya tersadar bahwa komponen lain ini masih nol di dalam konstruktor komponen induk.

Menggunakan @PostConstruct memecahkan itu:

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

dan:

@Component
public class MyComponent {
    @Autowired ComponentDAO dao;

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

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

1

Ini hanya berlaku untuk unit test.

Kelas Layanan saya memiliki anotasi layanan dan itu adalah @autowiredkelas komponen lain. Ketika saya menguji komponen kelas datang nol. Karena untuk kelas layanan saya membuat objek menggunakannew

Jika Anda menulis unit test pastikan Anda tidak membuat objek menggunakan new object(). Gunakan sebagai ganti injectMock.

Ini memperbaiki masalah saya. Berikut ini tautan yang bermanfaat


0

Juga perhatikan bahwa jika, untuk alasan apa pun, Anda membuat metode dalam bentuk @Serviceas final, kacang autowired yang akan Anda akses darinya akan selalu ada null.


0

Dengan kata-kata sederhana ada dua alasan utama untuk menjadi @Autowiredbidangnull

  • KELAS ANDA BUKAN Kacang SPRING.

  • LAPANGAN BUKAN Kacang.


0

Tidak sepenuhnya terkait dengan pertanyaan, tetapi jika injeksi bidang adalah nol, injeksi berbasis konstruktor masih akan berfungsi dengan baik.

    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;
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.