Spring Context Nedir? Bean Nasıl Oluşturulur ve Kullanılır?
22 min read

Spring Context Nedir? Bean Nasıl Oluşturulur ve Kullanılır?

Spring Context, Application Context veya IoC Container olarak tanımlanan yapıya nasıl bean ekleyebileceğimizi anlatıyoruz.
Spring Context Nedir? Bean Nasıl Oluşturulur ve Kullanılır?
Photo by Milada Vigerova / Unsplash

Spring Context, Application Context veya Spring IoC container olarakta adlandırılır. Spring Context, uygulamamızın object instance'larını framework'ün yönetebilmesi için depoladığımız alandır.

Varsayılan olarak Spring, uygulamanızda tanımladığınız nesnelerin hiçbirini bilmez. Spring'in nesnelerinizi görmesini sağlamak için onları context içine eklemeniz gerekir. IoC container veya application context olarakta adlandırılan Spring context'in içine eklenen objeler, context tarafından ilklendirilir, yönetilir, configure edilir ve gerektiğinde çağrılır. Bu şekilde, frameworkün sunduğu yetenekleri kullanmanıza izin verir.

Spring context'in ne olduğunu ve nasıl çalıştığını öğrenmek, Spring'i kullanmayı öğrenmenin ilk adımıdır, çünkü Spring context'i nasıl yöneteceğinizi bilmeden, onunla yapmayı öğreneceğiniz neredeyse hiçbir şey mümkün olmayacaktır.

1. Spring Context Nasıl Oluşturulur?

Spring Context'in ne olduğunu öğrendikten sonra nasıl oluşturabileceğimize bakalım. Spring, farklı gereksinimlere uygun farklı ApplicationContext implementasyonları sunar. Tüm bu implementasyonlar, ApplicationContext interfece'den türemektedir. Öyleyse, bazı yaygın ApplicationContext türlerini listeleyelim:

  1. AnnotationConfigApplicationContext
  2. AnnotationConfigWebApplicationContext
  3. XmlWebApplicationContext
  4. FileSystemXMLApplicationContext
  5. ClassPathXmlApplicationContext

Diğer tüm ApplicationCotext implementasyonlarını aşağıdaki javadoc sayfasına giderek inceleyebilirsiniz:

ApplicationContext (Spring Framework 5.3.16 API)

En yaygın kullanılan ApplicationContext implementasyonlarını inceleyerek nasıl kullanabileceğinizi kısa kod örnekleriyle gösterelim.

1.1 AnnotationConfigApplicationContext

AnnotationConfigApplicationContext classı Spring 3.0 ile birlikte ekosisteme dahil oldu. Input olarak @Configuration, @Component ve JSR-330 metadata ile annotated edilmiş sınıfları alır.

Güncel olarak en yaygın kullanılan Application Context oluşturma methodumuz budur. Yazımızın ilerleyen bölümlerinde de bu implementasyonu kullanarak contextimizi oluşturacağız. Ama şimdilik küçük bir örnek verelim.

ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
AccountService accountService = context.getBean(UserService.class);

1.2 AnnotationConfigWebApplicationContext

AnnotationConfigWebApplicationContext classı, AnnotationConfigApplicationContext'in web tabanlı bir varyasyonudur. Spring'in ContextLoaderListener servlet listener'ını veya bir web.xml dosyasındaki Spring MVC DispatcherServlet'i yapılandırırken bu sınıfı kullanabiliriz.

Ayrıca, Spring 3.0'dan itibaren, bu application context konteynerini programatik olarak da yapılandırabiliriz. Tek yapmamız gereken WebApplicationInitializer interface'ini implemente etmek.

public class MyWebApplicationInitializer implements WebApplicationInitializer {

  public void onStartup(ServletContext container) throws ServletException {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    context.register(AccountConfig.class);
    context.setServletContext(container);

    // servlet configuration
  }
}

1.3 XmlWebApplicationContext

XmlWebApplicationContext classını, XML tabanlı konfigürasyon kullandığınız web uygulamalarında kullanabilirsiniz. Aslında, bu contexti yapılandırmak yalnızca AnnotationConfigWebApplicationContext sınıfı gibidir. Yani, onu web.xml'de yapılandırabileceğiniz veya WebApplicationInitializer interface'ini implemente edebileceğiniz anlamına gelir:

public class MyXmlWebApplicationInitializer implements WebApplicationInitializer {

  public void onStartup(ServletContext container) throws ServletException {
    XmlWebApplicationContext context = new XmlWebApplicationContext();
    context.setConfigLocation("/WEB-INF/spring/applicationContext.xml");
    context.setServletContext(container);

    // Servlet configuration
  }
}

1.4 FileSystemXMLApplicationContext

FileSystemXMLApplicationContext classını, dosya sisteminden veya bir URL den XML tabanlı Spring konfigürasyonunuzu yükleyeceğiniz zaman kullanabilirsiniz. Bu sınıf, ApplicationContext'i programatik olarak yüklemeniz gerektiğinde kullanışlıdır. Genel olarak, test koşum ve standalone uygulamalar bunun için olası kullanım durumlarından bazılarıdır.

String path = "C:/myProject/src/main/resources/applicationcontext/account-bean-config.xml";

ApplicationContext context = new FileSystemXmlApplicationContext(path);
AccountService accountService = context.getBean("accountService", AccountService.class);

1.5 ClassPathXmlApplicationContext

ClassPathXmlApplicationContext classını, classpath'inizde yer alan XML tabanlı Spring konfigürasyonunuzu yükleyeceğiniz zaman kullanabilirsiniz. Tıpkı FileSystemXMLApplicationContext gibi bu da test koşum ve standalone uygulamalar oldukça kullanışlıdır.

ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext/bean-config.xml");
AccountService accountService = context.getBean("accountService", AccountService.class);

Tüm bu küçük örneklerle Spring Context veya diğer adlarıyla Application Context, IoC Container'larınızı oluşturabilirsiniz. Tercih ettiğiniz implementasyonla contexti oluşturduğunuzda içi boş bir (içini daha sonra dolduracağız) alan elde etmiş olursunuz.

Şekil 1: Application Context veya Spring Context veya IoC Container

Artık yavaştan Bean konusuna geçebiliriz.

2. Bean Nedir?

Bean, Spring context içine eklediğimiz object instance'larının her birine verilen isimdir. Kısaca ve en anlaşılır şekilde tekrar ifade edersek, context içinde yer alan her bir nesne bean olarak adlandırılır.

Şekil 2: Spring Context ve Bean'ler

Spring context ve bean'leri tam olarak bu şekilde hayal edebilirsiniz. Bu sizin Spring'i öğrenme sürecinizi kolaylaştıracaktır.

3. Spring Context'e Nasıl Bean Eklenir

Bu bölümde, Spring Context'e yeni nesne örneklerinin (object instances) yani beanlerin nasıl ekleneceğini öğreneceksiniz. Spring context'e bean eklemenin birden fazla yolu vardır. Bu yöntemleri şu şekilde sıralayabiliriz:

  • XML konfigürasyonu yardımıyla
  • @Bean anotasyonunu kullanarak
  • Stereotype (@Component, @Repository, @Service and @Controller) anotasyonlarını kullanarak
  • Programatik yöntemle

Tüm bu yöntemleri tek tek inceleyeceğiz. Fakat öncesinde Spring Framework Projesi Nasıl Oluşturulur? başlıklı bir önceki makalemizde yer alan adımları "4. XML ile ApplicationContext Oluşturmak" başlığına kadar (dahil değil) uygulayınız ve basit bir Spring Framework projesi oluşturunuz.

Projemizi oluşturduğumuzda henüz hiçbir context'e ya da bean'e sahip değiliz. Şimdi sırayla tüm yöntemleri kullanarak bean lerimizi contexte ekleyelim.

3.1 XML Konfigürasyonu İle Bean Ekleme

XML konfigürasyonu ile bean oluşturma yöntemi eski bir metoddur fakat değinmeden geçmeyeceğiz. Tabi bu yöntemi kullanabilmek için öncelikle  XmlWebApplicationContext, FileSystemXMLApplicationContext veya ClassPathXmlApplicationContext classlarınından birini kullanarak bir context oluşturalım.

Kolaylık olması amacıyla XML dosyamız resource dizini altında yer alıyor.

public class Main {
 
  public static void main(String[] args) {
    ApplicationContext context = new
                  ClassPathXmlApplicationContext( "beans.xml" );
  }
}

Bu işlemden sonra Şekil 1 deki gibi bir context uygulamamızın hafızasında oluşmuş oldu. Artık sıra nesnelerimizi yani bean'lerimizi bu context'e ekleme zamanı geldi.

Eğer siz nesnemizi oluşturduğumuz User isimli POJO class'tan new'leyerek kendiniz oluşturursanız context'in bu nesneden haberi olmaz. Nasıl yani?

public class Main {
 
  public static void main(String[] args) {
    ApplicationContext context = new
                  ClassPathXmlApplicationContext( "beans.xml" );
 
    User user = new User();
  }
}

Yukarıdaki örnekte olduğu gibi contexti oluşturur ve yeni bir nesneyi new'lerseniz durum aynı aşağıdaki şekildeki gibi olur.

Şekil 3: User nesnesi context dışında yer alıyor.

User classımızdan ürettiğimiz user nesnesi context dışında oluştuğu için context bu nesneden haberdar olamaz ve yönetemez. Eğer Spring Context'in nesnelerimizi yönetmesini istiyorsak mutlaka context içine eklemeliyiz.

XML tabanlı konfigürasyonlarda Context içine nesne ekleyebilmek için bu örnekte ClassPathXmlApplicationContext classına argüman olarak verdiğimiz beans.xml isimli dosyamızı şu şekilde düzenleyebiliriz.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="model.User"/>
</beans>

Ve artık kendiniz new anahtar kelimesini kullanarak nesne üretmemelisiniz. Yani Main classımız da şu şekilde gözükmeli.

public class Main {
 
  public static void main(String[] args) {
    ApplicationContext context = new
                  ClassPathXmlApplicationContext( "beans.xml" );
  }
}

Spring ayağa kalkarken bu XML konfigürasyonunu okuyacak ve bize class tagi içinde belirttiğimiz classtan id ile belirttiğimiz kimlikle bir nesne üretip context'e ekleyecektir. Son durumda aşağıdaki şemadaki görüntü oluşacaktır.

Şekil 4: user nesnesi context içine yerleşir ve contextin kontrolüne verilir.

Artık context'in yönetimine verdiğimiz nesneleri dilediğiniz yerde rahatlıkla çağırabilirsiniz. Örnek bir çağrım kodunu görelim.

User user1 = context.getBean(User.class);

Böylece istediğiniz classın objesini dilediğiniz yerden context içinden alarak kullanabilirsiniz.

NOTLAR:

XML ile beanlerinizi oluşturup contexte eklerken dikkat etmeniz gereken hususlar var. Bunları liste halinde paylaşalım.

1- Eğer XML konfigürasyonunda aynı type da birden fazla bean oluşturmuşsanız, type belirterek herhangi bir beani contextten alamazsınız. Çünkü context hangi beani döndüreceğine karar veremez.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user1" class="model.User"/>
    <bean id="user2" class="model.User"/>
</beans>
User user = context.getBean(User.class);

Bu durumda şu hatayı alırsınız.

Şekil 5: context hatası - 1

Hata mesajındanda anlaşılacağı üzere hangi doğru beani seçebilmek için sadece type yeterli değildir. İstediğimiz beani daha net belirtmeliyiz. Bunun için getBean() fonksiyonuna ilgili beanin id sini geçebilirsiniz.

User user = context.getBean("user1", User.class);

veya XML konfigürasyonunda aynı type daki beanlarden istediğinizi Primary işaretleyebilirsiniz.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user1" class="model.User" primary="true"/>
    <bean id="user2" class="model.User"/>
</beans>

2- getBean() fonksiyonunda type belirttiğinizde nesnenizi cast etmenize gerek kalmaz.

User user = context.getBean("user1", User.class);

Fakat type belirtmeden direkt id ile aldığınız bean'leri cast etmek zorundasınız.

User user = (User) context.getBean("user1");

3- Beanlerinizi oluştururken field'larına default value'leri rahatlıkla XML konfigürasyonunuz sayesinde geçebilirsiniz. Eğer default valueleri belirtmezseniz bean'in tüm field'ları null kalacaktır.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="model.User">
        <property name="name" value="Kerteriz Blog" />
    </bean>
</beans>
User user = (User) context.getBean("user");

System.out.println(user.getName()); // Kerteriz Blog

4- XML konfigürasyonu çok fazla kullanım şekline ve detayına sahip. Bu nedenle ana konudan uzaklaşmamak amacıyla burada kesiyoruz. İleride daha detaylı bir yazıda tekrar bu konuya geleceğiz.


3.2 @Bean Anotasyonunu İle Bean Ekleme

Bu bölümde, @Bean anotasyonunu kullanarak Spring context'e bir nesne örneği eklemeyi göstereceğiz. Ardından projenizde tanımlanan sınıfların nesnelerini (bizim durumumuzdaki User gibi) ve kendi oluşturmadığınız ancak uygulamanızda kullandığınız sınıfları eklemeniz mümkün olacaktır.

Spring context'e bean eklemeyi öğrenmenizin sebebinin, Spring'in yalnızca onun parçası olan nesneleri yönetebilmesi olduğunu unutmayın. İlk olarak, @Bean anotasyonunu kullanarak Spring context'e nasıl bean ekleneceğine dair basit bir örnek yapalım.

@Bean anotasyonunu kullanarak Spring  context'e bir fasulye eklemek için izlemeniz gereken adımlar aşağıdaki gibidir:

  1. Projenize Spring context'i yapılandırmak için kullanacağımız ve @Configuration anotasyonu ile işaretlenmiş bir yapılandırma sınıfı tanımlayın.
  2. Context'e eklemek istediğiniz nesneyi döndürenmetodu yapılandırma sınıfına ekleyin ve yönteme @Bean anotasyonuyla bu metodu işaretleyin.
  3. Spring'in 1. adımda tanımlanan konfigürasyon sınıfını kullanmasını sağlayın.
Şekil 6: @Configuration ve @Bean anotasyonları ile context'e bean ekleme

Şimdi tüm bu adımları daha detaylıca inceleyelim.

1️⃣. Adım: Projeye Konfigürasyon Sınıfı Tanımlama

İlk adım, projede bir konfigürasyon sınıfı oluşturmaktır. Bir Spring konfigürasyon sınıfı, @Configuration anotasyonuyla işaretlenir. Proje için Spring ile ilgili çeşitli konfigürasyonları tanımlamak için konfigürasyon sınıflarını kullanırız. İlerleyen yazılarda, konfigürasyon sınıflarını kullanarak yapılandırabileceğiniz farklı şeyler öğreneceksiniz. Şu an için yalnızca Spring context'e yeni örnekler eklemeye odaklanıyoruz.

@Configuration                   ❶
public class ProjectConfig {
}

❶ - Bu sınıfı bir Spring konfigürasyon sınıfı olarak tanımlamak için @Configuration anotasyonunu kullanıyoruz.

Bu basit kod bloğu, yapılandırma sınıfını nasıl tanımlayacağınızı gösterir. Bu yapılandırma sınıfına ProjectConfig adını verdim ve düzenli bir proje için yapılandırma sınıfını config isimli paket altına koyduk.

️2️⃣. Adım: Bean Döndürecek Metodu Oluşturmak ve @Bean Anotasyonuyla İşaretlemek

Bir konfigürasyon sınıfıyla yapabileceğiniz şeylerden biri, Spring context'e bean eklemektir. Bunu yapmak için, context'e eklemek istediğimiz nesne örneğini döndüren bir yöntem tanımlamamız ve bu yönteme @Bean anotasyonunu eklememiz gerekir; bu, Spring'in context'i başlattığında ve beanleri eklerken bu yöntemi çağırması gerektiğini bilmesini sağlar.

@Configuration
public class ProjectConfig {
 
  @Bean                        ❶
  User user() {
    User user = new User();
    user.setName("Kerteriz Blog");         ❷
    return user;                  ❸
  }
}

@Bean anotasyonu ekleyerek, Spring'e context başlatma sırasında bu yöntemi çağırması ve döndürülen değeri context'e eklemesi talimatını veriyoruz.

❷ Uygulamayı daha sonra test edeceğimiz zaman ilgili bean'i çağırabilmek için bean'e bir isim (id) veriyoruz.

