Rumah  >  Artikel  >  Java  >  Membongkar Kerja Dalaman Spring AOP

Membongkar Kerja Dalaman Spring AOP

王林
王林asal
2024-09-07 06:34:36599semak imbas

Unveiling the Inner Workings of Spring AOP

Dalam siaran ini, kami akan menyahmistifikasi mekanik dalaman Pengaturcaraan Berorientasikan Aspek (AOP) pada Musim Bunga. Tumpuan akan diberikan pada memahami cara AOP mencapai kefungsian seperti pengelogan, sering dianggap sebagai satu bentuk "ajaib". Dengan menelusuri pelaksanaan teras Java, kita akan melihat bagaimana semuanya tentang pantulan Java, corak proksi dan anotasi dan bukannya apa-apa yang benar-benar ajaib.

Prasyarat

  • API Proksi Teras Java
  • API Refleksi
  • API Anotasi

Ini semua adalah sebahagian daripada pakej java.lang.reflect, java.lang.annotation dan javassist.util.proxy.

Mekanisme Teras

Di tengah-tengah Spring AOP terletak konsep objek proksi, pemintas kaedah dan pantulan. Pemain utama dalam corak ini ialah MethodHandler (atau pengendali invocation). Pengendali ini mengawal kelakuan objek proksi dengan memintas panggilan kaedah. Apabila kaedah digunakan pada proksi, ia akan dihantar melalui pengendali, di mana anotasi boleh disemak melalui refleksi. Berdasarkan anotasi yang digunakan, logik yang diperlukan (cth., pengelogan) boleh dilaksanakan sebelum, selepas, atau kecuali.

Memecahkannya

  1. Objek Proksi: Ini ialah objek yang dicipta secara dinamik yang sesuai untuk objek perniagaan sebenar anda, menghalakan panggilan kaedah melalui pengendali kaedah.
  2. Pengendali Invocation: Di sinilah keajaiban pemintasan berlaku. Menggunakan refleksi, pengendali boleh memeriksa anotasi yang terdapat pada kaedah sasaran dan mengubah tingkah laku sewajarnya.
  3. Anotasi Tersuai: Anda boleh menentukan anotasi tersuai, yang berfungsi sebagai penanda untuk mencetuskan fungsi tambahan seperti pengelogan, semakan keselamatan atau pengurusan transaksi.

Contoh: Katakan kita ingin menambah pengelogan sebelum dan selepas pelaksanaan kaedah tertentu. Daripada pengelogan pengekodan keras di mana-mana, kami boleh menganotasi kaedah dengan @BeforeMethod dan @AfterMethod. Pengendali kami memeriksa kaedah untuk anotasi ini dan menambahkan logik pengelogan yang sesuai secara dinamik.

Di bawah ialah kelas bagaimana Pengawal dan Perkhidmatan kelihatan seperti contoh kami.

WorkerController.java

package edu.pk.poc.aop.controller;

import edu.pk.poc.aop.annotation.AfterMethod;
import edu.pk.poc.aop.annotation.All;
import edu.pk.poc.aop.annotation.BeforeMethod;
import edu.pk.poc.aop.helper.ProxyFactory;
import edu.pk.poc.aop.service.Worker;
import edu.pk.poc.aop.service.WorkerService;
import edu.pk.poc.aop.service.WorkerServiceImpl;

public class WorkerController {
    WorkerService workerService = ProxyFactory.createProxy(WorkerServiceImpl.class);
    /**
     * This Method 1s annotated with @BeforeMethod and @AfterMethod, So the log statements
     * will be generated before and after method call.
     */
    @BeforeMethod
    @AfterMethod
    public void engageFullTimeWorker() throws Exception {
        Worker fullTimeWorker = new Worker();
        fullTimeWorker.setName("FullTime-Worker");
        fullTimeWorker.setPartTime(false);
        fullTimeWorker.setDuration(9);
        workerService.doWork(fullTimeWorker);
    }
    /**
     * This Method is annotated with @All, So the log statements will be generated before and after method call
     * along with exception if raised.
     */
    @All
    public void engagePartTimeWorker() throws Exception {
        Worker partTimeWorker = new Worker();
        partTimeWorker.setName("PartTime-Worker");
        partTimeWorker.setPartTime(true);
        partTimeWorker.setDuration(4);
        workerService.doWork(partTimeWorker);
    }
}

WorkerServiceImpl.java

package edu.pk.poc.aop.service;

import edu.pk.poc.aop.annotation.AfterMethod;

public class WorkerServiceImpl implements WorkerService {
    /**
     * Here this method is annotated with only @AfterMethod, So only log statement
     * will be generated after method call
     */
    @AfterMethod
    @Override
    public void doWork(Worker worker) throws Exception {
        if (worker.isPartTime()) {
            throw new Exception("Part time workers are not permitted to work.");
        }
        System.out.print("A full time worker is working for " + worker.getDuration() + " hours :: ");
        for (int i = 1; i < worker.getDuration(); i++) {
            System.out.print("* ");
        }
        System.out.println();
    }
}

Kelas ujian Main.java

package edu.pk.poc.aop.test;

import edu.pk.poc.aop.controller.WorkerController;
import edu.pk.poc.aop.helper.ProxyFactory;
import edu.pk.util.Logger;

