AOP'yi uygulama
Görünüş yönelimli programlama, tamamen ayrı bir modülde yapılandırılamayan, farklı yöntemlerle birçok kez tekrarlanabilen herhangi bir kod olabilen, kesişen görevleri gerçekleştirmek için tasarlanmıştır. Buna göre AOP , bunu ana kodun dışında tutmamıza ve dikey olarak bildirmemize izin verir. Bir örnek, bir uygulamada bir güvenlik ilkesi kullanmaktır. Tipik olarak güvenlik, bir uygulamanın birçok öğesinden geçer. Ayrıca uygulamanın güvenlik politikası, uygulamanın mevcut ve yeni tüm bölümlerine eşit olarak uygulanmalıdır. Aynı zamanda, kullanımda olan bir güvenlik politikası da gelişebilir. Burası AOP kullanmak için mükemmel bir yer . Ayrıca başka bir örnek de günlük kaydıdır.. Günlüğe kaydetme işlevini manuel olarak eklemek yerine, günlüğe kaydetme için AOP yaklaşımını kullanmanın birkaç avantajı vardır:-
Günlüğe kaydetme kodunu eklemek ve kaldırmak kolaydır: yapmanız gereken tek şey, bazı açılardan birkaç yapılandırma eklemek veya kaldırmaktır.
-
Günlüğe kaydetme için tüm kaynak kodu tek bir yerde tutulur, bu nedenle kullanıldığı tüm yerleri manuel olarak aramanıza gerek yoktur.
-
Günlüğe kaydetme kodu, önceden yazılmış yöntemlerde ve sınıflarda veya yeni işlevlerde herhangi bir yere eklenebilir. Bu, kodlama hatalarının sayısını azaltır.
Ayrıca, bir tasarım yapılandırmasından bir yönü kaldırırken, tüm izleme kodunun kaybolduğundan ve hiçbir şeyin gözden kaçmadığından emin olabilirsiniz.
- Yönler, geliştirilebilen ve tekrar tekrar kullanılabilen ayrı kodlardır.
AOP'nin temel ilkeleri
Bu konuda daha fazla ilerlemek için önce AOP'nin ana kavramlarını tanıyalım. Tavsiye — Bir birleştirme noktasından çağrılan ek mantık veya kod. Tavsiye, bir birleştirme noktasından önce, sonra veya onun yerine yapılabilir (bunlar hakkında aşağıda daha fazla bilgi verilmektedir). Olası tavsiye türleri :-
Önce — bu tür tavsiyeler, hedef yöntemler, yani birleştirme noktaları yürütülmeden önce başlatılır. Yönleri sınıf olarak kullanırken, tavsiyeyi daha önce geliyor olarak işaretlemek için @Before ek açıklamasını kullanırız . Açıları .aj dosyaları olarak kullanırken , bu before() yöntemi olacaktır .
- After — hem normal yürütmede hem de bir istisna fırlatırken yöntemlerin yürütülmesi (birleştirme noktaları) tamamlandıktan sonra yürütülen tavsiye.
Görünümleri sınıf olarak kullanırken, @After ek açıklamasını bunun ardından gelen bir tavsiye olduğunu belirtmek için kullanabiliriz .
Açıları .aj dosyaları olarak kullanırken , bu after() yöntemidir.
-
Döndükten Sonra — bu tavsiye yalnızca hedef yöntem hatasız ve normal şekilde bittiğinde gerçekleştirilir.
Yönler sınıflar olarak temsil edildiğinde, tavsiyeyi başarılı bir şekilde tamamlandıktan sonra yürütülüyor olarak işaretlemek için @AfterReturning ek açıklamasını kullanabiliriz .
Görünümleri .aj dosyaları olarak kullanırken , bu after() döndürme (Object obj) yöntemi olacaktır .
-
Fırlattıktan Sonra — bu tavsiye, bir yöntemin, yani birleştirme noktasının bir istisna oluşturduğu durumlar için tasarlanmıştır. Bu tavsiyeyi, belirli türden başarısız yürütmeleri halletmek için kullanabiliriz (örneğin, tüm bir işlemi geri almak veya gerekli izleme düzeyiyle günlüğe kaydetmek için).
Sınıf yönleri için, @AfterThrowing ek açıklaması, bu tavsiyenin bir istisna attıktan sonra kullanıldığını belirtmek için kullanılır.
Görünümleri .aj dosyaları olarak kullanırken , bu after() fırlatma (Exception e) yöntemi olacaktır .
-
Etrafında - belki de en önemli tavsiye türlerinden biri. Bir yöntemi, yani örneğin belirli bir birleştirme noktası yöntemini uygulayıp uygulamamayı seçmek için kullanabileceğimiz bir birleştirme noktasını çevreler.
Birleştirme noktası yöntemi yürütülmeden önce ve sonra çalışan tavsiye kodu yazabilirsiniz.
Çevre tavsiyesi, birleştirme noktası yöntemini ve yöntem bir şey döndürürse dönüş değerlerini çağırmaktan sorumludur. Başka bir deyişle, bu tavsiyede, bir hedef yöntemin çalışmasını çağırmadan basit bir şekilde simüle edebilir ve dönüş sonucu olarak istediğiniz şeyi döndürebilirsiniz.
Sınıflar olarak verilen yönler, bir birleştirme noktasını saran tavsiyeler oluşturmak için @Around ek açıklamasını kullanırız . .aj dosyaları biçimindeki görünümleri kullanırken , bu yöntem around() yöntemi olacaktır .
-
Derleme zamanı dokuması — yönün kaynak koduna ve yönü kullandığınız koda sahipseniz, kaynak kodunu ve yönü doğrudan AspectJ derleyicisini kullanarak derleyebilirsiniz;
-
Derleme sonrası dokuma (ikili dokuma) — yönleri koda örmek için kaynak kodu dönüşümlerini kullanamıyorsanız veya kullanmak istemiyorsanız, önceden derlenmiş sınıfları veya jar dosyalarını alabilir ve bunlara görünümler ekleyebilirsiniz;
-
Yükleme zamanı dokuması — bu yalnızca, sınıf yükleyici sınıf dosyasını yükleyene ve JVM için sınıfı tanımlayana kadar ertelenen ikili dokumadır.
Bunu desteklemek için bir veya daha fazla dokuma sınıfı yükleyicisi gerekir. Bunlar ya açıkça çalışma zamanı tarafından sağlanır ya da bir "dokuma aracısı" tarafından etkinleştirilir.
Java'daki örnekler
Ardından, AOP'yi daha iyi anlamak için "Merhaba Dünya" tarzı küçük örneklere bakacağız. En başta, örneklerimizin derleme zamanı dokumasını kullanacağını not edeceğim . Öncelikle pom.xml dosyamıza aşağıdaki bağımlılığı eklememiz gerekiyor :<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
Kural olarak, özel ajc derleyicisi, yönleri nasıl kullandığımızdır. IntelliJ IDEA bunu varsayılan olarak içermez, bu nedenle onu uygulama derleyicisi olarak seçerken 5168 75 AspectJ dağıtımına giden yolu belirtmeniz gerekir. Bu ilk yoldu. Kullandığım ikincisi, aşağıdaki eklentiyi pom.xml dosyasına kaydetmektir:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<showWeaveInfo>true</showWeaveInfo>
<<verbose>true<verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Bundan sonra, Maven'den yeniden içe aktarma yapmak ve mvn clean derlemesini çalıştırmak iyi bir fikirdir . Şimdi doğrudan örneklere geçelim.
Örnek 1
Bir Ana sınıf oluşturalım . İçinde, bir giriş noktamız ve konsolda geçirilen bir adı yazdıran bir yöntemimiz olacak:public class Main {
public static void main(String[] args) {
printName("Tanner");
printName("Victor");
printName("Sasha");
}
public static void printName(String name) {
System.out.println(name);
}
}
Burada karmaşık bir şey yok. Bir isim geçtik ve onu konsolda gösterdik. Programı şimdi çalıştırırsak, konsolda aşağıdakileri görürüz:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
Bu dosya bir çeşit sınıf gibidir. Bakalım burada neler oluyor: pointcut, birleştirme noktaları kümesidir; tebrik() bu noktasal kesimin adıdır; : yürütme, Main.printName(...) yönteminin tüm ( * ) çağrılarının yürütülmesi sırasında uygulanmasını belirtir . Daha sonra , hedef yöntem çağrılmadan önce yürütülen özel bir tavsiye — before() — gelir. : tebrik(), bu tavsiyenin yanıt verdiği kesme noktasıdır. Aşağıda, anladığımız Java dilinde yazılmış yöntemin gövdesini görüyoruz. Bu özellik mevcutken main'i çalıştırdığımızda , şu konsol çıktısını alacağız:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
.aj en boy dosyasından sonra , burada her şey daha belirgin hale gelir:
- @Aspect, bu sınıfın bir özellik olduğunu belirtir;
- @Pointcut("execution(* Main.printName(String))"), türü String olan bir giriş bağımsız değişkeni ile Main.printName'e yapılan tüm çağrılar için tetiklenen kesme noktasıdır ;
- @Before("selamlama()"), tebrik() kesme noktasında belirtilen kodu çağırmadan önce uygulanan tavsiyedir .
Örnek 2
Müşteriler için bazı işlemleri gerçekleştiren bir yöntemimiz olduğunu varsayalım ve bu yöntemi main'den çağırıyoruz :public class Main {
public static void main(String[] args) {
performSomeOperation("Tanner");
}
public static void performSomeOperation(String clientName) {
System.out.println("Performing some operations for Client " + clientName);
}
}
Bir "sözde işlem" oluşturmak için @Around ek açıklamasını kullanalım :
@Aspect
public class TransactionAspect{
@Pointcut("execution(* Main.performSomeOperation(String))")
public void executeOperation() {
}
@Around(value = "executeOperation()")
public void beforeAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("Opening a transaction...");
try {
joinPoint.proceed();
System.out.println("Closing a transaction...");
}
catch (Throwable throwable) {
System.out.println("The operation failed. Rolling back the transaction...");
}
}
}
ProceedingJoinPoint nesnesinin devam yöntemi ile tavsiyedeki yerini belirlemek için sarma yöntemini çağırıyoruz. Bu nedenle, yukarıdaki yöntemdeki kod joinPoint.proceed(); Önce'dir , altındaki kod ise Sonra'dır . main komutunu çalıştırırsak , bunu konsolda alırız:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
Sonra şu konsol çıktısını alırız:
Örnek 3
Bir sonraki örneğimizde, konsola giriş yapmak gibi bir şey yapalım. Önce, sözde iş mantığı eklediğimiz Main'e bir göz atın :public class Main {
private String value;
public static void main(String[] args) throws Exception {
Main main = new Main();
main.setValue("<some value>");
String valueForCheck = main.getValue();
main.checkValue(valueForCheck);
}
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
public void checkValue(String value) throws Exception {
if (value.length() > 10) {
throw new Exception();
}
}
}
main içinde , değer örneği değişkenine bir değer atamak için setValue kullanırız . Sonra değeri almak için getValue'yu kullanırız ve ardından 10 karakterden uzun olup olmadığını görmek için checkValue'u çağırırız. Eğer öyleyse, o zaman bir istisna atılacaktır. Şimdi metotların çalışmasını loglamak için kullanacağımız yönüne bakalım:
@Aspect
public class LogAspect {
@Pointcut("execution(* *(..))")
public void methodExecuting() {
}
@AfterReturning(value = "methodExecuting()", returning = "returningValue")
public void recordSuccessfulExecution(JoinPoint joinPoint, Object returningValue) {
if (returningValue != null) {
System.out.printf("Successful execution: method — %s method, class — %s class, return value — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
returningValue);
}
else {
System.out.printf("Successful execution: method — %s, class — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName());
}
}
@AfterThrowing(value = "methodExecuting()", throwing = "exception")
public void recordFailedExecution(JoinPoint joinPoint, Exception exception) {
System.out.printf("Exception thrown: method — %s, class — %s, exception — %s\n",
joinPoint.getSignature().getName(),
joinPoint.getSourceLocation().getWithinType().getName(),
exception);
}
}
Burada neler oluyor? @Pointcut("execution(* *(..))") tüm yöntemlerin tüm çağrılarına katılacak. @AfterReturning(value = "methodExecuting()", return = "returningValue"), hedef yöntemin başarıyla yürütülmesinden sonra yürütülecek tavsiyedir. Burada iki durumumuz var:
- Yöntemin bir dönüş değeri olduğunda — if (returningValue! = Null) {
- Dönen değer olmadığında — başka {