❸ Spring, yöntemin döndürdüğü User objesini context'e ekler.

Metod/yöntem/fonksiyon için kullandığımız ismin fiil içermediğine dikkat edin. Metodlar genellikle eylemleri temsil ettiğinden, Java'nın best practice uygulamasının fiilleri yöntem adlarına koymak olduğunu öğrenmişsinizdir. Ancak Spring context'e bean eklemek için kullandığımız yöntemler için bu kuralı (convention) takip etmiyoruz. Bu tür yöntemler, döndürdükleri nesne örneklerini temsil eder ve dönen nesne Spring context'in bir parçası olur. Yöntemin adı da bean'in adı olur. Örneğin burada bean'in adı artık "user" oldu. Kısaca kural olarak, metod adlarını sınıf adıyla aynı isim olarak belirleyebilirsiniz.

3️⃣. Adım: Yeni Oluşturulan Spring Konfigürasyon Sınıfını Kullanarak Spring Context'i Başlatmak

Bean olması gereken nesneleri Spring'e söylediğimiz bir yapılandırma sınıfı implemente ettik. Şimdi, context'i başlatırken Spring'in bu yapılandırma sınıfını kullandığından emin olmamız gerekiyor.

public class Main {
 
  public static void main(String[] args) {
    var context = 
      new AnnotationConfigApplicationContext(
            ProjectConfig.class);                ❶
  }
}

❶ Spring context objesi oluştururken, Spring'e onu kullanmasını talimat vermek için konfigürasyon sınıfını bir parametre olarak gönderiyoruz.

User objesinin gerçekten de şu anda context'in bir parçası olduğunu doğrulamak için, objeye başvurabilir ve adını aşağıdaki listede gösterildiği gibi konsolda yazdırabilirsiniz.

public class Main {
 
  public static void main(String[] args) {
    var context = 
      new AnnotationConfigApplicationContext(
        ProjectConfig.class);
 
    User u = context.getBean(User.class);    ❶
 
    System.out.println(u.getName());
  }
}

❶ Spring context'inden User tipinde bir bean referansı alır.

Şimdi context içinde eklediğiniz user objesine verdiğiniz ismi konsolda göreceksiniz ve benim örneğimde "Kerteriz Blog" olacaktır..


Yaptığımız örnekte göreceğiniz üzere Spring Context'e herhangi bir tipte nesneyi kolaylıkla ekleyebiliriz. Örneğin String ve Integer tipinde obje ekleyeceğimiz bir örnek daha yapalım.

Şekil 7: Spring Context içine farklı tipte nesneler ekleyeceğiz.

Aşağıdaki kod ile, konfigürasyon sınıfına String ve Integer tipinde bir bean ekleyebiliriz.

@Configuration
public class ProjectConfig {
 
  @Bean
  User user() {
    User user = new User();
    user.setName("Kerteriz Blog");
    return user;
  }
 
  @Bean                  ❶
  String hello() {
    return "Hello";
  }
 
  @Bean                  ❷
  Integer ten() {
    return 10;
  }
}

❶ Spring context'e "Hello" değerli String objesini ekler

❷ Spring context'e 10 değerli Integer objesini ekler

Şimdi bu iki yeni bean'e user objesinde yaptığımız gibi başvurabilirsiniz. Sonraki kod bloğu, yeni bean'lerin değerlerini yazdırmak için main metodu nasıl değiştireceğinizi gösterir.

public class Main {
 
  public static void main(String[] args) {
    var context = new AnnotationConfigApplicationContext(
                   ProjectConfig.class);
 
    User user = context.getBean(User.class);    ❶
    System.out.println(user.getName());
 
    String s = context.getBean(String.class);
    System.out.println(s);
 
    Integer n = context.getBean(Integer.class);
    System.out.println(n);
  }
}

❶ Burada herhangi bir casting işlemi yapmanız gerekmez. Spring, context içinde istediğiniz türde bir bean arar. Böyle bir beanyoksa, Spring bir exception atar.

Uygulamayı şimdi çalıştırdığımızda, sonraki kod parçacığında gösterildiği gibi, üç bean'in değerleri konsolda yazdırılacaktır.

Kerteriz Blog
Hello
10

Şimdiye kadar Spring context'e bir veya daha fazla farklı tipte bean ekledik. Peki aynı tipten birden fazla nesne ekleyebilir miyiz? Evet ise, bu nesnelere bireysel olarak nasıl başvurabiliriz? Hadi şimdi aynı tipten birden fazla bean'i Spring context'e nasıl ekleyebileceğimizi ve daha sonra bunlara nasıl başvurabileceğimizi gösterelim.

Şekil 8: Aynı tipteki objeleri Spring context içine ekleyeceğiz.

@Bean anotasyonu ile aynı tipten birden fazla nesneyi Spring context'e bean olarak ekleyebilirsiniz. Fakat bunu yapabilmek için her objenin benzersiz bir tanımlayıcısı (identifier) olması gerekir.

NOT: Bean'in adını user objesindeki name field'i ile karıştırmayın. Örneğimizde, Spring context içindeki bean adları (veya tanımlayıcıları) user1, user2 ve user3'tür (bunları tanımlayan @Bean yöntemlerinin adı gibi). User'lara verdiğim isimler ise Ali, Veli ve Mehmet'tir.

@Configuration
public class ProjectConfig {
 
  @Bean
  User user1() {
    var user = new User();
    user.setName("Ali");
    return user;
  }
 
  @Bean
  User user2() {
    var user = new User();
    user.setName("Veli");
    return user;
  }
 
  @Bean
  User user3() {
    var user = new User();
    user.setName("Mehmet");
    return user;
  }
}

Tabii ki, artık sadece tipini belirterek beanleri context'ten alamazsınız. Bunu yaparsanız, bir exception alırsınız çünkü Spring, hangi objeye başvurduğunuzu bildirdiğinizi tahmin edemez. Aşağıdaki koda bakın. Böyle bir kodu çalıştırmak, Spring'in size daha net olmanız gerektiğini söylediği bir exception fırlatır.

public class Main {
 
  public static void main(String[] args) {
    var context = new 
      AnnotationConfigApplicationContext(ProjectConfig.class);
 
    User u = context.getBean(User.class);    ❶
 
    System.out.println(u.getName());
 
  }
}

❶ Bu satırda bir exception alacaksınız çünkü Spring, üç User objesinden hangisinden bahsettiğinizi tahmin edemez.

Uygulamanızı çalıştırırken, bir sonraki kod parçacığı gibi bir exception alacaksınız.

Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'main.User' available: expected single matching bean but found 3: 
     user1,user2,user3    ❶
     at ...

❶ Context içindeki User beanlerinin isimleri

Bu belirsizlik sorununu çözmek için, bean adını kullanarak objelerden birine tam olarak başvurmanız gerekir. Varsayılan olarak Spring, bean isimleri olarak @Bean ile anoted edilmiş metotların isimlerini kullanır. @Bean ile işaretlenmiş motd isimlerinde fiilleri kullanarak adlandırmamamızın sebebinin bu olduğunu unutmayın. Bizim örneğimizde, benaler user1, user2 ve user3 isimlerine sahiptir. Bu adları, exception mesajında önceki kod parçacığında bulabilirsiniz. Öyleyse şimdi main metodu, adını kullanarak bu beanlerden birine açıkça atıfta bulunacak şekilde değiştirelim. Aşağıdaki kodda user2 beaninden nasıl bahsettiğimi görebilirsiniz.

public class Main {
 
  public static void main(String[] args) {
    var context = new 
      AnnotationConfigApplicationContext(ProjectConfig.class);
    
    Parrot u = context.getBean("user2", User.class);    ❶
    System.out.println(u.getName());
 
  }
}

❶ İlk parametre, atıfta bulunduğumuz objenin adıdır.

Uygulamayı şimdi çalıştırdığınızda, artık bir exception almayacaksınız. Bunun yerine konsolda ikinci user olan Veli'nin adını göreceksiniz.

Bu sorunu çözmenin diğer bir yöntemi ise bean'lerden birini @Primary olarak işaretlemektir. Böylece sadece tip ile bean çağırdığınızda, aynı tipten birden fazla olan bean'lerden öncelikle @Primary olarak işaretlenen size getirilecektir.

@Bean
@Primary       ❶
User user2() {
  var user = new User();
  user.setName("Veli");
  return user;
}

❶ @Primary anotasyonu ile bean'i default olarak işaretleme

Son özellik olarak bean'e başka bir isim vermek isterseniz, @Bean anotasyonuna name veya value attribute'lerinden birini geçebilirsiniz. Aşağıdaki kodlardan herhangi biri, "Veli" bean'inin adını değiştirecektir:

@Bean(name = "veli")

@Bean(value = "veli")

@Bean("veli")

Kod üzerinde görelim.

@Bean(name = "veli")    ❶
Parrot user2() {
  var user = new User();
  user.setName("Veli");    ❷
  return user;
}

❶ Bean'inin adını belirler

❷ User'ın adını belirler


3.3 Stereotype Anotasyonları İle Bean Ekleme

Stereotype (@Component, @Repository, @Service and @Controller) anotasyonlarını kullanarak bean'lerimizi context'e ekleyebiliriz. Bu bölümde @Component anotasyonunu baz alarak örneklerimizi inceleyeceğiz. Çünkü diğer üç stereotype anotasyonu da @Component anotasyonundan türer.

Stereotype anotasyonlarıyla, Spring context içinde bir instance'ına sahip olmanız gereken sınıfın üstüne anotasyon eklersiniz. Bunu yaparak sınıfı component olarak işaretlediğinizi söylersiniz. Uygulama Spring context'i oluşturduğunda, Spring, component olarak işaretlediğiniz sınıfın bir instance'ını oluşturur ve bu instance'ı context'e ekler. Spring'e stereotype anotasyonlarıyla işaretlenmiş sınıfları nerede arayacağını söylemek için ise yine bir konfigürasyon sınıfına ihtiyacımız olacak. Bunun için, her iki yaklaşımı da (@Bean ve stereotype) aynı konfigürasyon sınıfı içinde kullanabilirsiniz.

Bu süreçte izlememiz gereken adımlar şu şekildedir:

  1. @Component anotasyonunu kullanarak, Spring'in kendi ceontext'ine bir instance eklemesini istediğiniz sınıfları işaretleyin (bizim örneğimizde User).
  2. Konfigürasyon sınıfı üzerinde @ComponentScan anotasyonu kullanarak, Spring'e işaretlediğiniz sınıfları nerede bulacağı konusunda bilgi verin.
Şekil 9: Stereotype anotasyonları ile Spring Context'e bean ekleme aşamaları

Stereotip anotasyonlarını kullanırken iki adımı göz önünde bulundurun. İlk olarak, Spring'in içeriğine bir bean eklemesini istediğiniz sınıfa anotasyon eklemek için stereotype anotasyonunu (@Component vs.) kullanın. İkinci olarak, Spring'e stereotype anotasyonlarıyla işaretlenmiş sınıfları nerede arayacağınızı söylemek için @ComponentScan anotasyonunu kullanın.