public class Main {
    public static void main(String[] args) {
        WorkerController controller = ProxyFactory.createProxy(WorkerController.class);
        Logger logger = new Logger();
        try {
            System.out.println("Testing @BeforeMethod and @AfterMethod");
            System.out.println("-----------------------------------------");
            controller.engageFullTimeWorker();
            System.out.println("Testing @All");
            System.out.println("-----------------------------------------");
            controller.engagePartTimeWorker();
        } catch (Exception e) {
            logger.error("Exception caught in Main class");
        }
    }
}

Output

Testing @BeforeMethod and @AfterMethod
-----------------------------------------
>>> Entering into edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker()
A full time worker is working for 9 hours :: * * * * * * * * 
>>> Exiting from edu.pk.poc.aop.service.WorkerServiceImpl.doWork()
>>> Exiting from edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker()
Testing @All
-----------------------------------------
>>> Entering into edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker()
>>> Exception in edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker()
Exception caught in Main class

Bagaimana Ia Berfungsi

Apabila kaedah digunakan pada objek proksi, panggilan dipintas oleh pengendali, yang menggunakan pantulan untuk memeriksa semua anotasi pada kaedah sasaran. Berdasarkan anotasi tersebut, pengendali memutuskan sama ada untuk log masuk/keluar kaedah, log pengecualian atau langkau pengelogan sama sekali.

Begini cara anda boleh memvisualisasikannya:

  • Sebelum Pelaksanaan: Log masukan kaedah.
  • Selepas Pelaksanaan: Log keluar kaedah atau kejayaan.
  • Semua: Log masukan kaedah, kemasukan kaedah dan pengecualian jika dinaikkan. Tingkah laku dinamik ini menunjukkan bahawa Spring AOP memanfaatkan teras Java API daripada menggunakan beberapa helah ajaib.

Tentukan Anotasi

package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterMethod {

}
package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforeMethod {

}
package edu.pk.poc.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface All {

}

Tentukan Kilang Proksi

package edu.pk.poc.aop.helper;

/**
 * The {@code ProxyFactory} class is responsible for creating proxy objects using the Javassist library.
 * It allows for dynamic generation of proxies for classes or interfaces, with support for method interception.
 */
public class ProxyFactory {

    /**
     * A Javassist ProxyFactory instance used to generate proxy classes.
     */
    private static final javassist.util.proxy.ProxyFactory factory = new javassist.util.proxy.ProxyFactory();

    /**
     * Creates a proxy object for the given class or interface.
     * If the class is an interface, the proxy implements the interface.
     * If it's a concrete class, the proxy extends the class.
     *
     * @param <T>   the type of the class or interface for which the proxy is to be created
     * @param klass the {@code Class} object representing the class or interface to proxy
     * @return a proxy instance of the specified class or interface, or {@code null} if proxy creation fails
     */
    public static <T> T createProxy(Class<T> klass) {
        if (klass.isInterface())
            factory.setInterfaces(new Class[]{klass});
        else
            factory.setSuperclass(klass);
        try {
            return (T) factory.create(new Class<?>[0], new Object[0], new AOPLoggingMethodHandler());
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return null;
    }
}

Tentukan MethodHandler

package edu.pk.poc.aop.helper;

import edu.pk.poc.aop.annotation.AfterMethod;
import edu.pk.poc.aop.annotation.All;
import edu.pk.poc.aop.annotation.BeforeMethod;
import edu.pk.poc.aop.annotation.OnException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import edu.pk.util.Logger;
import javassist.util.proxy.MethodHandler;

public class AOPLoggingMethodHandler implements MethodHandler {

    private static final Logger logger = new Logger();

    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        if (proceed != null) { // Concrete Method
            Object result = null;
            String className = resolveClassName(self);
            try {
                if (isAnnotationPresent(thisMethod, BeforeMethod.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.info(">>> Entering into " + className + "." + thisMethod.getName() + "()");
                }
                result = proceed.invoke(self, args);
                if (isAnnotationPresent(thisMethod, AfterMethod.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.info(">>> Exiting from " + className + "." + thisMethod.getName() + "()");
                }
            } catch (Throwable t) {
                if (isAnnotationPresent(thisMethod, OnException.class) || isAnnotationPresent(thisMethod, All.class)) {
                    logger.error(">>> Exception in " + className + "." + thisMethod.getName() + "()");
                }
                throw t;
            }
            return result;
        }
        throw new RuntimeException("Method is Abstract");
    }

    private boolean isAnnotationPresent(Method method, Class klass) {
        Annotation[] declaredAnnotationsByType = method.getAnnotationsByType(klass);
        return declaredAnnotationsByType != null && declaredAnnotationsByType.length > 0;
    }

    private String resolveClassName(Object self) {
        String className = self.getClass().getName();
        if (className.contains("_$$")) {
            className = className.substring(0, className.indexOf("_$$"));
        }
        return className;
    }
}

Kesimpulan

Spring AOP ialah alat yang berkuasa untuk merentasi kebimbangan, tetapi ia tidak melakukan sesuatu yang revolusioner. Ia dibina di atas konsep teras Java seperti refleksi dan proksi, yang tersedia dalam bahasa itu sendiri. Dengan memahami perkara ini, anda boleh lebih menghargai cara Spring memudahkan mekanik peringkat rendah ini untuk kemudahan pembangun.

Atas ialah kandungan terperinci Membongkar Kerja Dalaman Spring AOP. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn