Skip to content

5. [Cours]: مقدمه‌ای بر چارچوب اسپرینگ

واژگان کلیدی: معماری چندلایه، اسپرینگ، تزریق وابستگی.

اسپرینگ برای اولین بار در سال ۲۰۰۴ به‌عنوان یک مخزن شیء ظاهر شد. از آن زمان، به چندین شاخه تکامل یافته است: Spring MVC، Spring Data، Spring Batch، ... [http://spring.io]. در این فصل، ما صرفاً بر روی کانtejner شیء تمرکز خواهیم کرد. در اینجا چند نکته کلیدی آورده شده است:

  • یک برنامه کاربردی کلاس‌های متعددی دارد که برخی از آن‌ها اشیایی را به اشتراک می‌گذارند که باید منحصر به فرد باشند (سینگلتون‌ها). اسپرینگ این سینگلتون‌ها را ایجاد و مدیریت می‌کند؛
  • اسپرینگ این تک‌نسخه‌ها را در ساختاری به نام «زمینه» قرار می‌دهد؛
  • کلاس‌ها با درخواست آن‌ها از اسپرینگ با استفاده از نام، نوع، یا هر دو، به سینگلتون‌های برنامه دسترسی پیدا می‌کنند؛
  • اسپرینگ این اشیاء واحد را ایجاد می‌کند و هرگونه وابستگی که ممکن است داشته باشند را مدیریت می‌کند: یک شیء واحد در واقع ممکن است مرجع‌هایی به یک یا چند شیء واحد دیگر نگهداری کند. هنگامی که اسپرینگ یک شیء واحد را ایجاد می‌کند، وابستگی‌های آن را نیز ایجاد می‌کند؛
  • هنگامی که یک برنامه مبتنی بر Spring راه‌اندازی می‌شود، می‌تواند از Spring بخواهد تا تمام سینگلتون‌های برنامه را ایجاد کند. این سینگلتون‌ها سپس در کانکست Spring در دسترس خواهند بود؛
  • اسپرینگ استفاده از معماری‌های لایه‌بندی‌شده و برنامه‌نویسی مبتنی بر رابط را تسهیل می‌کند. در موارد ساده، هر لایه توسط یک کلاس تک‌نمونه پیاده‌سازی می‌شود و یک رابط را پیاده‌سازی می‌کند. اگر برنامه به جای کلاس‌های پیاده‌سازی، با رابط‌های لایه کار کند، این امر به یک معماری مقیاس‌پذیر منجر می‌شود که به لطف دو ویژگی زیر، امکان تغییر پیاده‌سازی یک لایه بدون تأثیر بر لایه‌های دیگر را فراهم می‌کند:
    • برنامه با استفاده از نام لایه، به یک مرجع برای آن دسترسی پیدا می‌کند. اسپرینگ مرجعی به کلاس پیاده‌ساز لایه را در اختیار آن قرار می‌دهد؛
    • برنامه این مرجع را به عنوان مرجعی به رابط لایه و نه به یک کلاس، استفاده می‌کند؛

می‌توان Singletonها را به سه روش اعلام کرد که این روش‌ها قابل ترکیب هستند:

  • در داخل یک فایل XML،
  • در یک کلاس پیکربندی ویژه؛
  • با استفاده از هر کلاسی با استفاده از انوتیشن‌ها؛

در زیر، سه مثال از پیکربندی را ارائه می‌دهیم:

  • [exemple-01]: پیکربندی متمرکز در یک فایل XML واحد؛
  • [exemple-02]: پیکربندی متمرکز در یک کلاس جاوا واحد؛
  • [exemple-03]: پیکربندی توزیع‌شده در چندین کلاس جاوا؛

مثال نهایی، [exemple-04]، بر پیکربندی Spring برای یک معماری لایه‌لایه تمرکز دارد. این مهم‌ترین مثال است. از آن برای پیکربندی معماری‌های توصیف‌شده در این سند بارها استفاده خواهد شد.

این چهار مثال پایه‌های آنچه در ادامه می‌آید را بنا می‌نهند:

  • پیکربندی اسپرینگ و تزریق وابستگی؛
  • استفاده از Maven برای مدیریت وابستگی‌های پروژه؛
  • استفاده از JUnit برای تست پروژه‌ها؛

5.1. Support

 

پوشه [support / chap-05] حاوی پروژه‌های Eclipse برای این فصل است.

5.2. Exemple-01

5.2.1. پروژه اکلیپس

 

5.2.2. کلاس [Personne]

 

package istia.st.spring.core;

public class Personne {

    // fields
    private String nom;
    private String prenom;
    private int age;

    // سازنده‌ها
    public Personne() {

    }

    public Personne(String nom, String prénom, int âge) {
        this.nom = nom;
        this.prenom = prénom;
        this.age = âge;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s,%d]", prenom, nom, age);
    }

    // گیرنده و تنظیم‌کننده

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getPrenom() {
        return prenom;
    }

    public void setPrenom(String prenom) {
        this.prenom = prenom;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

توجه: گترها و سترها می‌توانند به‌طور خودکار طبق [1-2] تولید شوند:

5.2.3. کلاس [Appartement]

 

package istia.st.spring.core;

public class Appartement {

    // fields
    private Personne proprietaire;
    private int surface;

    // گیرنده‌ها و تنظیم‌کننده‌ها

    public Personne getProprietaire() {
        return proprietaire;
    }

    public void setProprietaire(Personne proprietaire) {
        this.proprietaire = proprietaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", proprietaire, surface);
    }

}

توجه: این کلاس کانتراکتور صریحی ندارد. در این صورت، کانتراکتور پیش‌فرض بدون پارامتر—که هیچ کاری انجام نمی‌دهد—همیشه وجود دارد. هنگامی که کانتراکتورها ایجاد می‌شوند، این کانتراکتور پیش‌فرض دیگر به طور ضمنی وجود ندارد. بنابراین باید به صورت صریح تعریف شود:

public Appartement(){
}

5.2.4. فایل پیکربندی Spring

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-4.0.xsd">
    <!-- شخص ۰۱ -->
    <bean id="personne_01" class="istia.st.spring.core.Personne">
        <constructor-arg index="0" value="dubois" />
        <constructor-arg index="1" value="paul" />
        <constructor-arg index="2" value="34" />
    </bean>
    <!-- شخص ۰۲ -->
    <bean id="personne_02" class="istia.st.spring.core.Personne">
        <property name="nom" value="martin" />
        <property name="prenom" value="micheline" />
        <property name="age" value="18" />
    </bean>
    <!-- فهرستی از افراد -->
    <util:list id="club">
        <ref bean="personne_01" />
        <ref bean="personne_02" />
    </util:list>
    <!-- یک ساختار مسطح -->
    <bean id="appartement" class="istia.st.spring.core.Appartement">
        <property name="surface" value="100" />
        <property name="proprietaire" ref="personne_01" />
    </bean>
</beans>
  • خطوط ۲ و ۲۷: singletonها درون تگ <beans> تعریف می‌شوند؛
  • خطوط ۶–۱۰: هر singleton درون تگ <bean> تعریف می‌شود؛
  • خط ۶: [id] شناسهٔ نمونهٔ واحد است. [class] نام کامل کلاسی است که باید نمونهٔ آن ایجاد شود؛
  • خطوط ۷–۹: سه مقداری که باید به سازنده کلاس [Personne] پاس داده شوند؛
  • خطوط ۱۲–۱۶: کلاس [Personne] ابتدا با استفاده از سازنده پیش‌فرض خود [new Personne()] ایجاد می‌شود. سپس، برای هر تگ [property]، از یک متد setter (تنظیم‌کننده) کلاس استفاده می‌شود. به عنوان مثال، برای خط ۱۳، متد [setNom("martin")] اجرا خواهد شد. بنابراین، متد [setNom] باید وجود داشته باشد. این یک نکته مهم است که باید به خاطر بسپارید؛
  • خطوط ۱۸–۲۱: تگ <util:list> برای تعریف یک تک‌چیز (singleton) که یک لیست است، استفاده می‌شود؛
  • خط ۱۹: به singleton با شناسه [personne_01] که در خط ۶ تعریف شده است، اشاره می‌کند. این همان چیزی است که به آن تزریق وابستگی گفته می‌شود. دو ویژگی می‌توانند برای مقداردهی اولیه فیلد یک singleton استفاده شوند:
    • [value]: برای تخصیص یک مقدار اولیه (primitive) (رشته، عدد، تاریخ و غیره) به فیلد،
    • [ref]: برای تخصیص مرجع یک شی Spring به فیلد؛

توجه: فایل پیکربندی Spring می‌تواند به صورت زیر تولید شود [1-4]:

5.2.5. کلاس قابل اجرا

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo01 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        // بازیابی کنتکست Spring
        ApplicationContext ctx = new ClassPathXmlApplicationContext("config-01.xml");
        // بازیابی کردن بیین‌ها
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        // نمایش آنها
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // بیان‌های بازیابی‌شده از نوع سینگلتون هستند
        // می‌توان آن‌ها را چندین بار درخواست کرد؛ همان بین همیشه بازیابی می‌شود
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • خط ۱۴: کانکست Spring ایجاد می‌شود. سپس تمام singletonهای تعریف‌شده در فایل [config-01.xml] نمونه‌سازی می‌شوند؛
  • خط ۱۶: یک مرجع از نوع [personne_01] که با شناسه [Personne] شناسایی شده است را درخواست می‌کند. این پارامتر دوم اختیاری است، اما اگر حذف شود، مرجعی از نوع [Object] بازگردانده می‌شود که باید سپس به نوع [Personne] تبدیل شود؛
  • خط ۱۹: ما از نام bean استفاده نمی‌کنیم، بلکه فقط از نوع آن استفاده می‌کنیم، زیرا تنها یک singleton از نوع [Appartement] وجود دارد؛
  • خط ۱۸: هم شناسه و هم نوع singleton مورد نظر استفاده شده‌اند. شناسه زائد است زیرا تنها یک singleton از نوع [new ArrayList<Personne>().getClass()] وجود دارد؛
  • خطوط ۳۲–۳۳: نشان می‌دهند که اگر یک نمونهٔ واحد (singleton) یکسان چندین بار درخواست شود، همیشه همان مرجع دریافت می‌شود، و بدین ترتیب اثبات می‌کند که این در واقع یک نمونهٔ واحد است. درک این نکته مهم است؛

توجه: یک کلاس قابل اجرا را می‌توان به صورت زیر تولید کرد: [1-6]:

  • با تیک زدن [6] است که کلاس تولید شده حاوی یک متد استاتیک [main] خواهد بود، که آن را قابل اجرا می‌سازد؛

5.2.6. وابستگی‌های پروژه

 
  • وابستگی‌های Spring: [spring-core, spring-beans, spring-context, spring-expression, commons-logging];

وابستگی‌ها به پروژه به شرح زیر اضافه می‌شوند:

  • برای [1]: روی پروژه کلیک راست کرده / [Build Path] / [Configure Build Path];
  • به [2]: [Add JARs] اگر فایل‌های JARs که باید اضافه شوند در یک پوشه درون پروژه باشند. در غیر این صورت، [Add External JARs];
  • به [3]، فایل‌های JARs را که باید به ClassPath پروژه اضافه شوند، انتخاب کنید (آنها در پوشه [lib] درون پروژه قرار دارند)؛

تعریف: [ClassPath] یک پروژه، مجموعه‌ای از پوشه‌هایی است که توسط JVM (ماشین مجازی جاوایی) که پروژه را اجرا می‌کند، اسکن می‌شوند تا کلاسی را که به آن ارجاع شده است، پیدا کند. برای یک پروژه اکلیپس، [ClassPath] شامل عناصر زیر است:

  • پوشه [bin] پروژه؛
  • عناصر [Build Path] پروژه؛

پوشه [bin] پوشه‌ای است که با کامپایل کردن پوشه [src] تولید می‌شود. بنابراین، هر چیزی که در پوشه [src] قرار داده شود، به طور خودکار بخشی از [ClassPath] می‌شود (حتی اگر یک فایل .java نباشد). بنابراین، در پروژه قبلی، فایل پیکربندی Spring با نام [config-01.xml] که در پوشه [src] قرار دارد، در زمان اجرا در پوشه [Classpath] پروژه گنجانده خواهد شد.

5.2.7. نتایج

févr. 21, 2014 1:16:23 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3ac67f69: startup date [Fri Feb 21 13:16:23 CET 2014]; root of context hierarchy
févr. 21, 2014 1:16:23 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
Infos: Loading XML bean definitions from class path resource [config-01.xml]
personnes--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
club--------
Personne[paul, dubois,34]
Personne[micheline, martin,18]
appartement--------
Appartement[Personne[paul, dubois,34], 100]
beans [p01,p01b] identiques ? true

5.3. Exemple-02

5.3.1. پروژه اکلیپس

 

5.3.2. کلاس پیکربندی Spring


package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public Appartement appartement(Personne personne_01) {
        Appartement appartement = new Appartement();
        appartement.setSurface(200);
        appartement.setPropriétaire(personne_01);
        return appartement;
    }
}
  • خط ۹: انوتیشن [@Configuration] یک انوتیشن اسپرینگ است. این نشان می‌دهد که کلاس دارای انوتیشن، اشیاء تک‌نسخه (singletons) را تعریف می‌کند. این اشیاء با استفاده از انوتیشن [@Bean] تعریف می‌شوند. اسپرینگ تمام متدهایی را که با [@Bean] علامت‌گذاری شده‌اند، اجرا خواهد کرد. این متدها سینگل‌تون‌های برنامه را ایجاد می‌کنند؛
  • خطوط ۱۲–۱۵: یک تک‌نفره را که با [personne_01] شناسایی می‌شود، تعریف می‌کنند، یعنی نام متد.
  • خط ۲۳: پارامترهای [personne_01, personne_02] حامل نام‌های singletonها هستند. Spring به‌طور خودکار آن‌ها را با ارجاع به این singletonها مقداردهی اولیه می‌کند. این روش به تزریق پارامتر (parameter injection) معروف است؛

این روش پیکربندی سینگل‌تون‌ها صریح‌تر از روشی است که از فایل XML استفاده می‌کند. در واقع، ما خود همان کاری را که Spring به‌طور ضمنی با استفاده از فایل XML انجام می‌داد، تکرار می‌کنیم.

5.3.3. کلاس قابل اجرا

 

package istia.st.spring.core;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo02 {

    @SuppressWarnings({ "unchecked", "resource" })
    public static void main(String[] args) {
        //بازیابی کانکسِت Spring
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        // بازیابی کردن بین‌ها
        Personne p01 = ctx.getBean("personne_01", Personne.class);
        Personne p02 = ctx.getBean("personne_02", Personne.class);
        List<Personne> club = ctx.getBean("club", new ArrayList<Personne>().getClass());
        Appartement appart01 = ctx.getBean(Appartement.class);
        //نمایش آن‌ها
        System.out.println("personnes--------");
        System.out.println(p01);
        System.out.println(p02);
        System.out.println("club--------");
        for (Personne p : club) {
            System.out.println(p);
        }
        System.out.println("appartement--------");
        System.out.println(appart01);
        // بیان‌های بازیابی‌شده از نوع سینگلتون هستند
        // می‌توان آن‌ها را چندین بار درخواست کرد؛ همیشه همان bean بازیابی می‌شود
        Personne p01b = ctx.getBean("personne_01", Personne.class);
        System.out.println(String.format("beans [p01,p01b] identiques ? %s", p01b == p01));
    }
}
  • خط ۱۳ همهٔ بیَن‌های تعریف‌شده در کلاس [Config] را نمونه‌سازی می‌کند؛
  • بقیه کد بدون تغییر باقی می‌ماند؛

5.3.4. وابستگی‌های پروژه

وابستگی‌ها توسط فایل زیر تعریف شده‌اند: [pom.xml]:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-02</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <!-- اسپرینگ -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.3.RELEASE</version>
        </dependency>
    </dependencies>
    <!--افزونه‌ها -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>

مدیریت دستی وابستگی‌های یک پروژه، هنگام استفاده از کتابخانه‌های جاوایی که وابستگی‌هایشان نامشخص است، به یک دردسر تبدیل می‌شود. برای مثال، فریم‌ورک [Hibernate] که مدیریت دسترسی به پایگاه داده را بر عهده دارد، ده‌ها وابستگی دارد. پروژه [Maven] این مشکل را حل می‌کند. شما وابستگی مورد نیاز خود را مشخص می‌کنید. سپس این وابستگی به طور خودکار در مخازن Maven در سراسر اینترنت جستجو می‌شود. اگر خود وابستگی درخواستی وابستگی‌های دیگری داشته باشد، آن‌ها نیز به طور خودکار دانلود می‌شوند. این وابستگی‌های دانلود شده در یک مخزن محلی روی دستگاه ذخیره می‌شوند. اگر بعداً برنامه دیگری به همان وابستگی نیاز داشته باشد، آن وابستگی دانلود نخواهد شد، بلکه از مخزن محلی بازیابی می‌شود. یک وابستگی با عناصر زیر مشخص می‌شود:

  • خط ۱۷: یک تگ <dependency
  • خط ۱۸: یک ویژگی [groupId]، که عموماً شرکت ایجادکننده وابستگی را مشخص می‌کند؛
  • خط ۱۹: یک ویژگی [artifactId] که وابستگی را شناسایی می‌کند؛
  • خط ۲۰: یک ویژگی [version] که نسخه مورد نظر را مشخص می‌کند؛

خودِ ساخت پروژه یک کامپوننت Maven را تولید می‌کند که در خطوط ۴–۸ تعریف شده است:

  • خطوط ۴–۶: ویژگی‌های [ groupId, artifactId,version] که همین‌الان توضیح دادیم؛
  • خطوط ۷–۸: این‌ها ویژگی‌های اختیاری هستند؛

بعداً کمی به نقش خطوط ۲۴–۴۰ باز خواهیم گشت. برای تبدیل یک پروژه استاندارد اکلیپس به یک پروژه Maven، باید دو کار انجام دهید:

  • فایل [pom.xml] مذکور در بالا را ایجاد کنید؛
  • اعلام کنید که پروژه اکنون یک پروژه Maven است [1-4]:

نماد یک پروژه Maven یک M [4] است. حرف S نشان می‌دهد که پروژه حاوی اجزای Spring است. تبدیل یک پروژه Eclipse به یک پروژه Maven (همان‌طور که ما همین حالا انجام دادیم) توصیه نمی‌شود، زیرا در این صورت پروژه فاقد ساختار مورد انتظار یک پروژه Maven خواهد بود که گاهی می‌تواند به مشکلات غیرمنتظره‌ای منجر شود.

5.3.5. تولید آرتیفکت Maven پروژه

ما به عنصری که در خطوط ۴ تا ۶ فایل [pom.xml] تعریف شده است، به‌عنوان آرتفکت Maven پروژه اشاره می‌کنیم:


    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-02</artifactId>
<version>0.0.1-SNAPSHOT</version>

برای تولید این آرتیفکت، خطوط ۳ تا ۷ زیر باید در فایل [pom.xml] موجود باشند:


    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

این‌ها پلاگ‌این Maven را که قادر به تولید آرتیفکت پروژه است، تعریف می‌کنند. سپس به شرح زیر ادامه می‌دهیم:

آرتفکت تولیدشده به این روش در مخزن محلی Maven قرار می‌گیرد. مکان این مخزن را می‌توان در پیکربندی Eclipse یافت:

 

سپس می‌توانید تأیید کنید که آرتفکت Maven به درستی نصب شده است:

 

از این پس، یک پروژه محلی دیگر در Maven قادر خواهد بود از این آرشیو استفاده کند.

5.4. Exemple-03

5.4.1. پروژه اکلیپس

این بار، ما یک پروژه Maven به نام [1-8] ایجاد می‌کنیم:

  • در [3b]: یک پوشه خالی را مشخص کنید که پروژه در آن ایجاد خواهد شد؛
  • برای [4]: شناسه‌ی گروه Maven که پروژه به آن تعلق خواهد داشت؛
  • در [5]: نام آرتیفکت Maven تولید شده:
  • در [6]: نسخهٔ آن؛
  • در [7]: نوع بسته‌بندی آن (سایر گزینه‌ها شامل war، ear، apk و غیره هستند)؛
  • در [8]: پروژهٔ ایجادشده به این ترتیب؛

به‌طور پیش‌فرض، یک پروژه Maven دارای ساختار دایرکتوری مشخصی است:

  • [src / main / java]: کد منبع پروژه. خروجی کامپایل‌شده از این منابع در پوشه [target/classes] پروژه قرار می‌گیرد؛
  • [src / main / resources]: منابعی که باید در کلاس‌پث پروژه باشند اما فایل‌های منبع جاوا نیستند. این منابع به همان شکل به پوشه [target/classes] پروژه کپی می‌شوند؛
  • [src / test / java]: کد منبع تست‌های پروژه. خروجی کامپایل‌شده از این منابع در پوشه [target/test-classes] پروژه قرار داده خواهد شد. این موارد در آرشیو Maven پروژه گنجانده نمی‌شوند؛
  • [src / test / resources]: منابعی که باید برای تست‌ها در کلاس‌پث پروژه باشند، بدون اینکه لزوماً فایل‌های منبع جاوا باشند. این فایل‌ها به همان صورت به پوشه [target/test-classes] پروژه کپی خواهند شد؛

ما پروژه را به شرح زیر تکمیل می‌کنیم:

 

5.4.2. پیکربندی Maven

یک فایل با نام [pom.xml] به‌طور پیش‌فرض ایجاد می‌شود. ما آن را به شرح زیر تغییر می‌دهیم:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-core-03</name>
    <description>Introduction à Spring</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <!--پروژهٔ والد Maven -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring Context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <!-- logs -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

    </dependencies>

    <!-- پلاگین‌ها -->
    <build>
        <plugins>
            <!--برای تولید آرشیو پروژه همراه با وابستگی‌های آن -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <!--برای نصب آرشیو پروژه در مخزن محلی Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • خط ۱۱: پروژه با نام UTF-8 کدگذاری شده است؛
  • خط ۱۲: ما از JDK نسخه ۱.۸ برای کامپایل پروژه استفاده می‌کنیم؛
  • خطوط ۱۶–۲۰: برای پروژه‌هایی که از کتابخانه‌های Spring استفاده می‌کنند، استفاده از یک پروژه والد Maven به نام [spring-boot-starter-parent] مفید است. این پروژه نسخه‌های کتابخانه‌های مختلف Spring و همچنین نسخه‌های وابستگی‌های آن‌ها را تعریف می‌کند. این بدان معناست که دیگر نیازی به مشخص کردن آن‌ها در تعریف وابستگی‌ها نیست. بنابراین، در خطوط ۲۴ تا ۲۷، نسخه مورد نظر از [spring-context] مشخص نشده است. این نسخه همان خواهد بود که توسط پروژه والد [spring-boot-starter-parent] تعریف شده است. این تکنیک به این معناست که دیگر نیازی به نگرانی در مورد ناسازگاری‌های نسخه‌ای احتمالی بین وابستگی‌ها ندارید. وابستگی‌هایی که توسط پروژه والد تعریف شده‌اند با یکدیگر سازگار هستند؛
  • خطوط 29–32: Spring مقدار قابل توجهی اطلاعات را از طریق یک کتابخانه لاگ‌گیری به کنسول می‌نویسد. این کتابخانه در اینجا وارد شده است؛
  • خطوط ۴۰–۴۷: یک افزونه Maven، که بعداً به آن باز خواهیم گشت؛
  • خطوط ۵۰–۵۲: افزونه برای تولید آرتیفکت Maven پروژه؛

5.4.3. کلاس پیکربندی Spring

  

کلاس [Config] به شرح زیر است:


package istia.st.spring.core.config;

import istia.st.spring.core.entities.Personne;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "spring.core.entities" })
public class Config {