Hadi o zaman User sınıfı ile örneğimizi ele alalım. User sınıfına, örneğin @Component gibi stereotype anotasyonlarından biriyle anotasyon ekleyerek sınıfın bir örneğini Spring context'e ekleyelim.

@Component               ❶
public class User {
 
  private String name;
 
  public String getName() {
    return name;
  }
 
  public void setName(String name) {
    this.name = name;
  }
}

❶ Sınıf üzerinde @Component anotasyonu kullanarak, Spring'e bu sınıfın bir instance'ını oluşturmasını ve onu ceontext'e eklemesini söyleriz.

Fakat bir saniye! Bu kod henüz çalışmayacak. Varsayılan olarak Spring, stereotype anotasyonlu sınıfları aramaz, bu nedenle kodu olduğu gibi bırakırsak, Spring context'e User tipinde bir bean eklenmez. Spring'e stereotype anotasyonlu sınıfları araması gerektiğini söylemek için, konfigürasyon sınıfı üzerinde @ComponentScan anotasyonunu kullanıyoruz. Ayrıca @ComponentScan anotasyonu ile Spring'e bu sınıfları nerede arayacağını söylüyoruz.

@Configuration
@ComponentScan(basePackages = "main")     ❶
public class ProjectConfig {
 
}

❶ Anotasyonun basePackages attribute'ünü kullanarak, Spring'e stereotype anotasyonlu sınıfları hangi paket altında arayacağını söyledik.

Şimdi Spring'e şunları söylediniz:

  1. Context'e instance'ı eklenecek sınıfları (User)
  2. Bu sınıfları nerede bulabileceği (@ComponentScan kullanarak)

NOT: Bean'leri tanımlamak için artık metodlara ihtiyacımız yok. Ve şimdi bu yaklaşım daha iyi gibi görünüyor çünkü aynı şeyi daha az kod yazarak başarıyorsunuz. Ama bu bölümün sonuna kadar bekleyin. Senaryoya bağlı olarak her iki yaklaşımın da faydalı olduğunu öğreneceksiniz.

Spring'in bean'i oluşturduğunu ve kendi context'ine eklediğini kanıtlamak için main metodu aşağıdaki kodda gösterildiği gibi yazmaya devam edebilirsiniz.

public class Main {
 
  public static void main(String[] args) {
    var context = new 
      AnnotationConfigApplicationContext(ProjectConfig.class);
 
      User user = context.getBean(User.class);
 
      System.out.println(user);             ❶
      System.out.println(user.getName());   ❷
  }
}

❶ Spring bağlamından alınan instance'ın varsayılan String representation'ını yazdırır

❷ Context'e Spring tarafından eklenen user instance'ına herhangi bir name atamadığımız için null yazdırır

Bu uygulamayı çalıştırırken, Spring'in context'ine bir User örneği eklediğini göreceksiniz, çünkü yazdırılan ilk değer bu örneğin varsayılan String temsilidir. Ancak bu user'a herhangi bir name değeri atamadığımız için basılan ikinci değer null olur. Spring sadece sınıfın örneğini yaratır, ancak bu örneği daha sonra herhangi bir şekilde değiştirmek istersek (bir name atamak gibi) bu hala bizim görevimizdir.

Spring context'ine bean eklemenin en sık karşılaşılan iki yolunu ele aldığımıza göre, şimdi bunların kısa bir karşılaştırmasını yapalım.

@Bean anotasyonu kullanmak

Stereotype anotasyonları kullanmak

  1. Spring context'e eklediğiniz instance oluşturma süreci üzerinde tam kontrole sahip olursunuz. @Bean ile anoted edilen metdun gövdesindeki instance'ı oluşturmak ve yapılandırmak sizin sorumluluğunuzdadır. Spring yalnızca bu instance'ı alır ve onu olduğu gibi context'e ekler.

  2. Spring context'e aynı tipten birden fazla örnek eklemek için bu yöntemi kullanabilirsiniz.

  3. Spring context'e herhangi bir instance eklemek için @Bean anotasyonunu kullanabilirsiniz. Instance'ı tanımlayan sınıfın uygulamanızda tanımlanması gerekmez. Hatırlayın, daha önce Spring bağlamına bir String ve Integer eklemiştik.

  4. Oluşturduğunuz her bean için ayrı bir metod yazmanız gerekiyor, böylece uygulamanıza boilerplate kod eklemiş oluyorsunuz. Bu nedenle projelerimizde stereotype anotasyonlara ikinci bir seçenek olarak @Bean kullanmayı tercih ediyoruz.

  1. Yalnızca framework bean'i oluşturduktan sonra instance üzerinde kontrole sahip olursunuz.

  2. Bu şekilde, context'e sınıfın yalnızca bir instance'ını ekleyebilirsiniz.

  3. Stereotype anotasyonlarını yalnızca uygulamanızın sahip olduğu sınıfların bean'lerini oluşturmak için kullanabilirsiniz. Örneğin, @Bean anotasyonuyla bölüm yaptığımız gibi String veya Integer türünde bir bean ekleyemezdiniz çünkü bir stereotype anotasyonu ekleyerek bunları değiştirmek için bu sınıflara sahip değilsiniz.

  4. Spring context'e bean eklemek için stereotype anotasyonlarını kullanmak, uygulamanıza boilerplate kod eklemez. Uygulamanıza ait sınıflar için genel olarak bu yaklaşımı tercih edebilirsiniz.

Gözlemleyeceğiniz şey, gerçek dünya senaryolarında mümkün olduğunca stereotype anotasyonları kullanacağınızdır (çünkü bu yaklaşım daha az kod yazmayı gerektirir) ve @Bean'i yalnızca başka türlü bean ekleyemediğinizde kullanacaksınız. (Örneğin, bir library'nin parçası olan sınıf için bean yaratırsınız, böylece o sınıfı stereotype anotasyon eklemek için değiştiremezsiniz).

BONUS: Tabi stereotype anotasyonları ile oluşturduğumuz bean'leri yönetemiyoruz anlamı çıkarmanızı istemeyiz. bunun için küçük bir bölüm daha işleyelim.

🟢 @PostConstruct İle Bean'leri Oluşturduktan Sonra Yönetmek

Bu bölümde tartıştığımız gibi, stereotype anotasyonları kullanarak Spring'e bir bean oluşturmasını ve onu context'ine eklemesini söyledik. Ancak @Bean anotasyonu kullanmanın aksine, instance oluşturma üzerinde tam kontrole sahip olamıyoruz. @Bean kullanarak Spring context'e eklediğimiz User instance'larının her biri için bir isim (identifier) tanımlayabildik, ancak @Component kullanarak Spring, User sınıfının constructor'ını çağırdıktan sonra bir şey yapma şansımız olmadı. Spring bean oluşturduktan hemen sonra bazı kodları uygulamak istersek ne yapmalıyız? Bunun için @PostConstruct anotasyonunu kullanabiliriz.

Spring, @PostConstruct ek açıklamasını Java EE'den ödünç alır. Bu açıklamayı Spring bean oluşturulduktan sonra Spring'in yürüttüğü bir dizi talimat belirtmek için Spring bean ile de kullanabiliriz. Yalnızca bileşen sınıfında bir yöntem tanımlamanız ve bu yöntemi, yapıcı yürütmeyi bitirdikten sonra Spring'e bu yöntemi çağırması talimatını veren @PostConstruct ile açıklamanız gerekir.

@PostConstruct anotasyonunu kullanmak için gereken Maven bağımlılığını pom.xml'e ekleyelim:

<dependency>
   <groupId>javax.annotation</groupId>
   <artifactId>javax.annotation-api</artifactId>
   <version>1.3.2</version>
</dependency>

NOT: Java 11'den daha küçük bir Java sürümü kullanıyorsanız bu bağımlılığı eklemeniz gerekmez. Java 11'den önce Java EE bağımlılıkları JDK'nın bir parçasıydı. Java 11 ile JDK, Java EE bağımlılıkları da dahil olmak üzere SE ile ilgili olmayan API'lerden temizlendi.

Kaldırılan API'lerin bir parçası olan işlevleri kullanmak istiyorsanız (@PostConstruct gibi), şimdi bağımlılığı açıkça uygulamanıza eklemeniz gerekir. Şimdi, sonraki kod parçacığında sunulduğu gibi User sınıfında bir yöntem tanımlayabilirsiniz:

@Component
public class User {
 
  private String name;
 
  @PostConstruct
  public void init() {
    this.name = "Kerteriz Blog";
  }
 
  // Omitted code
}

Çok benzer şekilde, ancak gerçek dünyadaki uygulamalarda daha az karşılaşıldığında, @PreDestroy adlı bir anotasyonda kullanabilirsiniz. Bu anotasyon ile, context'i kapatıp temizlemeden hemen önce Spring'in çağırdığı bir yöntem tanımlarsınız. @PreDestroy ek açıklaması ayrıca JSR-250'de açıklanmıştır ve Spring tarafından ödünç alınmıştır. Ancak genellikle geliştiricilerin bunu kullanmaktan kaçınmasını ve Spring context'i temizlemeden önce bir şeyi yürütmek için farklı bir yaklaşım bulmasını öneririm, çünkü Spring'in context'i temizlemede başarısız olmasını bekleyebilirsiniz. @PreDestroy yönteminde hassas bir şey (veritabanı bağlantısını kapatmak gibi) tanımladığınızı varsayalım; Spring metodu çağırmazsa, büyük sorunlarla karşılaşabilirsiniz.


3.4 Programatik Yöntemle Bean Ekleme

Bu bölümde, Spring context'e programatik olarak bean eklemeyi göstereceğiz. Spring 5 ile Spring context'e programatik olarak bean ekleme seçeneğimiz var, ve bu da büyük bir esneklik sunuyor çünkü context instance'ının bir metodunu çağırarak doğrudan context'e yeni bean'ler eklemenizi sağlıyor. Kısaca @Bean veya stereotype anotasyonları ihtiyaçlarınız için yeterli olmadığında Context'e bean eklemenin özel bir yolunu kullanmak için bu yaklaşımı kullanırsınız.

Uygulamanızın belirli yapılandırmalarına bağlı olarak Spring context'e belirli bean'leri kaydetmeniz gerektiğini varsayalım. @Bean ve stereotype anotasyonları ile senaryoların çoğunu uygulayabilirsiniz, ancak bir sonraki sunulan koddaki gibi bir şey yapamazsınız:

if (condition) {  
   registerBean(b1);    ❶
} else {
   registerBean(b2);    ❷
}

❶ Koşul doğruysa, Spring context'e belirli bir bean ekler.

❷ Aksi takdirde, Spring context'e başka bir bean ekler.

Şekil 10: registerBean kullanarak belirli bir koşula uyan instance'ları bean olarak ekleyebilirsiniz.

Spring context'e belirli nesne instance'ları eklemek için registerBean() yöntemini kullanabiliriz.

