1. Yalın Java sınıflarının kullanıldığı bileşen tabanlı programlama
Bileşenler aynı sınıflardan yarattığımız nesneler gibidir. Ancak onlardan farklı olarak yaşayabilmeleri, var olabilmeleri için bir barınağa ya da kaba (=container) ihtiyaç duyarlar. Barınak bileşenlerin yaşam döngülerini yönetir ve bazı sık kullanılan hizmetler sunar. Kurumsal Java uygulamalarındaki uygulama sunucuları Web kabı ve EJB kabı olmak üzere iki kap ve bileşen modeli içerir. Web kabında Servlet ve JSP bileşen modelleri yer alır. EJB kabında ise Session Bean, Message Driven Bean bileşen modelleri bulunur.
Tarihsel nedenlerden dolayı Java EE'de bir kaç tane bileşen modeli ile karşılaşıyoruz:
i. EJB kabında çalışan ağır sıklet bileşen modeli: EJB
ii. Web kabında çalışan hafif sıklet bileşen modeli: EJB Lite
iii. Hafif sıklet bileşen modeli: CDI
Java EE 6 ile birlikte yeni bir bileşen modelimiz daha var: CDI (Contexts and Dependency Injection) Bean. CDI en temelde hizmet alan ve veren bileşenler arasındaki bağımlılıkları yöneten bir çatı sunuyor. Tıpkı Spring, Guice, JBoss Seam gibi. Bunun dışında ilgiye dayalı programlama, olay temelli programlama ve dekoratör tasarım kalıbı gibi çözümlere hızlı bir şekilde ulaşmamızı sağlar. CDI, Java EE'deki diğer tüm API'lerle ve onların bileşenleriyle tümleşik çalışacak şekilde tasarlanmıştır.
2. Java SE 5 ile gelen notlar (=Annotations) kullanılarak sağlanan bildirimsel programlama. Örneğin, CDI bileşenlerini @Bean damgasını kullanarak bildiriyoruz:
package com.example.lottery.web.model; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; import com.example.lottery.service.LotteryService; @Named("lottery") @RequestScoped public class LotteryViewModel { private Collection<Integer> numbers; @Inject private LotteryService lotteryService; public LotteryViewModel() { } public Collection<Integer> getNumbers() { numbers = lotteryService.draw(); return numbers; } }
package com.example.lottery.service; import java.util.Set; public interface LotteryService { Set<Integer> draw(); }
package com.example.lottery.service.impl; import java.util.Set; import java.util.TreeSet; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.LotteryService; import com.example.lottery.service.RandomNumberGenerator; @Named @Singleton public class SimpleLotteryService implements LotteryService { @Inject private RandomNumberGenerator randomNumberGenerator; @Override public Set<Integer> draw() { Set<Integer> number = new TreeSet<>(); while (number.size() < 6) number.add(randomNumberGenerator.generate(1, 49)); return number; } }
public interface RandomNumberGenerator { int generate(int max); int generate(int min,int max); }
package com.example.lottery.service.impl; import java.util.Random; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.RandomNumberGenerator; @Named @Singleton public class SimpleRandomNumberGenerator implements RandomNumberGenerator { private final Random random = new Random(); @Override public int generate(int max) { return random.nextInt(max); } @Override public int generate(int min, int max) { return random.nextInt(max - min + 1) + min; } }
lottery.jsp:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Lottery Page</title> </head> <body> <form> <input type="submit" value="Draw" /> </form> <h2> <ul> <c:forEach items="${lottery.numbers}" var="number"> <li>${number}</li> </c:forEach> </ul> </h2> </body> </html>
Peki LotteryService arayüzünü gerçekleyen birden fazla bileşen varsa, oluşan belirsizliği nasıl gidereceğiz? Eğer belirsizliği ortandan kaldırmaz isek uygulamayı sunucuya yayınlayamayız. Çözüm için @Qualifier notundan yararlanacağız. Önce @Qualifier ile damgalanmış notlar oluşturacağız:
Cheap.java
package com.example.lottery.service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Qualifier public @interface Cheap { }
package com.example.lottery.service;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Qualifier public @interface Fast { }
package com.example.lottery.service;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @Qualifier public @interface Good { }
Hızlı, ucuz ve iyi. Hiçbir gerçekleme bunların üçünü de birden sağlamıyor. İkisini seç:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Qualifier @Fast @Cheap public @interface FastCheap { }
package com.example.lottery.service.impl; import java.util.Set; import java.util.TreeSet; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.FastCheap; import com.example.lottery.service.LotteryService; @Named @Singleton @FastCheap public class FastCheapLotteryService implements LotteryService { private static int counter = 0; @Override public Set<Integer> draw() { Set<Integer> number = new TreeSet<>(); while (number.size() < 6) { counter = (counter % 49) + 1; number.add(counter); } return number; } }
package com.example.lottery.service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Qualifier @Fast @Good public @interface FastGood { }
package com.example.lottery.service.impl; import java.util.Set; import java.util.TreeSet; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.FastGood; import com.example.lottery.service.LotteryService; import com.example.lottery.service.RandomNumberGenerator; @Named @Singleton @FastGood public class FastGoodLotteryService implements LotteryService { @Inject private RandomNumberGenerator randomNumberGenerator; @Override public Set<Integer> draw() { Set<Integer> number = new TreeSet<>(); while (number.size() < 6) number.add(randomNumberGenerator.generate(1, 49)); return number; } }
package com.example.lottery.service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.inject.Qualifier; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) @Qualifier @Good @Cheap public @interface GoodCheap { }
package com.example.lottery.service.impl; import java.util.Set; import java.util.TreeSet; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.GoodCheap; import com.example.lottery.service.LotteryService; @Named @Singleton @GoodCheap public class GoodCheapLotteryService implements LotteryService { private static int counter = 0; @Override public Set<Integer> draw() { Set<Integer> number = new TreeSet<>(); while (number.size() < 6) { counter = ( 13 * counter + 43 ) % 49 + 1; number.add(counter); } return number; } }
@Named("lottery") @SessionScoped public class LotteryViewModel implements Serializable { private Collection<Integer> numbers; @Inject @FastCheap private transient LotteryService lotteryService; public LotteryViewModel() { numbers = new ArrayList<>(); } public Collection<Integer> getNumbers() { numbers = lotteryService.draw(); return numbers; } }
İşte yanıtı:
package com.example.lottery.service.impl; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import com.example.lottery.service.Fast; import com.example.lottery.service.FastCheap; import com.example.lottery.service.FastGood; import com.example.lottery.service.GoodCheap; import com.example.lottery.service.LotteryService; @Named @Singleton public class LotteryStrategy { private int count = 0; @Inject @FastGood private LotteryService fastGoodLotteryService; @Inject @FastCheap private LotteryService fastCheapLotteryService; @Inject @GoodCheap private LotteryService goodCheapLotteryService; @Inject @Fast private LotteryService fastLotteryService; @Produces @RequestScoped public LotteryService strategy() { count++; switch (count % 4) { case 0: return fastGoodLotteryService; case 1: return fastCheapLotteryService; case 2: return fastLotteryService; default: return goodCheapLotteryService; } } }
Şimdi konuyu başka bir noktaya çekmek istiyorum: LotteryViewModel sınıfının bağımlılığını biraz daha gevşetebilir miyiz? Aşağıdaki örneğe bir bakalım:
@Named("lottery") @RequestScoped public class LotteryViewModel { @RandomNumber(min = 1, max = 49, size = 6, distinct = true, sorted = true, containerType = ArrayList.class) private Collection<Integer> numbers; @RandomNumber(min = 1, max = 100, size = 1) private int secret; public LotteryViewModel() { } public Collection<Integer> getNumbers() { return numbers; } public int getSecret() { return secret; } }
@Random:
package com.example.service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface RandomNumber { int min() default 1; int max() default 100; int size() default 1; boolean distinct() default false; boolean sorted() default false; Class<?> containerType() default Void.class; }
- Kaç tane rastgele sayı üretilecek?
- Hangi aralıkta değerlere ihtiyaç var?
- Bu sayılar tekil mi olsun?
- Sıralansın mı?
package com.example.cdi.extension; import java.lang.reflect.Field; import java.util.Set; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.inject.spi.InjectionTarget; import javax.enterprise.inject.spi.ProcessInjectionTarget; import com.example.service.RandomNumber; public class RandomNumberExtension implements Extension { public RandomNumberExtension() { } public <T> void initializeRandomNumberExtension( final @Observes ProcessInjectionTarget<T> pit) { AnnotatedType<T> at = pit.getAnnotatedType(); for (Field field : at.getJavaClass().getDeclaredFields()) if (field.isAnnotationPresent(RandomNumber.class)) { final InjectionTarget<T> it = pit.getInjectionTarget(); InjectionTarget<T> wrapped = new InjectionTarget<T>() { @Override public void dispose(T instance) { it.dispose(instance); } @Override public Set<InjectionPoint> getInjectionPoints() { return it.getInjectionPoints(); } @Override public T produce(CreationalContext<T> ctx) { return it.produce(ctx); } @Override public void inject(T instance, CreationalContext<T> ctx) { try { RandomNumberGenerator.generate(instance); } catch (InstantiationException e) { e.printStackTrace(); } } @Override public void postConstruct(T instance) { it.postConstruct(instance); } @Override public void preDestroy(T instance) { it.preDestroy(instance); } }; pit.setInjectionTarget(wrapped); } } }
com.example.cdi.extension.RandomNumberExtension
package com.example.cdi.extension; import java.lang.reflect.Field; public class RandomNumberGenerator { private static final Random random = new Random(); public static void generate(Object o) throws InstantiationException { Class<?> clazz = o.getClass(); for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(RandomNumber.class)) { try { RandomNumber rn = field.getAnnotation(RandomNumber.class); Class<?> containerType = rn.containerType(); if (rn.size() > 1) { generateCollection(o, field, rn); } else if (rn.size() == 1) { generateScalar(o, field, rn); } } catch (IllegalArgumentException | IllegalAccessException e) { e.printStackTrace(); } } } } private static void generateCollection(Object o, Field field, RandomNumber rn) throws InstantiationException, IllegalAccessException { Collection<Integer> randomValues = (Collection<Integer>) rn .containerType().newInstance(); int min = rn.min(); int max = rn.max(); for (int i = 0; i < rn.size(); ++i) randomValues.add(random.nextInt(max - min + 1) + min); field.setAccessible(true); field.set(o, randomValues); } private static void generateScalar(Object o, Field field, RandomNumber rn) throws InstantiationException, IllegalAccessException { int min = rn.min(); int max = rn.max(); int randomValue = random.nextInt(max - min + 1) + min; field.setAccessible(true); field.set(o, randomValue); } }
<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>com.example</groupId> <artifactId>imdb-cdi-javase</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>imdb-cdi-javase</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.jboss.weld.se</groupId> <artifactId>weld-se-core</artifactId> <version>1.1.10.Final</version> </dependency> </dependencies> </project>
package com.example.console; import java.util.ArrayList; import java.util.Collection; import javax.enterprise.event.Observes; import org.jboss.weld.environment.se.events.ContainerInitialized; import com.example.service.RandomNumber; public class TestCDI { @RandomNumber(min = 1, max = 49, size = 6, distinct = true, sorted = true, containerType = ArrayList.class) private Collection<Integer> numbers; @RandomNumber(min = 1, max = 100, size = 1) private int secret; public void main(@Observes ContainerInitialized event){ System.err.println(numbers); System.err.println(secret); } }