    @Bean
    public Personne personne_01() {
        return new Personne("Paul", "Dubois", 34);
    }

    @Bean
    public Personne personne_02() {
        return new Personne("Martin", "Micheline", 18);
    }

    @Bean
    public List<Personne> club(Personne personne_01, Personne personne_02) {
        List<Personne> personnes = new ArrayList<Personne>();
        personnes.add(personne_01);
        personnes.add(personne_02);
        return personnes;
    }

    @Bean
    public int mySurface() {
        return 200;
    }
}
  • در اینجا با کدی مواجه می‌شویم که قبلاً غیرفعال شده است، به همراه دو افزوده جدید:
    • خط ۱۳: نشان می‌دهد که بیین‌های دیگری برای instantiate شدن در پکیج [spring.core.entities] وجود دارند،
    • خطوط 34–37: یک بیون به نام [mySurface];

5.4.4. کلاس [Appartement]

 

package spring.core.entities;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Appartement {

    // fields injected by Spring
    @Autowired
    @Qualifier("personne_01")
    private Personne propriétaire;

    @Autowired
    @Qualifier("mySurface")
    private int surface;

    // گیرنده و تنظیم‌کننده
    public Personne getPropriétaire() {
        return propriétaire;
    }

    public void setPropriétaire(Personne propriétaire) {
        this.propriétaire = propriétaire;
    }

    public int getSurface() {
        return surface;
    }

    public void setSurface(int surface) {
        this.surface = surface;
    }

    // toString
    public String toString() {
        return String.format("Appartement[%s, %s]", propriétaire, surface);
    }

}
  • خط ۷: آناوتیشن [@Component] به Spring می‌گوید که این کلاس یک سینگلتون است که فریم‌ورک باید آن را نمونه‌سازی و مدیریت کند. دلیل اینکه این سینگلتون پیدا می‌شود این است که ما [@ComponentScan({ "istia.st.spring.core.entities" })] را در کلاس [Config] نوشته‌ایم؛
  • خط ۱۱: به Spring دستور می‌دهد تا مرجع یکی از singletonها را در فیلد تزریق کند. این کار را می‌توان به دو روش تعریف کرد:
    • با شناسهٔ آن (خطوط ۱۲ و ۱۶)،
    • بر اساس نوع آن، اگر تنها یک سینگل‌تون از آن نوع وجود داشته باشد؛

5.4.5. اجرای پروژه

اجرای پروژه خروجی زیر را در کنسول تولید می‌کند:

17:32:39.797 [main] DEBUG o.s.core.env.StandardEnvironment - Adding [systemProperties] PropertySource with lowest search precedence
....
17:32:40.134 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'appartement'
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
17:32:40.135 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'personne_01'
beans [p01,p01b] identiques ? true
  • خطوط ۱–۳: Spring تعداد بسیار زیادی از موارد لاگ را تولید می‌کند که به چندین ده خط می‌رسد. این لاگ‌ها می‌توانند برای عیب‌یابی پروژه‌ای که کار نمی‌کند بسیار مفید باشند. وقتی پروژه در حال کار است، لاگ‌ها را می‌توان به صورت زیر کاهش داد:
  

در پوشه [src / main / resources]، فایل زیر با نام [logback.xml] ایجاد می‌شود:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- کدگذارها به‌طور پیش‌فرض نوع اختصاص داده می‌شوند
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!--کنترل سطح لاگ -->
  <root level="info"> <!-- اطلاعات، اشکال‌زدایی، هشدار -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
  • در خط ۱۲، سطح لاگ را تنظیم کنید. [debug] یک سطح بسیار تفصیلی است، در حالی که [info] بسیار کمتر؛

در اینجا نتایج با [level=info] آمده است:

17:39:58.580 [main] INFO  o.s.c.a.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7cf10a6f: startup date [Tue Apr 07 17:39:58 CEST 2015]; root of context hierarchy
personnes--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
club--------
Personne[Dubois, Paul,34]
Personne[Micheline, Martin,18]
appartement--------
Appartement[Personne[Dubois, Paul,34], 200]
beans [p01,p01b] identiques ? true

اکنون تنها یک خط لاگ وجود دارد.

5.4.6. ایجاد آرشیو پروژه همراه با وابستگی‌های آن

آرشیوی که در پروژه قبلی ایجاد شده است نیز می‌تواند توسط یک پروژه اکلیپس غیر Maven استفاده شود. برخی پروژه‌ها از کتابخانه‌های متعددی استفاده می‌کنند و اطمینان از اینکه هیچ‌کدام از قلم نیفتند می‌تواند دشوار باشد. اینجاست که Maven معجزه می‌کند، زیرا شما فقط کافی است وابستگی سطح بالا را مشخص کنید تا وابستگی‌های سطح پایین‌تر به طور خودکار به کلاس‌پث پروژه اضافه شوند. وقتی یک پروژه اکلیپس غیر Maven نیاز به استفاده از آرشیوهای یک پروژه Maven دارد، می‌توان آرتفکت پروژه دوم را به همراه تمام وابستگی‌های آن تولید کرد (که در پروژه قبلی این امکان وجود نداشت). برای این کار، خطوط ۳ تا ۱۰ زیر باید در فایل [pom.xml] موجود باشند:


    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
</build>

این‌ها پلاگ‌این Maven را تعریف می‌کنند که قادر به تولید آرتیفکت پروژه به همراه تمام وابستگی‌های آن است. سپس، در [1-6] به شرح زیر عمل کنید:

  • [4-6] نشان‌دهنده پیکربندی ساخت Maven است؛
  • در [4]، هر نامی را وارد کنید؛
  • در [5]، پوشه پروژه را مشخص کنید؛
  • در [6]، اهداف Maven را وارد کنید:
    • [clean]: پوشه پروژه [target] حذف می‌شود؛
    • [compile]: پروژه کامپایل شده است. خروجی‌های ساخت در پوشهٔ بازتولیدشدهٔ [target] قرار می‌گیرند؛
    • [assembly:single]: کلاس‌های پروژه و وابستگی‌های آن در یک آرشیو JAR واحد درون پوشه [target] قرار می‌گیرند؛

پس از اجرا، نتیجه زیر به دست می‌آید:

یک آرشیو JAR یک فایل فشرده است و بنابراین می‌توان آن را با یک ابزار تفکیک فشرده‌سازی باز کرد. پس از تفکیک فشرده آرشیو قبلی، ساختار دایرکتوری زیر به دست می‌آید:

  • در [8]، کلاس‌های وابستگی‌های پروژه؛
  • در [9]، کلاس‌های مربوط به خود پروژه؛

5.5. Exemple-04

5.5.1. هدف

این مثال بر اساس یکی از مثال‌های ارائه‌شده در سند [Introduction à Spring IoC] است که مشارکت Spring در پیکربندی معماری‌های چندلایه را نشان می‌دهد. در سند اصلی، این مثال با استفاده از یک پیکربندی Spring که با فایلی به نام XML ایجاد شده است، پیاده‌سازی شده است. در اینجا، ما این مثال را با استفاده از پیکربندی مبتنی بر کلاس‌های جاوا و anotationها پیاده‌سازی می‌کنیم.

هدف در اینجا پیکربندی یک پروژه Spring برای معماری زیر است:

هر لایه یک رابط دارد که توسط دو کلاس پیاده‌سازی شده است. ما می‌خواهیم نشان دهیم که به لطف Spring، می‌توانیم پیاده‌سازی یک لایه را بدون هیچ تأثیری بر کد لایه‌های دیگر تغییر دهیم.

5.5.2. پروژه اکلیپس

5.5.2.1. Génération

ما در حال ایجاد یک نوع پروژه جدید هستیم:

  • در [4]، نام پروژهٔ Eclipse را وارد کنید؛
  • در [5]، یک پروژه Maven را انتخاب کنید؛
  • در [6]، یک نسخه از جاوا با نسخهٔ ۱.۷ یا بالاتر را انتخاب کنید؛
  • در [7]، نسخه پیشنهادی Spring Boot را انتخاب کنید؛
  • اطلاعات در [8-11] مربوط به Maven است؛
  • در [12]، می‌توانید یکی یا چند مورد از وابستگی‌های پیشنهادی را انتخاب کنید. این کار منجر به گنجانده شدن تعدادی وابستگی در فایل Maven [pom.xml] خواهد شد؛
  • در [13]، یک پوشهٔ خالی موجود را برای قرار دادن پروژه مشخص کنید؛
  • در [14]، پروژهٔ تولیدشده. اکنون اجزای آن را بررسی خواهیم کرد؛

