Spring Framework Nedir? Kurulumu ve Kullanımı
Hızlıca Java tabanlı uygulamalar geliştirmek ve bağımlık yönetimi için kullanılan spring framework nedir, kurulumu ile kullanımı bilgileri yer alıyor.
Spring Framework nedir?
Java ile geliştirme yapmayı kolaylaştıran Core Container, AOP, Data Access, Web gibi modüllerden oluşan bir framework-kütüphanedir.
Spring platformunda yer alan Spring Boot, Spring Data, Spring MVC, Spring Batch, Spring Security gibi projelerin temelinde Spring framework yer alır.
Spring framework temelinde ise Spring Core modülünde yer alan IoC Container vardır.
IoC Container nedir?
Sınıflardan nesne oluşturmak, oluşan nesnelerin ihtiyaç duyduğu nesneleri bağlamak ve yönetmek için kullanılan prensip ve desenlerinin bir araya geldiği araçlardır.
IoC Container araçları genellikle aşağıdaki prensip ve desenleri kullanarak işlem yapar.
- IoC Container Framework
- IoC – Inversion of Control – Tasarım prensibi
- Factory Pattern
- Dependency Injection
- Constructor Injection
- Setter Injection
- Interface Injection
- Strategy Pattern
- Service Locator Pattern
- Template Method Pattern
- DIP – Dependency Inversion Principle – Tasarım prensibi
- IoC – Inversion of Control – Tasarım prensibi
NOT: Tasarım prensibleri (DRY, SOLID, SoC ,KISS, YAGNI) uyulması gereken kuralları belirtirken, tasarım desenleri bu kuralların uygulamalarıdır.
Spring framewok Inversion of Control prensibini uygulamak için genellikle Dependency Injection tasarım desenini kullanır.
IoC Inversion of Control nedir?
Bir sınıf içerisinde(A) kullanılan sınıfların(B, C vb.) başka bir yöntemle(Constructor, Factory vb.) oluşturulması, dahil edilmesi, kontrol edilmesine Inversion of Control denir.
OOP-NYP ile geliştirilen uygulamalarda sınıflar bir işlemi yapmak için diğer sınıfları kullanır.
Bir sınıfın(A) bir başka sınıfı(B) kullanabilmesi için new anahtar kelimesi ile oluşturulması gerekir.
public class A {
private B b;
public A() {
b = new B();
}
public void calistir() {
b.yazdir();
}
}
public class B {
public void yazdir() {
System.out.println("Yusuf Sezer");
}
}
Sınıf(A) içerisinde new anahtar kelimesi ile başka bir sınıfı(B) kullanması, sınıfın(A) diğer sınıfa(B) bağımlı olmasına neden olur.
public class App {
public static void main(String[] args) {
A a = new A();
a.calistir();
}
}
Bu bağımlılığın sınıftan(A) alınarak başka bir sınıfa verilmesi Kontrolün Tersine Çevrilmesi-Inversion of Control olarak adlandırılır.
Bu işlemin yapılması için Factory, Dependency Injection, Strategy, Service Locator, Template Method gibi çeşitli tasarım desenleri kullanılır.
Factory Pattern nedir?
Factory pattern veya fabrika tasarım deseni bir sınıf üzerinden sınıfların oluşturulması yöntemdir.
public class ObjectFactory {
public static B getB() {
return new B();
}
}
public class A {
private B b;
public A() {
b = ObjectFactory.getB();
}
public void calistir() {
b.yazdir();
}
}
public class B {
public void yazdir() {
System.out.println("Yusuf Sezer");
}
}
public class App {
public static void main(String[] args) {
A a = new A();
a.calistir();
}
}
Factory tasarım desenini uygulayan sınıf(ObjectFactory) her yeni sınf(C, D vb.) ihtiyacında düzenlenmesi gerekir.
Dependency Injection nedir?
Bir sınıf(A) içerisinde kullanılacak sınıfların(B,C vb.) kurucu veya metot ile sınıfa atanmasına Dependency Injection denir.
public class A {
private B b;
public A(B b) { // kurucu
this.b = b;
}
public void setB(B b) { // metot
this.b = b;
}
public void calistir() {
b.yazdir();
}
}
public class B {
public void yazdir() {
System.out.println("Yusuf Sezer");
}
}
public class App {
public static void main(String[] args) {
B b = new B();
A a = new A(b);
a.calistir();
}
}
Dependency Inversion Principle nedir?
Bir sınıfın(A) başka bir sınıfı(B) doğrudan kullanması yerine soyut sınıf veya arayüz üzerinden(ICalistir vb.) kullanılmasına Dependency Inversion denir.
Sınıfların birbirini doğrudan kullanması sınıflar arasında bağın sıkı olmasına neden olur.
Yukarıdaki örnekte B sınıfı yerine benzer işi yapan C sınıfı kullanmak istendiğinde B sınıfını kullanan sınıflarda değişiklik yapılması gerekir.
Sınıfları doğrudan kullanmak yerine soyut sınıf veya arayüzler/şartnameler/sözleşmeler kullanmak değişikliğin daha kolay yönetilmesini sağlar.
public class A {
private ICalistir calistir;
public A(ICalistir calistir) { // kurucu
this.calistir = calistir;
}
public void setCalistir(ICalistir calistir) { // metot
this.calistir = calistir;
}
public void calistir() {
calistir.yazdir();
}
}
public interface ICalistir {
void yazdir();
}
public class B implements ICalistir {
@Override
public void yazdir() {
System.out.println("Yusuf Sezer");
}
}
public class C implements ICalistir {
@Override
public void yazdir() {
System.out.println("Yusuf Sefa Sezer");
}
}
public class App {
public static void main(String[] args) {
//ICalistir calistir = new B();
ICalistir calistir = new C();
A a = new A(calistir);
a.calistir();
}
}
İhtiyaç duyulan sınıfların oluşturulması, yönetilmesi Spring framework içerisinde yer alan Core Container kullanılarak yapılır.
Spring framework bu işlemleri yaparken tasarım prensipleri, tasarım desenleri, Java Reflection, Java Annotation gibi yapıları kullanır.
Java Reflection hakkında detaylı bilgi almak için Java Reflection yazıma bakmalısın.
Java Annotations hakkında detaylı bilgi almak için Java Annotations yazıma bakmalısın.
Spring framework kurulumu
Her bir modüle ait JAR dosyasının ayrı ayrı indirilerek projeye eklenmesi ile kurulacağı gibi maven, gradle gibi araçlar ile hızlıca kurulumu yapılabilir.
Maven projesi oluşturmak için archetype:generate kullanabiliriz.
Windows için;
mvn archetype:generate ^
-DgroupId=com.yusufsezer ^
-DartifactId=SpringApp ^
-DarchetypeArtifactId=maven-archetype-quickstart ^
-DinteractiveMode=false
Unix(Linux, MacOS) için;
mvn archetype:generate \
-DgroupId=com.yusufsezer \
-DartifactId=SpringApp \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Kullanılacak modülün belirlenmesinden sonra maven modülün ihtiyaç duyduğu modülleri projeye ekleyecektir.
Spring framework çekirdeğinde(Core Container) spring-core, spring-beans, spring-context, spring-expression paketleri yer alır.
Benzer şekilde; spring-aop, spring-aspects, spring-messaging, spring-jdbc, spring-tx, spring-orm, spring-oxm, spring-jms, spring-web, spring-webmvc, spring-websocket vb. paketler çekirdek modülde yer alan paketleri kullanarak işlem yapar.
Projeyi oluşturduktan sonra spring-context paketini pom.xml dosyasına ekleyerek Spring framework core işlemlerini yapabiliriz.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.6</version>
</dependency>
Spring framework ile MVC tabanlı uygulama geliştirmek için spring-context yerine spring-webmvc olarak değiştirmek yeterli olacaktır.
Spring framework kullanımı
Spring framework sınıf örneklerini oluşturmak için XML, Annotation ve Java kodları olmak üzere farklı yöntemleri destekler.
Kullanılan yöntem ne olursa olsun temelinde BeanFactory arayüzünde yer alan metotlar kullanılır.
Geniş kullanım desteğinden dolayı XML kullanarak basit bir “Merhaba Spring!” uygulaması hazırlayalım.
public class Mesaj {
private String mesaj;
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
this.mesaj = mesaj;
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
}
XML ile tanımlamayı yapalım.
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesaj" value="Spring"/>
</bean>
</beans>
Spring framework sınıfları bean olarak adlandırır ve bean etiketi ile tanımlanır.
Bean etiketinin class özelliği sınıfın yolunu, id özelliği ise hangi isimle ulaşılacağını ifade eden benzersiz bir değer alır.
Bean etiketi belirtilen sınıfa göre çeşitli alt etiketler(property, constructor-arg vb.) alarak sınıfa değer ataması yapar.
ClassPathXmlApplicationContext sınıfı ile src/main/resources/spring.xml yolundaki XML dosyasını okuyarak sınıfa değer ataması yapalım.
public class App {
public static void main(String[] args) {
String xmlFile = "spring.xml";
BeanFactory beanFactory = new ClassPathXmlApplicationContext(xmlFile);
//Mesaj mesaj = (Mesaj) beanFactory.getBean("mesaj");
//Mesaj mesaj = beanFactory.getBean("mesaj", Mesaj.class);
Mesaj mesaj = beanFactory.getBean(Mesaj.class);
mesaj.yazdir();
}
}
BeanFactory arayüzünde yer alan aşırı yüklenmiş getBean metotları ile nesne elde edilir.
NOT: IoC Container BeanFactory arayüzü ile sağlanır.
Uygulamalar bağımlılıkların oluşturulması ve yönetilmesinin yanı sıra uygulama alanına(context) göre çeşitli özelliklere ihtiyaç duyar.
Uygulama alanına göre ihtiyaç duyulan işlemlerin yapılmasında ApplicationContext arayüzü kullanılır.
Yukarıda yer alan BeanFactory uygulamasını ApplicationContext ile benzer şekilde aşağıdaki gibi kullanabiliriz.
public class App {
public static void main(String[] args) {
String xmlFile = "spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(xmlFile);
//Mesaj mesaj = (Mesaj) context.getBean("mesaj");
//Mesaj mesaj = context.getBean("mesaj", Mesaj.class);
Mesaj mesaj = context.getBean(Mesaj.class);
mesaj.yazdir();
System.out.println("Nesne sayısı: " + context.getBeanDefinitionCount());
Arrays.stream(context.getBeanDefinitionNames())
.forEach(System.out::println);
}
}
ApplicationContext arayüzü BeanFactory ve bazı arayüzlerden(EnvironmentCapable, ListableBeanFactory, MessageSource) kalıtım alarak daha fazla özellik sağlar.
Bean tanımlama
XML ile bean oluşturmak için bean etiketi, özellikleri ve alt etiketlerini kullanır.
abstract – Bean ifadesinin soyut olduğunu belirtir.
autowire – Sınıfın otomatik olarak nasıl bağlanacağını belirtir.
class – Sınıf yolunu belirtir.
depends-on – Sınıfın hangi sınıfa bağlı olduğunu belirtir.
destroy-method – Sınıfın sonlandırılmasında çalışacak metodu belirtir.
factory-bean – Sınfın fabrika sınıfını belirtir.
factory-method – Sınfın fabrika metodunu belirtir.
id – Sınıfa erişim için kullanılacak adı belirtir.
init-method – Sınıfın başlangıcında çalışacak metodu belirtir.
lazy-init – Sınıfın oluşturulma zamanını ifade eder.
name – Sınıfa erişim için kullanılacak adı belirtir.
parent – Sınıfın üst sınıfını(kalıtım alacağı) belirtir.
primary – Aynı sınıfa ait birden fazla bean olduğunda önceliği belirtir.
scope – Sınıf kapsamını belirtir.
property – Sınıftaki alanlara ait değerleri belirtir.
constructor-arg – Sınıf kurucusuna ait değerleri belirtir.
description – Bean açıklamasını belirtir.
Bean kapsamı
Oluşturulan nesneler singleton yani her sınıf için sadece bir nesne oluşturulacaktır.
Nesne kapsamını değiştirmek için bean etiketi scope özelliğine aşağıdaki değerler atanabilir.
singleton – Her bir sınıfa ait tek bir nesne oluşturulacağını belirtir. (Varsayılan)
prototype – Sınıfa ait nesnenin her istenildiğinde yeniden oluşturulacağını belirtir.
session – Web tabanlı uygulamalarda sınıfın her oturum için oluşturulacağını belirtir.
request – Web tabanlı uygulamalarda sınıfın her istek için yeniden oluşturulacağını belirtir.
application – Web tabanlı uygulamalarda uygulamaya ait sadece tek bir nesne oluşturulacağını belirtir.
websocket – Web tabanlı websocket uygulamalarında sınıfa ait sadece tek bir nesne oluşturulacağını belirtir.
<bean id="mesaj" class="com.yusufsezer.Mesaj" scope="singleton">
<property name="mesaj" value="Spring" />
</bean>
<bean id="mesaj1" class="com.yusufsezer.Mesaj" scope="prototype">
<property name="mesaj" value="Framework" />
</bean>
Sınıfa ait kapsama erişmek için BeanFactory arayüzünde yer alan isSingleton ve isPrototype metodları kullanılır.
public class App {
public static void main(String[] args) {
String xmlFile = "spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(xmlFile);
Mesaj mesaj = context.getBean("mesaj", Mesaj.class);
System.out.println("Singleton: " + context.isSingleton("mesaj"));
System.out.println("Prototype: " + context.isPrototype("mesaj"));
Mesaj mesaj1 = context.getBean("mesaj1", Mesaj.class);
System.out.println("Singleton: " + context.isSingleton("mesaj1"));
System.out.println("Prototype: " + context.isPrototype("mesaj1"));
}
}
Not: Spring framework özel scope alanı oluşturmayı destekler.
Bean yaşam döngüsü
Spring framework Servlet, JSP, JSF vb. yapılarda yer alan yaşam döngüsüne sahiptir.
Yaşam döngüsü bir nesne oluşturulurken, oluşturulduktan sonra, değerlerin atanması ve nesnenin sonlandırılması sırasında yapılacak, çalışacak işlemleri ifade eder.
Spring framework yaşam döngüsü sırasında çalışacak metotları belirlemek için birden fazla yöntem sağlar.
Sınıf/bean başlangıcında, sonlandığında çalışacak metotlar bean etiketine ait init-method ve destroy-method özellikleri kullanılarak tanımlanabilir.
<bean id="mesaj" class="com.yusufsezer.Mesaj"
init-method="initMethod"
destroy-method="destroyMethod">
<property name="mesaj" value="Spring" />
</bean>
Her bean için ayrı metot tanımı yerine beans etiketi default-init-method ve default-destroy-method özellikleri kullanılabilir.
XML etiketlerini kullanmak herkesin farklı metot isimleri vermesine neden olacağından ayrıca InitializingBean ve DisposableBean arayüzleri yer alır.
public class Mesaj implements InitializingBean, DisposableBean {
private String mesaj;
public Mesaj() {
System.out.println("com.yusufsezer.Mesaj.<init>()");
}
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
System.out.println("com.yusufsezer.Mesaj.setMesaj()");
this.mesaj = mesaj;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("com.yusufsezer.Mesaj.afterPropertiesSet()");
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
public void initMethod() {
System.out.println("com.yusufsezer.Mesaj.initMethod()");
}
@Override
public void destroy() throws Exception {
System.out.println("com.yusufsezer.Mesaj.destroy()");
}
public void destroyMethod() {
System.out.println("com.yusufsezer.Mesaj.destroyMethod()");
}
}
NOT: InitializingBean ve DisposableBean arayüzleri Spring framework kütüphanesine bağlılığı arttırdığından dolayı Spring tarafından kullanımı önerilmez.
Arayüzler kullanıldığında standart olarak afterPropertiesSet ve destroy metot isimleri kullanılır.
public class App {
public static void main(String[] args) {
String xmlFile = "spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(xmlFile);
Mesaj mesaj = context.getBean("mesaj", Mesaj.class);
((ClassPathXmlApplicationContext) context).registerShutdownHook();
mesaj.yazdir();
//((ClassPathXmlApplicationContext) context).close();
}
}
NOT: Bean sonlandırma(destroy, init-destroy vb.) metotlarının çalışması için registerShutdownHook veya close metodunun çalıştırılması gerekir.
Aware
Spring framework bean yaşam döngüsü sırasında işlem adımı ile ilgili bilgiye erişim için Aware arayüzünden türeyen çeşitli arayüzlere sahiptir.
Aware arayüzleri bean adlandırma, yükleme ve bağlama sırasında işlem adımını parametre olarak metotlara geçirerek kullanılır.
Bazı aware arayüzleri ve çalışma sırası aşağıdaki gibidir.
- BeanNameAware – bean oluşturma sırasında
- BeanClassLoaderAware – bean sınıfı yükleme sırasında
- BeanFactoryAware – BeanFactory bağlama sırasında
- EnvironmentAware – Environment bağlama sırasında
- ApplicationEventPublisherAware – ApplicationEventPublisher bağlama sırasında
- MessageSourceAware – MessageSource bağlama sırasında
- ApplicationContextAware – ApplicationContext bağlama sırasında
public class Mesaj implements
BeanNameAware,
BeanClassLoaderAware,
BeanFactoryAware,
EnvironmentAware,
ApplicationEventPublisherAware,
MessageSourceAware,
ApplicationContextAware {
private String mesaj;
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
this.mesaj = mesaj;
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
@Override
public void setBeanName(String name) {
System.out.println("com.yusufsezer.Mesaj.setBeanName()");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("com.yusufsezer.Mesaj.setBeanClassLoader()");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("com.yusufsezer.Mesaj.setBeanFactory()");
}
@Override
public void setEnvironment(Environment environment) {
System.out.println("com.yusufsezer.Mesaj.setEnvironment()");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
System.out.println("com.yusufsezer.Mesaj.setApplicationEventPublisher()");
}
@Override
public void setMessageSource(MessageSource messageSource) {
System.out.println("com.yusufsezer.Mesaj.setMessageSource()");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
System.out.println("com.yusufsezer.Mesaj.setApplicationContext()");
}
}
NOT: Aware arayüzleri Spring framework kütüphanesine bağlılığı arttırdığından dolayı Spring tarafından kullanımı önerilmez.
Processor
Bean oluşturma, sonlandırma adımlarında beanları yönetmek, kontrol etmek ve düzenlemek için BeanPostProcessor ve BeanFactoryPostProcessor gibi arayüzler kullanılır.
public class MesajProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(bean.getClass());
System.out.println("com.yusufsezer.MesajProcessor.postProcessBeforeInitialization()");
if (bean instanceof Mesaj) {
System.out.println("Bean mesaj türünden, işlemi başlat");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(bean.getClass());
System.out.println("com.yusufsezer.MesajProcessor.postProcessAfterInitialization()");
if (bean instanceof Mesaj) {
System.out.println("Bean mesaj türünden, işlemi bitir");
}
return bean;
}
}
BeanPostProcessor arayüzünün çalışması için ayrı bir bean olarak tanımlanması gerekir.
Arayüzdeki metotlar her bir bean için ayrı ayrı çalıştırılır.
<bean class="com.yusufsezer.MesajProcessor" />
BeanFactoryPostProcessor arayüzünün çalışması için ayrı bir bean tanımlanmasına ihtiyaç yoktur ve beanlar yüklendikten sonra bir defa çalışır.
public class Mesaj implements BeanFactoryPostProcessor {
// diğer metotlar
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
System.out.println("com.yusufsezer.Mesaj.postProcessBeanFactory()");
}
}
NOT: BeanPostProcessor ve BeanFactoryPostProcessor arayüzleri birlikte çalışmamaktadır.
Bean sonlandırmayı kontrol etmek için BeanPostProcessor arayüzünden kalıtım alınarak genişletilen DestructionAwareBeanPostProcessor arayüzü de kullanılabilir.
Bean olay yönetimi
Spring framework bean içerisinde gerçekleşen bir işlem(dosya silme, dosya oluşturma, veritabanı bağlantısı vb.) sonucunda olay tetikleme ve olay işleme yapısına sahiptir.
Oluşan olayları işleyebilmek için ApplicationListener arayüzünü işlenecek olan olay sınıfına göre düzenlemek yeterli olacaktır.
Spring framework içerisinde yer alan işlemler sırasında oluşan olayları işleyebilmek için aşağıdaki gibi ön tanımlı olay sınıflarına sahiptir.
- ApplicationEvent,
- ContextStartedEvent,
- ContextStoppedEvent,
- ContextClosedEvent
public class AppListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event);
}
}
<bean class="com.yusufsezer.AppListener" />
public class App {
public static void main(String[] args) {
String xmlFile = "spring.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(xmlFile);
Mesaj mesaj = context.getBean(Mesaj.class);
((ClassPathXmlApplicationContext) context).start();
((ClassPathXmlApplicationContext) context).refresh();
mesaj.yazdir();
((ClassPathXmlApplicationContext) context).stop();
((ClassPathXmlApplicationContext) context).close();
}
}
Spring framework özel bir olay tanımlamayı, tanımlanan olayı tetiklemeyi ve tetiklenen olayı işlemeyi sağlar.
Özel bir olay tanımlamak için ApplicationEvent arayüzünü kalıtım almak yeterli olacaktır.
public class MesajEvent extends ApplicationEvent {
private String yazilanMesaj;
public MesajEvent(Object source, String mesaj) {
super(source);
this.yazilanMesaj = mesaj;
}
public void yazilanMesajiGoster() {
System.out.println(yazilanMesaj);
}
}
Olayı tetiklemek için ApplicationEventPublisherAware arayüzü ile olay tetikleme sınıfı bağlanır.
public class Mesaj implements ApplicationEventPublisherAware {
// ...
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
Olaya ait sınıfın örneği oluşturularak ApplicationEventPublisher arayüzünde yer alan publishEvent metodu ile olay tetiklenir.
public void yazdir() {
String yazilanMesaj = "Merhaba " + mesaj + "!";
MesajEvent mesajEvent = new MesajEvent(this, yazilanMesaj + " ekrana yazdırıldı.");
System.out.println(yazilanMesaj);
applicationEventPublisher.publishEvent(mesajEvent);
}
Tetiklenen olay için listener oluşturularak olay işlenir.
public class MesajListener implements ApplicationListener<MesajEvent> {
@Override
public void onApplicationEvent(MesajEvent event) {
event.yazilanMesajiGoster();
}
}
<bean class="com.yusufsezer.MesajListener" />
Bean sınıfının içerisindeki yazdir metodu çalıştırıldığında MesajEvent sınıfının örneği oluşturularak tanımlanan özel olay tetiklenecektir.
Bean değer atama
Spring framework sınıfların ihtiyaç duyduğu değerleri atamak ve bağımlılık yönetimi için çeşitli değer atama yöntemlerine sahiptir.
public class Mesaj {
private String mesaj;
public Mesaj(String mesaj) {
this.mesaj = mesaj;
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
}
Sınıf kurucusuna değer atamak için constructor-arg etiketi ve özellikleri kullanılır.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<constructor-arg value="Spring" />
<!--<constructor-arg name="mesaj" value="Spring" />-->
<!--<constructor-arg index="0" value="Spring" />-->
<!--<constructor-arg type="String" value="Spring" />-->
</bean>
Aynı türe ait birden fazla parametre belirsizliğe yol açacağından etikete ait özellikler(name, index, type) kullanılır.
Bir sınıfın ihtiyaç duyduğu başka bir bean ref özelliği ile bağlanabilir.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<constructor-arg ref="mesajım" />
</bean>
<bean id="mesajım" class="java.lang.String">
<constructor-arg value="Spring" />
</bean>
Tanımlı bir bean kullanmak için etiket(idref) ve özellik(ref) kullanılabilir.
<property name="ozellik">
<idref bean="baskaBirBean" />
</property>
Bir sınıf içerisindeki sınıflar için iç içe bean tanımlaması yapılabilir.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesaj">
<bean class="java.lang.String">
<constructor-arg value="Spring" />
</bean>
</property>
</bean>
Spring framework Java koleksiyonlarını da bağlamayı destekler.
public class Mesaj {
private List<String> mesajlar;
public void setMesajlar(List<String> mesajlar) {
this.mesajlar = mesajlar;
}
public void yazdir() {
for (String mesaj : mesajlar) {
System.out.println("Merhaba " + mesaj + "!");
}
}
}
Değer ataması için koleksiyon türüne göre(array, list, set, map, props) etiketlerin kullanımı yeterli olacaktır.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
Spring bean ifadelerini nesne havuzunda saklarken name özelliği veya alias etiketi ile farklı isimlerle aynı nesneye erişimi sağlar.
<bean id="mesaj" class="com.yusufsezer.Mesaj" name="mesaj2, mesaj3">
veya
<alias name="mesaj" alias="mesaj4" />
Tanımlanan farklı isimler kullanılarak bean erişimi sağlanır.
Mesaj mesaj = context.getBean("mesaj", Mesaj.class);
//Mesaj mesaj = context.getBean("mesaj2", Mesaj.class);
//Mesaj mesaj = context.getBean("mesaj3", Mesaj.class);
//Mesaj mesaj = context.getBean("mesaj4", Mesaj.class);
Bean kalıtımı
Spring framework bir bean ifadesinde tanımlanan değerleri farklı bir bean içerisinde kullanabilmek için bean kalıtım özelliğine sahiptir.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesaj" value="Spring" />
</bean>
<bean id="digerMesaj" class="com.yusufsezer.DigerMesaj" parent="mesaj">
<property name="mesaj2" value="IoC" />
</bean>
digerMesaj bean tanımlama sırasında parent özelliği ile sınıf içerisinde yer alan mesaj değerini mesaj bean tanımlamasından almıştır.
public class DigerMesaj {
private String mesaj;
private String mesaj2;
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
this.mesaj = mesaj;
}
public String getMesaj2() {
return mesaj2;
}
public void setMesaj2(String mesaj2) {
this.mesaj2 = mesaj2;
}
}
Spring framework soyut bean tanımlama özelliğine sahiptir.
<bean id="sablon" abstract="true">
<property name="mesaj" value="Spring" />
<property name="mesaj2" value="IoC" />
</bean>
<bean id="digerMesaj" class="com.yusufsezer.DigerMesaj" parent="sablon" />
Herhangi bir sınıf belirtmeden tanımlanan sablon isimli bean abstract özelliği ile soyut bean özelliği elde etmiş ve diğer bean içerisinde parent ile kullanılmıştır.
NOT: Koleksiyonların kalıtımı merge özelliği ile sağlanır.
Bean auto-wire
Spring framework içerisinde yer alan auto-wire özelliği önceden tanımlanmış bean ifadelerini otomatik olarak bağlamayı sağlar.
Bir bean içerisinde kullanılan bir başka bean sınıfının bağlama işlemi için ref kullanılır.
public class MesajServisi {
private Mesaj mesaj;
public Mesaj getMesaj() {
return mesaj;
}
public void setMesaj(Mesaj mesaj) {
this.mesaj = mesaj;
}
public void calistir() {
mesaj.yazdir();
}
}
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
<bean id="mesajServisi" class="com.yusufsezer.MesajServisi">
<property name="mesaj" ref="mesaj" />
</bean>
Spring framework auto-wire özelliği ile bağlama işlemini otomatik hale getirelim.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
<bean id="mesajServisi" class="com.yusufsezer.MesajServisi" autowire="byName" />
Otomatik bağlama özelliğini ada göre belirleyerek bağlama işleminin Spring framework tarafından yapılmasını sağladık.
NOT: MesajServisi sınıfı içerisindeki ad ile bean adının aynı olduğuna dikkat edin.
Otomatik bağlama özelliği aşağıdaki değerleri alabilir.
- <bean autowire=”byName” /> – Ada göre otomatik bağlama yapar.
- <bean autowire=”byType” /> – Türe göre otomatik bağlama yapar.
- <bean autowire=”constructor” /> – Türe göre kurucu üzerinden otomatik bağlama yapar.
- <bean autowire=”default” /> – Türe göre otomatik bağlama yapar.
- <bean autowire=”no” /> – Otomatik bağlamayı devre dışı yapar.
Otomatik türe göre bağlama işlemi sırasında aynı türe sahip birden fazla bean tanımı belirisizliğe yol açar.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
<bean id="mesajSpring" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Spring Framework</value>
<value>Spring MVC</value>
<value>Spring Data</value>
<value>Spring Boot</value>
</list>
</property>
</bean>
<bean id="mesajServisi" class="com.yusufsezer.MesajServisi" autowire="byType" />
Bu durumun önüne geçmek için autowire-candidate özelliği değerine false değerini vermek yeterli olacaktır.
<bean id="mesaj" class="com.yusufsezer.Mesaj" autowire-candidate="false">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
<bean id="mesajSpring" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Spring Framework</value>
<value>Spring MVC</value>
<value>Spring Data</value>
<value>Spring Boot</value>
</list>
</property>
</bean>
<bean id="mesajServisi" class="com.yusufsezer.MesajServisi" autowire="byType" />
Otomatik bağlama işleminde kullanılan constructor değeri bağlama işlemini setXXX metodları yerine kurucu metot üzerinden yapar.
Annotations
Spring framework otomatik bağlama işlemi için @Autowired annotation kullanımına sahiptir.
Annotation sınıf özelliği, kurucu veya setXXX metotları ile kullanılabilir.
// Özellik
@Autowired
private Mesaj mesaj;
// Kurucu
@Autowired
public MesajServisi(Mesaj mesaj) {
this.mesaj = mesaj;
}
// setXXX
@Autowired
public void setMesaj(Mesaj mesaj) {
this.mesaj = mesaj;
}
Annotations kullanımının aktif hale gelmesi için context:annotation-config veya context:component-scan etiketinin eklenmesi gerekir.
<context:annotation-config />
<context:component-scan base-package="com.yusufsezer" />
Aralarındaki fark context:annotation-config tüm projeyi ararken context:component-scan sadece base-package ile belirtilen paketi tarar.
<bean id="mesaj" class="com.yusufsezer.Mesaj">
<property name="mesajlar">
<list>
<value>Java</value>
<value>IoC</value>
<value>Spring</value>
</list>
</property>
</bean>
<bean class="com.yusufsezer.MesajServisi" />
<context:annotation-config />
public class MesajServisi {
@Autowired
private Mesaj mesaj;
public void calistir() {
mesaj.yazdir();
}
}
Otomatik bağlama işleminde XML ayarlarının kullanımı yerine tamamen annotation kullanımı ortaya çıkabilecek birçok hatanın derleme sırasında bulunmasını sağlayacaktır.
Java tabanlı annotation kullanımı
Spring framework XML tabanlı olarak ayarların yapımı ve bean tanımlamanın yanında Java tabanlı annotation kullanımını destekler.
XML ayarlarında kullanılan ClassPathXmlApplicationContext sınıfından farklı olarak AnnotationConfigApplicationContext sınıfı kullanılır.
Ayarların yüklenmesi için ayar sınıfının bulunduğu paket veya sınıf parametre olarak geçirilip kullanılabilir.
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Ayarlar.class);
//ApplicationContext context = new AnnotationConfigApplicationContext("com.yusufsezer");
//Mesaj mesaj = (Mesaj) context.getBean("mesaj");
//Mesaj mesaj = context.getBean("mesaj", Mesaj.class);
Mesaj mesaj = context.getBean(Mesaj.class);
mesaj.yazdir();
System.out.println("Nesne sayısı: " + context.getBeanDefinitionCount());
Arrays.stream(context.getBeanDefinitionNames())
.forEach(System.out::println);
}
}
AnnotationConfigApplicationContext ayar sınıflarını kayıt etmek için register, annotation tanımlarının aranacağı paketi belirtmek için scan metoduna sahiptir.
@Configuration
class Ayarlar {
@Bean
public Mesaj mesaj() {
Mesaj mesaj = new Mesaj();
mesaj.setMesaj("Spring");
return mesaj;
}
}
Karmaşık XML ifadelerine gerek duymaması, oluşabilecek hataların derleme sırasında ortaya çıkmasından dolayı annotation kullanımı artmıştır.
NOT: Annotation kullanımı XML kullanımına göre bazı özellikleri desteklemez.
XML ile bean tanımlama sırasında kullanılan id, name özelliği, init-method, destroy-method ve autowire-candidate özellikleri @Bean ile kullanımı aşağıdaki gibidir.
@Bean({"mesajBean"})
@Bean(name = {"mesajBean"})
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
@Bean(autowireCandidate = false)
XML ile bean kapsamı(scope) belirlemek için @Scope ifadesine kullanılacak kapsam parametre olarak geçirilir.
@Scope("prototype")
Otomatik bağlamak işlemi için @Autowired ifadesi kullanılır.
Otomatik bağlama işlemi sırasında isimlendirme kurallarına dikkat edilmediğinde veya aynı türden @Bean tanımında ortaya çıkan belirsizliği gidermek için @Qualifier ifadesi kullanılır.
@Autowired
@Qualifier("mesajBean")
Otomatik bağlamada ortaya çıkan belirsizliği gidermek için @Primary ifadesi ile öncelik verilebilir.
Diğer bir yöntem ise bean tanımında kullanılan adlandırma ile Java özelliğinin aynı ada sahip olmasıdır.
Bir bean ifadesinin başka bir bean ifadesinden önce oluşturulması için depends-on için @DependsOn ifadesi kullanılır.
Aware arayüzleri kullanılarak erişilen sınıflara herhangi bir arayüz kullanılmadan sadece @Autowired ile erişim sağlanabilir.
public class Mesaj {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
private String mesaj;
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
this.mesaj = mesaj;
}
public void yazdir() {
String yazilanMesaj = "Merhaba " + mesaj + "!";
MesajEvent mesajEvent = new MesajEvent(this, yazilanMesaj + " ekrana yazdırıldı.");
System.out.println(yazilanMesaj);
applicationEventPublisher.publishEvent(mesajEvent);
}
}
Otomatik bağlama işleminin yanında olayları yönetmek için kullanılan arayüzlere ihtiyaç duymadan @EventListener ile olayların yönetimi sağlanır.
@Configuration
class Ayarlar {
@EventListener // Tüm ApplicationEvent olaylarında çalışır
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event);
}
@EventListener(MesajEvent.class) // Sadece MesajEvent olaylarında çalışır
public void onApplicationEvent(MesajEvent mesajEvent) {
System.out.println(mesajEvent);
}
@Bean
public Mesaj mesaj() {
Mesaj mesaj = new Mesaj();
mesaj.setMesaj("Spring");
return mesaj;
}
}
Olayların yönetimi sırasında kullanılan @EventListener ifadesine parametre ekleyerek sadece ilgili olayın yönetimi sağlanmış olur.
Spring framework parametre içermeyen kurucuya sahip sınıfları @Component ile otomatik olarak bağlama özelliğine sahiptir.
Bu tür bağlama işlemleri için aşağıdaki stereotype annotation ifadeleri kullanılır.
- @Component – Genel bağlama ifadesidir
- @Service – Servis katmanı bağlama ifadesidir
- @Controller – Spring MVC tabanlı uygulamalarda Controller katmanı bağlama ifadesidir
- @Repository – Veri katmanı bağlama ifadesidir
@Component
public class Mesaj {
private String mesaj;
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
this.mesaj = mesaj;
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
}
@Service
public class MesajServis {
@Autowired
private Mesaj mesaj;
public void calistir() {
mesaj.yazdir();
}
}
Boş kurucuya sahip sınıflardan bean oluşturmak için @ComponentScan, AnnotationConfigApplicationContext#scan yöntemleri kullanılabilir.
@Configuration
@ComponentScan
class Ayarlar {
}
ApplicationContext içerisinde yer alan getBeanDefinitionCount veya getBeanDefinitionNames metotları kullanılarak tanımlanan sınıflar ile bilgi alınabilir.
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Ayarlar.class);
MesajServis mesajServis = context.getBean(MesajServis.class);
mesajServis.calistir();
System.out.println("Nesne sayısı: " + context.getBeanDefinitionCount());
Arrays.stream(context.getBeanDefinitionNames())
.forEach(System.out::println);
}
}
Standartlar
Spring framework JCP (Java Community Process) tarafından belirlenmiş JSR-250 ve JSR-330 standart tanımlamaları destekler.
Standartların kullanımı için paketlerin projeye eklenmesi gerekir.
JSR-330 için;
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
JSR-250 için;
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>3.0.0</version>
</dependency>
Spring ifadelerinin standart karşılığı aşağıdaki gibidir.
- @Autowired yerine @Inject veya @Resource
- @Qualifier yerine @Qualifier veya @Named
- @Component(@Component, @Service, @Controller, @Repository) yerine @Named veya @ManagedBean
- @Scope yerine @Singleton
Spring içerisinde yer alan init-method, destroy-method ifadelerine karşılık @PostConstruct ve @PreDestroy ifadeleri kullanılır.
@Named
public class Mesaj {
private String mesaj;
public Mesaj() {
System.out.println("com.yusufsezer.Mesaj.<init>()");
}
public String getMesaj() {
return mesaj;
}
public void setMesaj(String mesaj) {
System.out.println("com.yusufsezer.Mesaj.setMesaj()");
this.mesaj = mesaj;
}
public void yazdir() {
System.out.println("Merhaba " + mesaj + "!");
}
@PostConstruct
public void initMethod() {
System.out.println("com.yusufsezer.Mesaj.initMethod()");
}
@PreDestroy
public void destroyMethod() {
System.out.println("com.yusufsezer.Mesaj.destroyMethod()");
}
}
Dependency Inversion Principle
Spring framework DIP(Dependency Inversion Principle) prensibinde yer alan arayüzler üzerinden sınıfların kullanımını destekler.
İlk olarak ortak bir arayüz/şartname/sözleşme tanımlayalım.
public interface IMesaj {
void yazdir();
}
Arayüzü uygulayan sınıfları hazırlayalım.
public class EkranaMesaj implements IMesaj {
@Override
public void yazdir() {
System.out.println("Merhaba");
}
}
public class DosyayaMesaj implements IMesaj {
@Override
public void yazdir() {
try {
Path path = Paths.get("mesaj.txt");
Files.writeString(path, "Merhaba", StandardOpenOption.CREATE);
} catch (IOException ex) {
System.err.println(ex);
}
}
}
Bean tanımlarını yapalım.
@Bean
public IMesaj ekranaMesaj() {
return new EkranaMesaj();
}
@Bean
public IMesaj dosyayaMesaj() {
return new DosyayaMesaj();
}
Arayüzü uygulayan her iki sınıfa bean adlandırması ve uyguladığı arayüz üzerinden erişim sağlanabilir.
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Ayarlar.class);
IMesaj ekranaMesaj = context.getBean("ekranaMesaj", IMesaj.class);
ekranaMesaj.yazdir();
IMesaj dosyayaMesaj = context.getBean("dosyayaMesaj", IMesaj.class);
dosyayaMesaj.yazdir();
}
}
Aynı arayüzü uygulayan birden fazla bean otomatik bağlama sırasında belirsiz duruma neden olacaktır.
Bu belirsizliği ortadan kaldırmak için @Qualifier ile erişim sağlanabilir.
public class MesajServis {
@Autowired
@Qualifier("ekranaMesaj")
private IMesaj mesaj;
public void calistir() {
mesaj.yazdir();
}
}
Diğer yöntem ise özellik adının kullanılacak bean ile aynı ada sahip olmasıdır.
public class MesajServis {
@Autowired
private IMesaj ekranaMesaj;
public void calistir() {
ekranaMesaj.yazdir();
}
}
MessageSource kullanımı
MessageSource özelliği tanımlı mesajları uluslararasılaşma(internationalization-i18n) ile birlikte kullanmayı sağlar.
Özelliğin kullanımı için ilk olarak mesajlar .properties uzantılı olarak maven için src/main/resources/ dizininde oluşturulur.
mesajlar.properties
welcome.message=Welcome {0}
i18n desteğini(çok dilli) sağlamak için dosyaadı_ülkekodu olarak mesaj dosyaları oluşturulur.
mesajlar_tr.properties
welcome.message=Hoşgeldiniz {0}
Mesaj dosyaları hazırlandıktan sonra MessageSource arayüzünü uygulayan sınıfın(ResourceBundleMessageSource veya ReloadableResourceBundleMessageSource) XML veya annotation ile bean olarak tanımı yapılır.
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("mesajlar");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
XML karşılığı aşağıdaki gibidir.
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="mesajlar" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
Diğer değer atamaları da yapılarak MessageSource işlemi özelleştirilebilir.
NOT: Bean isimlendirmesi olarak messageSource kullanıldığına dikkat edilmelidir.
Bean tanımları yapıldıktan sonra MessageSource arayüzünde yer alan aşağıdaki metotlar ile mesajlara ulaşılır.
- String getMessage(String code, Object[] args, String defaultMessage, Locale locale)
- String getMessage(String code, Object[] args, Locale locale)
- String getMessage(MessageSourceResolvable resolvable, Locale locale)
Metotlarda yer alan
- code mesaj dosyasındaki kodu,
- args mesajda yer alan parametre({0}, {1}) değerini,
- defaultMessage mesaj yoksa kullanılacak varsayılan mesajı
- locale belirli bir dile ait mesajı,
- MessageSourceResolvable ifadesi code, args, defaultMessage ifade eder.
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Ayarlar.class);
String message = context.getMessage("welcome.message", new Object[]{"Yusuf"}, Locale.getDefault());
System.out.println(message);
String messageTR = context.getMessage("welcome.message", new Object[]{"Yusuf"}, new Locale("tr", "tr"));
System.out.println(messageTR);
}
}
Bean içerisinde MessageSource özelliğini kullanmak için MessageSourceAware arayüzünü sınıfa uygulamak
public class Mesaj implements MessageSourceAware {
private MessageSource messageSource;
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public void calistir() {
String message = messageSource.getMessage("welcome.message", new Object[]{"Yusuf"}, Locale.getDefault());
System.out.println(message);
}
}
veya otomatik bağlama özelliğini kullanmak yeterli olacaktır.
public class Mesaj {
@Autowired
private MessageSource messageSource;
public void calistir() {
String message = messageSource.getMessage("welcome.message", new Object[]{"Yusuf"}, Locale.getDefault());
System.out.println(message);
}
}
Diğer modüller ve sınıflar
Spring framework içerisinde çeşitli işlemler(veritabanı, mail, web) için geliştirilmiş modüller(jdbc, orm vb.) yer almaktadır.
Modül içerisinde yer alan sınıflar yapılacak olan işlemler arasında bir ara katman görevi görerek yapılacak işlem için gerekli olan sınıfları yapılandırır.
Yukarıdaki örnekte yer alan MessageSource özelliği modüllerin çalışmasının genel akışıdır.
İlk olarak yapılacak olan işleme ait sınıf için bean tanımlaması yapılır.
@Bean
public DataSource datataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
Yapılandırmadan sonra Spring Framework IoC sayesinde kullanımı sağlanır.
@Autowired
private DataSource dataSource;
public void calistir() {
try {
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
// işlemler
System.out.println(connection);
statement.close();
} catch (SQLException e) {
System.err.println(e);
}
}
Java JDBC hakkında detaylı bilgi almak için Java JDBC yazıma bakmalısın.
NOT: Veritabanı işlemlerinde birçok sınıf DataSource arayüzünü kullanır.
Java Derslerine buradan ulaşabilirsiniz.
Hayırlı günler dilerim.