Menerapkan AOP
Pemrograman berorientasi aspek dirancang untuk melakukan tugas lintas sektoral, yang dapat berupa kode apa pun yang dapat diulang berkali-kali dengan metode berbeda, yang tidak dapat sepenuhnya disusun menjadi modul terpisah. Karenanya, AOP memungkinkan kita menyimpan ini di luar kode utama dan mendeklarasikannya secara vertikal. Contohnya adalah menggunakan kebijakan keamanan dalam aplikasi. Biasanya, keamanan dijalankan melalui banyak elemen aplikasi. Terlebih lagi, kebijakan keamanan aplikasi harus diterapkan sama untuk semua bagian aplikasi yang ada dan yang baru. Pada saat yang sama, kebijakan keamanan yang digunakan dapat berkembang dengan sendirinya. Ini adalah tempat yang tepat untuk menggunakan AOP . Juga, contoh lain adalah logging. Ada beberapa keuntungan menggunakan pendekatan AOP untuk logging daripada menambahkan fungsional logging secara manual:-
Kode untuk masuk mudah untuk ditambahkan dan dihapus: yang perlu Anda lakukan hanyalah menambah atau menghapus beberapa konfigurasi dari beberapa aspek.
-
Semua kode sumber untuk logging disimpan di satu tempat, jadi Anda tidak perlu mencari secara manual semua tempat yang digunakan.
-
Kode logging dapat ditambahkan di mana saja, baik dalam metode dan kelas yang sudah ditulis atau dalam fungsionalitas baru. Ini mengurangi jumlah kesalahan pengkodean.
Selain itu, saat menghapus aspek dari konfigurasi desain, Anda dapat yakin bahwa semua kode penelusuran hilang dan tidak ada yang terlewatkan.
- Aspek adalah kode terpisah yang dapat diperbaiki dan digunakan berulang kali.
Prinsip dasar AOP
Untuk melangkah lebih jauh dalam topik ini, pertama mari kita mengenal konsep utama AOP. Nasihat — Logika atau kode tambahan yang dipanggil dari titik gabungan. Nasihat dapat dilakukan sebelum, sesudah, atau sebagai pengganti titik bergabung (lebih lanjut tentang mereka di bawah). Jenis saran yang mungkin :-
Sebelum — saran jenis ini diluncurkan sebelum metode target, yaitu poin gabungan, dijalankan. Saat menggunakan aspek sebagai kelas, kami menggunakan anotasi @Before untuk menandai saran yang akan datang sebelumnya. Saat menggunakan aspek sebagai file .aj , ini akan menjadi metode before() .
- After — saran yang dijalankan setelah eksekusi metode (gabung poin) selesai, baik dalam eksekusi normal maupun saat melempar pengecualian.
Saat menggunakan aspek sebagai kelas, kita dapat menggunakan anotasi @After untuk menunjukkan bahwa ini adalah saran yang muncul setelahnya.
Saat menggunakan aspek sebagai file .aj , ini adalah metode after() .
-
After Returning — saran ini dilakukan hanya ketika metode target selesai secara normal, tanpa kesalahan.
Saat aspek direpresentasikan sebagai kelas, kita dapat menggunakan anotasi @AfterReturning untuk menandai saran sebagai eksekusi setelah berhasil diselesaikan.
Saat menggunakan aspek sebagai file .aj , ini akan menjadi metode after() yang mengembalikan (Object obj) .
-
After Throwing — saran ini dimaksudkan untuk contoh ketika sebuah metode, yaitu, join point, melontarkan pengecualian. Kami dapat menggunakan saran ini untuk menangani beberapa jenis eksekusi yang gagal (misalnya, untuk memutar kembali seluruh transaksi atau mencatat dengan tingkat jejak yang diperlukan).
Untuk aspek kelas, anotasi @AfterThrowing digunakan untuk menunjukkan bahwa saran ini digunakan setelah melontarkan pengecualian.
Saat menggunakan aspek sebagai file .aj , ini akan menjadi metode after() throw (Exception e) .
-
Sekitar — mungkin salah satu jenis nasihat terpenting. Itu mengelilingi metode, yaitu, titik gabungan yang dapat kita gunakan untuk, misalnya, memilih apakah akan melakukan metode titik gabungan yang diberikan atau tidak.
Anda dapat menulis kode saran yang dijalankan sebelum dan sesudah metode join point dijalankan.
The around advice bertanggung jawab untuk memanggil metode join point dan mengembalikan nilai jika metode tersebut mengembalikan sesuatu. Dengan kata lain, dalam saran ini, Anda cukup mensimulasikan pengoperasian metode target tanpa memanggilnya, dan mengembalikan apa pun yang Anda inginkan sebagai hasil pengembalian.
Mengingat aspek sebagai kelas, kami menggunakan anotasi @Around untuk membuat saran yang menggabungkan titik gabung. Saat menggunakan aspek dalam bentuk file .aj , metode ini akan menjadi metode around() .
-
Menenun waktu kompilasi — jika Anda memiliki kode sumber aspek dan kode tempat Anda menggunakan aspek, maka Anda dapat mengompilasi kode sumber dan aspek secara langsung menggunakan kompiler AspectJ;
-
Anyaman pasca-kompilasi (anyaman biner) — jika Anda tidak dapat atau tidak ingin menggunakan transformasi kode sumber untuk merangkai aspek ke dalam kode, Anda dapat mengambil kelas atau file jar yang telah dikompilasi sebelumnya dan menyuntikkan aspek ke dalamnya;
-
Anyaman waktu muat — ini hanya anyaman biner yang ditunda hingga pemuat kelas memuat file kelas dan menentukan kelas untuk JVM.
Satu atau lebih pemuat kelas tenun diperlukan untuk mendukung ini. Mereka disediakan secara eksplisit oleh runtime atau diaktifkan oleh "agen penenun".
Contoh di Jawa
Selanjutnya, untuk pemahaman yang lebih baik tentang AOP , kita akan melihat contoh kecil bergaya "Hello World". Langsung saja, saya perhatikan bahwa contoh kita akan menggunakan waktu kompilasi tenun . Pertama, kita perlu menambahkan dependensi berikut di file pom.xml kita :<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
Sebagai aturan, kompiler ajc khusus adalah cara kami menggunakan aspek. IntelliJ IDEA tidak menyertakannya secara default, jadi saat memilihnya sebagai kompiler aplikasi, Anda harus menentukan jalur ke distribusi 5168 75 AspectJ . Ini adalah cara pertama. Yang kedua, yang saya gunakan, adalah mendaftarkan plugin berikut di file pom.xml :
<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>
Setelah ini, sebaiknya impor ulang dari Maven dan jalankan mvn clean compile . Sekarang mari kita lanjutkan langsung ke contoh.
Contoh No.1
Mari kita buat kelas Utama . Di dalamnya, kita akan memiliki titik masuk dan metode yang mencetak nama yang diteruskan di konsol: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);
}
}
Tidak ada yang rumit di sini. Kami memberikan nama dan menampilkannya di konsol. Jika kita menjalankan program sekarang, kita akan melihat yang berikut di konsol:
public aspect GreetingAspect {
pointcut greeting() : execution(* Main.printName(..));
before() : greeting() {
System.out.print("Hi, ");
}
}
File ini agak seperti kelas. Mari kita lihat apa yang terjadi di sini: pointcut adalah sekumpulan titik gabungan; salam() adalah nama dari pointcut ini; : eksekusi menunjukkan untuk menerapkannya selama eksekusi semua ( * ) panggilan metode Main.printName(...) . Selanjutnya muncul saran khusus — sebelum () — yang dijalankan sebelum metode target dipanggil. : greeting() adalah cutpoint yang ditanggapi oleh saran ini. Nah, di bawah ini kita melihat isi dari metode itu sendiri, yang ditulis dalam bahasa Jawa, yang kita pahami. Saat kita menjalankan main dengan aspek ini, kita akan mendapatkan keluaran konsol ini:
@Aspect
public class GreetingAspect{
@Pointcut("execution(* Main.printName(String))")
public void greeting() {
}
@Before("greeting()")
public void beforeAdvice() {
System.out.print("Hi, ");
}
}
Setelah file aspek .aj , semuanya menjadi lebih jelas di sini:
- @Aspect menunjukkan bahwa kelas ini adalah sebuah aspek;
- @Pointcut("execution(* Main.printName(String))") adalah cutpoint yang dipicu untuk semua panggilan ke Main.printName dengan argumen input yang bertipe String ;
- @Before("greeting()") adalah saran yang diterapkan sebelum memanggil kode yang ditentukan dalam titik potong greeting() .
Contoh No.2
Misalkan kita memiliki beberapa metode yang melakukan beberapa operasi untuk klien, dan kita memanggil metode ini dari main :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);
}
}
Mari gunakan anotasi @Around untuk membuat "transaksi semu":
@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...");
}
}
}
Dengan metode proses objek ProceedingJoinPoint , kami memanggil metode pembungkusan untuk menentukan lokasinya di saran. Oleh karena itu, kode dalam metode di atas joinPoint.proceed(); adalah Before , sedangkan kode dibawahnya adalah After . Jika kita menjalankan main , kita mendapatkan ini di konsol:
public static void performSomeOperation(String clientName) throws Exception {
System.out.println("Performing some operations for Client " + clientName);
throw new Exception();
}
Kemudian kami mendapatkan keluaran konsol ini:
Contoh No.3
Dalam contoh kita berikutnya, mari lakukan sesuatu seperti masuk ke konsol. Pertama, lihat Main , di mana kami telah menambahkan beberapa logika bisnis semu: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();
}
}
}
Di main , kami menggunakan setValue untuk menetapkan nilai ke variabel instance nilai . Kemudian kami menggunakan getValue untuk mendapatkan nilainya, dan kemudian kami memanggil checkValue untuk melihat apakah lebih dari 10 karakter. Jika demikian, maka pengecualian akan dilemparkan. Sekarang mari kita lihat aspek yang akan kita gunakan untuk mencatat pekerjaan metode:
@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);
}
}
Apa yang terjadi di sini? @Pointcut("execution(* *(..))") akan menggabungkan semua panggilan dari semua metode. @AfterReturning(value = "methodExecuting()", return = "returningValue") adalah saran yang akan dijalankan setelah eksekusi sukses dari metode target. Kami memiliki dua kasus di sini:
- Ketika metode memiliki nilai pengembalian — if (returningValue! = Null) {
- Ketika tidak ada nilai kembalian — else {