این پروژه یک پروژه Maven است که توسط فایل زیر پیکربندی شده است، [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>istia.st.spring.core</groupId>
    <artifactId>spring-core-04</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-core-04</name>
    <description>Programmation par interfaces</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- جستجوی والد از مخزن -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.SpringCore04Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • خطوط ۶–۱۲: حاوی اطلاعاتی هستند که در جادوگر ایجاد پروژه وارد شده‌اند؛
  • خطوط ۱۴–۱۹: پروژهٔ والد Maven، که تعدادی کتابخانه و نسخه‌های آن‌ها را تعریف می‌کند. اگر هر یک از این‌ها وابستگی‌های پروژه باشند، در فایل [pom.xml] بدون نسخه‌هایشان فهرست شده‌اند؛
  • خط ۲۳: این خط فقط در صورتی استفاده می‌شود که قصد داشته باشید یک آرشیو قابل اجرا از پروژه ایجاد کنید. در غیر این صورت، بلااستفاده است؛
  • خطوط ۲۸–۳۱: حداقل وابستگی‌ها برای یک پروژه Spring Boot. توجه داشته باشید که ما هیچ وابستگی‌ای را از لیست کشویی انتخاب نکرده‌ایم؛
  • خطوط 33–37: وابستگی مورد نیاز برای مدیریت تست‌های واحد (JUnit و [http://junit.org/]) یکپارچه با Spring. خط 36 نشان می‌دهد که این وابستگی فقط برای تست مورد نیاز است. در نتیجه، در آرشیو پروژه گنجانده نخواهد شد؛
  • خطوط ۴۲–۴۵: پلاگینی که برای تولید آرتیفکت Maven پروژه استفاده می‌شود؛

فهرست وابستگی‌های ارائه‌شده توسط این فایل به شرح زیر است: [1]:

خواهیم دید که این‌ها برای کاری که می‌خواهیم انجام دهیم کافی هستند.

5.5.2.2. کلاس قابل اجرا

  

کلاس اجرایی [SpringCore04Application] [[2] به شرح زیر است:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringCore04Application.class, args);
    }
}
  • در خط ۶، تگ [@SpringBootApplication] مخفف سه تگ [@Configuration, @EnableAutoConfiguration, @ComponentScan] است، که به این معنی است:
    • که کلاس [SpringCore04Application] یک کلاس پیکربندی Spring است؛
    • که به Spring Boot دستور داده شده است تا پیکربندی‌ها را بر اساس کلاس‌هایی که در classpath پروژه پیدا می‌کند اعمال کند، در این مورد در داخل وابستگی‌های Maven؛
    • برای بررسی دایرکتوری جاری (دایرکتوری کلاس [SpringCore04Application]) به منظور یافتن هرگونه کامپوننت دیگر Spring در آنجا؛
  • خط ۱۰: متد استاتیک [SpringApplication.run] اجرا می‌شود. پارامتر اول آن یک کلاس پیکربندی Spring است که در این مورد کلاس [SpringCore04Application] می‌باشد. پارامتر دوم آن، در این مورد، لیست آرگومان‌هایی است که به متد [main] (خط ۹) پاس شده است. نقش متد استاتیک [SpringApplication.run] ایجاد کانکست Spring است، یعنی ایجاد انواع Beans که یا در کلاس‌های پیکربندی یافت می‌شوند یا در پوشه‌هایی که توسط anotation [@ComponentScan] اسکن شده‌اند. متد [main] در اینجا کار دیگری انجام نمی‌دهد. برای اینکه کمی ملموس‌تر شود، آن را به شکل زیر تبدیل خواهیم کرد:

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class SpringCore04Application {

    public static void main(String[] args) {
        // مثال‌سازی کانکست Spring
        ConfigurableApplicationContext context = SpringApplication.run(SpringCore04Application.class, args);
        //نمایش کانکست
        System.out.println("---------------- Liste des beans Spring");
        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }
        //بستن کانکست
        context.close();
    }
}
  • خط ۱۲: متد استاتیک [SpringApplication.run] کانکست Spring را که ساخته است بازمی‌گرداند؛
  • خطوط ۱۵–۱۷: ما نام تمام بی́n‌ها در این زمینه را نمایش می‌دهیم؛

می‌توان برنامه را به صورت زیر اجرا کرد: [1-3]. روش معمول، [Run As Java Application]، نیز معتبر است.

نتیجه زیر به دست می‌آید:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

2015-04-08 11:18:38.254  INFO 4796 --- [           main] demo.SpringCore04Application             : Starting SpringCore04Application on Gportpers3 with PID 4796 (D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04\target\classes started by ST in D:\data\istia-1415\polys\istia\dvp-spring-database\codes\original\intro-spring-core\spring-core-04)
2015-04-08 11:18:38.295  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.776  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-04-08 11:18:38.788  INFO 4796 --- [           main] demo.SpringCore04Application             : Started SpringCore04Application in 0.773 seconds (JVM running for 1.335)
---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer
2015-04-08 11:18:38.789  INFO 4796 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@64485a47: startup date [Wed Apr 08 11:18:38 CEST 2015]; root of context hierarchy
2015-04-08 11:18:38.790  INFO 4796 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
  • خطوط ۱۴–۲۸: بین‌های (beans) مربوط به کانکست Spring. ما نقش آن‌ها را نمی‌دانیم. ما در خط ۱۸ به بین [springCore04Application] برخورد می‌کنیم که به دلیل انوتیشن (annotation) آن، [@SpringBootApplication]، به طور خودکار به یک بین Spring تبدیل می‌شود؛
  • سایر خطوط لاگ‌های Spring در سطح [INFO] هستند. همان‌طور که قبلاً دیدیم، این لاگ‌ها را می‌توان با فایل [logback.xml] که در classpath پروژه قرار دارد کنترل کرد:
  

<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- کدگذارها به‌طور پیش‌فرض نوع اختصاص داده می‌شوند
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!--کنترل سطح لاگ -->
  <root level="warn"> <!-- info، debug، warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

اگر در خط ۱۲ بالا، [info] را با [warn] جایگزین کنیم، نتیجه زیر را خواهیم داشت:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.3.RELEASE)

---------------- Liste des beans Spring
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
springCore04Application
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.condition.BeanTypeRegistry
propertySourcesPlaceholderConfigurer
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
mbeanExporter
objectNamingStrategy
mbeanServer

لاگ‌ها ناپدید شده‌اند. تنها پیام‌های سطح [warn] نمایش داده می‌شوند و در اینجا هیچ پیامی وجود نداشت.

5.5.3. پیاده‌سازی لایه‌های مختلف معماری

اکنون سه لایه معماری نشان داده شده در بالا را پیاده‌سازی خواهیم کرد:

  

لایه [DAO] توسط بسته [spring.core.dao] پیاده‌سازی شده است. این بسته رابط زیر [IDao] را فراهم می‌کند:


package spring.core.dao;

public interface IDao {

    public int doSomethingInDaoLayer(int a, int b);
}

این رابط دو پیاده‌سازی دارد: [Dao1] و [Dao2]:


package spring.core.dao;

public class Dao1 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a+b;
    }

}

package spring.core.dao;

public class Dao2 implements IDao {