Hadi bu yöntemin nasıl çalıştığını görelim. Programatik bir yaklaşım kullanarak Spring context'e bir beaneklemek için, ApplicationContext instance'ının registerBean() yöntemini çağırmanız yeterlidir. registerBean(), sonraki kod parçacığında gösterildiği gibi dört parametreye sahiptir:

<T> void registerBean(
  String beanName, 
  Class<T> beanClass, 
  Supplier<T> supplier, 
  BeanDefinitionCustomizer... customizers);
  1. Spring context'e eklediğiniz bean'e bir ad tanımlamak için ilk beanName parametresini kullanın. Eklediğiniz bean'e bir isim vermeniz gerekmiyorsa, metodu çağırırken değer olarak null kullanabilirsiniz.
  2. İkinci parametre, context'e eklediğiniz bean'i tanımlayan sınıftır. User sınıfının bir instance'ını eklemek istediğinizde bu parametreye verdiğiniz değer User.class'tır.
  3. Üçüncü parametre, bir Supplier instance'ıdır. Bu Supplier implementasyonunun, context'e eklediğiniz instance'ın değerini döndürmesi gerekir. Supplier'ın, java.util .function paketinde bulduğunuz functional bir interface olduğunu unutmayın. Bir Supplier implementasyonunn amacı, parametre almadan tanımladığınız bir değeri döndürmektir.
  4. Dördüncü ve son parametre, BeanDefinitionCustomizer'ın bir değişkenidir. (Bu tanıdık gelmiyorsa, sorun değil; BeanDefinitionCustomizer, yalnızca bean'in farklı özelliklerini yapılandırmak için uyguladığınız bir interface'dir; örneğin, onu primary yapmak gibi.) Varargs türü olarak tanımlandığından, bu parametreyi tamamen atlayabilirsiniz veya ona BeanDefinitionCustomizer türünde daha fazla değer verebilirsiniz.

Şimdi @Bean veya @Component gibi herhangi bir anotasyon kullanmadan context'e nasıl bean ekleyebileceğimizi kod üzerinde görelim. Bunun için projenin main metodunda, Spring context'e User tipi bir instance eklemek için registerBean() yöntemini kullanmalıyız.

Şekil 11: registerBean() parametreleri

Kod üzerinde bir örnekte görelim:

public class Main {
 
  public static void main(String[] args) {
    var context = 
      new AnnotationConfigApplicationContext(
          ProjectConfig.class);
 
      User u = new User();                     ❶
      u.setName("Kerteriz Blog");
 
      Supplier<User> userSupplier = () -> u;   ❷
 
      context.registerBean("user1", 
        User.class, userSupplier);             ❸
 
      User user = context.getBean(User.class);    ❹
      System.out.println(user.getName());             ❹
  }
}

❶ Spring context'e eklemek istediğimiz instance'ı oluşturuyoruz.

❷ Bu instance'ı döndürmek için bir Supplier tanımlarız.

❸ Instance'ı Spring context'e eklemek için registerBean() yöntemini çağırırız.

❹ Bean'in context'te olduğunu doğrulamak için user bean'ine başvurur ve adını konsola yazdırırız.

Eklediğiniz bean'lerin farklı özelliklerini ayarlamak için son parametre olarak bir veya daha fazla bean yapılandırıcı instance'ı kullanabilirsiniz. Örneğin, sonraki kod parçacığında gösterildiği gibi registerBean() metod çağrısını değiştirerek bean'i primary yapabilirsiniz. Primary bean, context'te aynı tipten birden fazla bean varsa, Spring'in varsayılan olarak seçtiği instance'ı tanımlar:

context.registerBean("user1", 
                User.class, 
                userSupplier, 
                bc -> bc.setPrimary(true));

4. ÖZET

  • Spring'de öğrenmeniz gereken ilk şey, Spring context'e instance'ları (bean dediğimiz) eklemektir. Spring context'ini, Spring'in yönetmesini beklediğiniz instance'ları eklediğiniz bir kova olarak hayal edebilirsiniz. Spring, yalnızca context'e eklediğiniz örnekleri görebilir.
  • Bean'leri Spring context'e dört şekilde ekleyebilirsiniz: XML dosyasıyla, @Bean anotasyonunu kullanarak, stereotip anotasyonlarını kullanarak veya bunu programatik olarak yaparak.
  • Spring bağlamına örnekler eklemek için @Bean anotasyonunu kullanmak, herhangi bir tip nesne örneğini bir bean olarak ve hatta aynı tipten birden çok örneği Spring context'ine eklemenizi sağlar. Bu açıdan bakıldığında, bu yaklaşım, stereotype anotasyonları kullanmaktan daha esnektir. Yine de, context'e eklenen her bağımsız instance için yapılandırma sınıfında ayrı bir yöntem yazmanız gerektiğinden daha fazla kod yazmanızı gerektirir.
  • Stereotype anotasyonlarını kullanarak, yalnızca belirli bir anotasyon içeren uygulama sınıfları için bean'ler oluşturabilirsiniz (örneğin, @Component). Bu yapılandırma yaklaşımı, daha az kod yazmayı gerektirir ve bu da yapılandırmanızın daha rahat okunmasını sağlar. Tanımladığınız ve not ekleyebileceğiniz sınıflar için bu yaklaşımı @Bean anotasyonuna tercih edeceksiniz.
  • registerBean() yöntemini kullanmak, Spring context'e bean eklemek için özel logic tanımlamanıza imkan sağlar. Unutmayın, bu yaklaşımı yalnızca Spring 5 ve sonrasında kullanabilirsiniz.