    public int doSomethingInDaoLayer(int a, int b) {
        return a-b;
    }

}

لایه [métier] توسط بسته [spring.core.metier] پیاده‌سازی شده است. این لایه دارای رابط زیر [IMetier] است:


package spring.core.metier;

public interface IMetier {

    public int doSomethingInMetierLayer(int a, int b);
}

این رابط دو پیاده‌سازی دارد: [Metier1] و [Metier2]:


package spring.core.metier;

import spring.core.dao.IDao;

public class Metier1 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a++;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }
}

package spring.core.metier;

import spring.core.dao.IDao;

public class Metier2 implements IMetier {

    private IDao dao;

    public int doSomethingInMetierLayer(int a, int b) {
        a--;
        b++;
        return dao.doSomethingInDaoLayer(a, b);
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }


}

لایه [UI] توسط بسته [spring.core.ui] پیاده‌سازی شده است. این لایه دارای رابط زیر [IUi] است:


package spring.core.ui;

public interface IUi {

    public int doSomethingInUiLayer(int a, int b);
}

این رابط دو پیاده‌سازی دارد: [Ui1] و [Ui2]:


package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui1 implements IUi {

    private IMetier metier;
    
    public int doSomethingInUiLayer(int a, int b) {
        a++;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

package spring.core.ui;

import spring.core.metier.IMetier;

public class Ui2 implements IUi {

    private IMetier metier;

    public int doSomethingInUiLayer(int a, int b) {
        a--;
        b++;
        return metier.doSomethingInMetierLayer(a, b);
    }

    public void setMetier(IMetier metier) {
        this.metier = metier;
    }

}

5.5.4. پیکربندی پروژه Spring

  

کلاس پیکربندی [Config] به شرح زیر است:


package spring.core.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.core.dao.Dao1;
import spring.core.dao.Dao2;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.metier.Metier1;
import spring.core.metier.Metier2;
import spring.core.ui.IUi;
import spring.core.ui.Ui1;
import spring.core.ui.Ui2;

@Configuration
public class Config {

    // -------------- پیاده‌سازی [Ui1, Metier1, Dao1]
    @Bean
    public IDao dao1() {
        return new Dao1();
    }

    @Bean
    public IMetier metier1(IDao dao1) {
        Metier1 metier = new Metier1();
        metier.setDao(dao1);
        return metier;
    }

    @Bean
    public IUi ui1(IMetier metier1) {
        Ui1 ui = new Ui1();
        ui.setMetier(metier1);
        return ui;
    }

    // -------------- پیاده‌سازی [Ui2, Metier2, Dao2]
    @Bean
    public IDao dao2() {
        return new Dao2();
    }

    @Bean
    public IMetier metier2(IDao dao2) {
        Metier2 metier = new Metier2();
        metier.setDao(dao2);
        return metier;
    }

    @Bean
    public IUi ui2(IMetier metier2) {
        Ui2 ui = new Ui2();
        ui.setMetier(metier2);
        return ui;
    }
}
  • خطوط ۲۰–۲۳: بین با نام [dao1] (نام متد) یک نمونه از کلاس [Dao1] (خط ۲۲) است، که به‌عنوان پیاده‌سازی رابط [IDao] در نظر گرفته می‌شود. (خط ۲۱). بنابراین، بین [dao1] به عنوان یک نمونه از یک رابط (اصطلاح نادرست است اما منظور روشن است) در نظر گرفته می‌شود، نه یک نمونه از یک کلاس. این یک نکته مهم برای درک است. تمام بین‌های دیگر نیز نمونه‌هایی از رابط‌ها خواهند بود؛
  • خطوط ۲۵–۳۰: یک نمونه از رابط [IMetier] که توسط کلاس [Metier1] پیاده‌سازی شده است؛
  • خطوط ۳۲–۳۷: یک نمونه از رابط [IUi] که توسط کلاس [Ui1] پیاده‌سازی شده است؛
  • خطوط ۲۰–۳۷: پیاده‌سازی لایه‌های [UI, Metier, DAO] با نمونه‌هایی از [Ui1, Metier1, Dao1
  • خطوط ۴۰–۵۷: لایه‌های [UI, Metier, DAO] را با نمونه‌هایی از [Ui2, Metier2, Dao2] پیاده‌سازی کنید؛

5.5.5. آزمون واحد [JUnitTest]

  

کلاس [JUnitTest] در شاخه [src / test / java] پروژه Maven قرار دارد. عناصر این شاخه در آرشیو نهایی پروژه گنجانده نمی‌شوند. کد آن به شرح زیر است:


package spring.core.tests;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import spring.core.config.Config;
import spring.core.dao.IDao;
import spring.core.metier.IMetier;
import spring.core.ui.IUi;

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {
...
}
  • خط ۱۶: آناوتیشن [@SpringApplicationConfiguration] یک آناوتیشن از پروژه Spring Boot Test (خط ۸) است. این آناوتیشن توسط وابستگی زیر در فایل [pom.xml] معرفی شده است:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
</dependency>

این آنوتیشن به عنوان پارامتر خود، لیست کلاس‌های پیکربندی را که برای ساخت کانکست Spring مورد نیاز برای تست استفاده می‌شوند، می‌پذیرد. در اینجا ما از کلاس پیکربندی [Config] که قبلاً معرفی شده است، استفاده می‌کنیم؛

  • خط 17: آناوتیشن [@RunWith] یک آناوتیشن از JUnit (خط 5) است. پارامتر آن کلاسی است که مسئول اجرای تست‌ها است، به جای کلاس پیش‌فرض ارائه‌شده توسط فریم‌ورک JUnit. این کلاس یک کلاس Spring است (خط ۹). این کلاس از anotationهای Spring موجود در کلاس تست استفاده خواهد کرد؛

کلاس کامل به شرح زیر است


...

@SpringApplicationConfiguration(classes = { Config.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTest {

    // لایه [UI]
    @Autowired
    @Qualifier("ui1")
    private IUi ui1;

    @Autowired
    @Qualifier("ui2")
    private IUi ui2;

    // layer [métier]
    @Autowired
    @Qualifier("metier1")
    private IMetier metier1;

    @Autowired
    @Qualifier("metier2")
    private IMetier metier2;

    //لایه [dao]
    @Autowired
    @Qualifier("dao1")
    private IDao dao1;

    @Autowired
    @Qualifier("dao2")
    private IDao dao2;

    @Test
    public void testDao() {
        Assert.assertEquals(30, dao1.doSomethingInDaoLayer(10, 20));
        Assert.assertEquals(-10, dao2.doSomethingInDaoLayer(10, 20));
    }

    @Test
    public void testMetier() {
        Assert.assertEquals(32, metier1.doSomethingInMetierLayer(10, 20));
        Assert.assertEquals(-12, metier2.doSomethingInMetierLayer(10, 20));
    }

    @Test
    public void testUI() {
        Assert.assertEquals(34, ui1.doSomethingInUiLayer(10, 20));
        Assert.assertEquals(-14, ui2.doSomethingInUiLayer(10, 20));
    }

}
  • خطوط ۸–۱۰: ما (در خط ۸) بیانی به نام [ui1] (در خط ۹) را تزریق می‌کنیم. توجه کنید که در خط ۱۰ ما یک نمونه از یک رابط را به جای یک نمونه از یک کلاس تزریق می‌کنیم؛
  • خطوط 21–32: سایر بی́n‌های تعریف‌شده در کلاس [Config] به همین شکل تزریق می‌شوند؛
  • خط ۳۴: آناوتیشن [@Test] متدی را برای اجرا در حین تست مشخص می‌کند. سایر آناوتیشن‌های ممکن به شرح زیر هستند:
    • [@BeforeClass]: متدی که قبل از شروع تست‌ها اجرا می‌شود؛
    • [@AfterClass]: متدی که پس از اتمام همه تست‌ها اجرا می‌شود؛
    • [@Before]: متدی که باید قبل از هر آزمون اجرا شود؛
    • [@After]: متدی که پس از هر آزمون اجرا می‌شود؛
  • خط ۳۶: ما بررسی می‌کنیم که فراخوانی [dao1.doSomethingInDaoLayer(10, 20)] واقعاً مقدار ۳۰ را برمی‌گرداند. طبق کنوانسیون، پارامتر اول مقدار مورد انتظار و پارامتر دوم مقدار واقعی است؛
  • خط ۳۶: نمونه [dao1] از رابط [IDao] را آزمایش می‌کند؛
  • خط ۳۷: نمونه [dao2] از رابط [IDao] را آزمایش می‌کند؛
  • خط ۴۲: نمونه [metier1] از رابط [IMetier] را آزمایش می‌کند؛
  • خط ۴۳: نمونه [metier2] از رابط [IMetier] را آزمایش می‌کند؛
  • خط ۴۸: مورد [ui1] از رابط [IUi] را آزمایش می‌کند؛
  • خط ۳۶: مورد آزمایش قرار می‌دهد نمونه [ui2] از رابط [IUi];

تأییدهایی که می‌توان در یک متد تست استفاده کرد به شرح زیر است:

  • assertEquals(expression1, expression2): بررسی می‌کند که مقادیر دو عبارت برابر هستند. انواع زیادی از عبارت‌ها پذیرفته می‌شوند (int, String, float, double, boolean, char, short). اگر دو عبارت برابر نباشند، یک استثنا از نوع [AssertionFailedError ] پرتاب می‌شود،
  • assertEquals(real1, real2, delta): بررسی می‌کند که دو عدد حقیقی تا فاصله δ برابر هستند، یعنی c.a.d abs(real1 - real2) <= δ. برای مثال، می‌توان assertEquals(real1, real2, 1E-6) را نوشت تا بررسی شود که دو مقدار تا فاصله ۱۰⁻⁶ برابر هستند،
  • `assertEquals(message, expression1, expression2)` و `assertEquals(message, real1, real2, delta)` گونه‌هایی هستند که به شما امکان می‌دهند پیام خطایی را که با استثنای [AssertionFailedError] پرتاب‌شده هنگام شکست متد [assertEquals] مرتبط است، مشخص کنید،
  • assertNotNull(Object) و assertNotNull(message, Object): بررسی می‌کند که مرجع Object null نباشد،
  • assertNull(Object) و assertNull(message, Object): بررسی می‌کند که مرجع Object برابر null باشد،
  • assertSame(Object1, Object2) و assertSame(message, Object1, Object2): بررسی می‌کند که ارجاعات Object1 و Object2 به یک شیء اشاره می‌کنند،
  • assertNotSame(Object1, Object2) و assertNotSame(message, Object1, Object2): بررسی می‌کند که ارجاعات Object1 و Object2 به یک شیء یکسان اشاره نمی‌کنند؛

برای اجرای تست، مراحل زیر را دنبال کنید:

نتیجه زیر به دست می‌آید:

 

در اینجا، همه تست‌ها با موفقیت انجام شده‌اند. این مثال چه چیزی را نشان می‌دهد؟ این مثال انعطاف‌پذیری ارائه شده توسط فریم‌ورک Spring را هنگام پیکربندی یک معماری لایه‌ای نشان می‌دهد. شما می‌توانید به سادگی با پیکربندی آن، پیاده‌سازی [Ui1, Metier1, Dao1] یا [Ui2, Metier2, Dao2] را انتخاب کنید. بنابراین، در تست قبلی JUnit، اگر فقط تزریق بی́n‌های [ui1, metier1, dao1] را حفظ کنیم، در حال کار با معماری اول هستیم. برای تغییر معماری، کافی است بی́n‌های تزریق‌شده را تغییر دهیم. این کار را می‌توان بدون تغییر کد لایه‌هایی که رابط‌ها را پیاده‌سازی می‌کنند، انجام داد. این نوع برنامه‌نویسی به عنوان برنامه‌نویسی مبتنی بر رابط شناخته می‌شود، زیرا ما از نمونه‌های کلاس‌های پیاده‌ساز لایه‌ها استفاده نمی‌کنیم، بلکه از نمونه‌های رابط‌های آن‌ها استفاده می‌کنیم.

5.6. Conclusion

  • اسپرینگ اشیایی را مدیریت می‌کند که از نوع تک‌نسخه (singleton) هستند (یک نمونه واحد). اسپرینگ همچنین اشیایی را مدیریت می‌کند که هر بار که یک نمونه از آن درخواست می‌شود، ایجاد می‌گردند. این سناریو نیز در این سند پوشش داده خواهد شد؛
  • این اشیاء را می‌توان به روش‌های مختلفی اعلام کرد که می‌توان آن‌ها را با هم ترکیب کرد:
    • در یک فایل XML،
    • در یک کلاس جاوا که با [@Configuration] انوتیت شده است،
    • با هر کلاس جاوایی که با [@Component, @Service, ...] انوتیت شده باشد؛
  • می‌توان یک شی Spring را با استفاده از آناوتیشن [@Autowired] به درون یک شی Spring دیگر تزریق کرد. این روش به عنوان تزریق وابستگی (Dependency Injection) شناخته می‌شود (DI: Dependency Injection);
  • اسپرینگ هنگامی که همراه با پارادایم برنامه‌نویسی مبتنی بر رابط استفاده شود، برای پیکربندی معماری‌های لایه‌ای بسیار مفید است؛