Глубокое понимание динамического прокси-сервера Spring AOP

задняя часть Spring API модульный тест

1. Spring AOP

Spring — это легкий контейнер, а основные концепции всей серии Spring — это IoC и AOP. Видно, что АОП является одним из ядер среды Spring, играет очень важную роль в приложении, а также является основой других компонентов Spring. АОП (аспектно-ориентированное программирование), то есть аспектно-ориентированное программирование, можно сказать, является дополнением и улучшением ООП (объектно-ориентированное программирование, объектно-ориентированное программирование). ООП вводит такие понятия, как инкапсуляция, наследование и полиморфизм, чтобы установить иерархию объектов, которая имитирует набор общих поведений. Однако ООП позволяет разработчикам определять вертикальные отношения, но не подходит для определения горизонтальных отношений, таких как функции регистрации.

В центре внимания этой статьи не базовые знания об АОП.Мы в основном рассматриваем механизм реализации нижнего уровня основной функции АОП: принцип реализации динамических агентов. Перехват АОП реализован динамическими агентами в Java. На основе целевого класса добавьте базовую логику, создайте расширенные целевые классы (логика поверхности среза или перед выполнением функции целевого класса, или когда функция целевого класса выполняется, или функция целевого класса выдает ненормальное время. Различное время обрезки Различные перехватчики, такие как BeforeAdViseInterceptor, AfteradviseInterceptor, ThrowsAdViseInterceptor и т. д.).

Так как же динамический прокси реализует вплетение логики аспекта (совета) в метод целевого класса? Ниже мы подробно представим и реализуем два динамических прокси, используемых в АОП.

В исходном коде АОП для реализации функции перехвата используются два динамических прокси: динамический прокси jdk и динамический прокси cglib. Оба метода существуют одновременно, каждый со своими преимуществами и недостатками. Динамический прокси jdk реализован механизмом отражения внутри java, а нижний слой динамического прокси cglib реализован asm. В целом, механизм отражения более эффективен в процессе генерации классов, тогда как asm более эффективен в связанном процессе выполнения после генерации класса (класс, сгенерированный asm, можно кэшировать для решения проблемы неэффективности генерации asm). процесс).

Давайте возьмем пример реализации этих двух методов по отдельности.

2. Динамический прокси JDK

2.1 Определение интерфейса и класса реализации

public interface OrderService {
    public void save(UUID orderId, String name);

    public void update(UUID orderId, String name);

    public String getByName(String name);
}

Вышеприведенный код определяет интерфейс перехваченного объекта, сквозную задачу. Следующий код реализует интерфейс перехваченного объекта.

public class OrderServiceImpl implements OrderService {

    private String user = null;

    public OrderServiceImpl() {
    }

    public OrderServiceImpl(String user) {
        this.setUser(user);
    }

	//...
	
    @Override
    public void save(UUID orderId, String name) {
        System.out.println("call save()方法,save:" + name);
    }

    @Override
    public void update(UUID orderId, String name) {
        System.out.println("call update()方法");
    }

    @Override
    public String getByName(String name) {
        System.out.println("call getByName()方法");
        return "aoho";
    }
}

2.2 Класс динамического прокси JDK

public class JDKProxy implements InvocationHandler {
	//需要代理的目标对象
    private Object targetObject;
    
    public Object createProxyInstance(Object targetObject) {
        this.targetObject = targetObject;
        return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(),
                this.targetObject.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//被代理对象
        OrderServiceImpl bean = (OrderServiceImpl) this.targetObject;
        Object result = null;
        //切面逻辑(advise),此处是在目标类代码执行之前
        System.out.println("---before invoke----");
        if (bean.getUser() != null) {
            result = method.invoke(targetObject, args);
        }
        System.out.println("---after invoke----");
        return result;
    }

	//...

}

Приведенный выше код реализует динамический прокси-класс JDKProxy, реализует интерфейс InvocationHandler и реализует метод вызова в интерфейсе. Когда клиент вызывает бизнес-метод прокси-объекта, прокси-объект выполняет метод вызова, а метод вызова делегирует вызов целевому объекту, что эквивалентно вызову метода целевого объекта. оценивается, и метод перехватывается.

2.3 Тестирование

public class AOPTest {
    public static void main(String[] args) {
        JDKProxy factory = new JDKProxy();
        //Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例  
        OrderService orderService = (OrderService) factory.createProxyInstance(new OrderServiceImpl("aoho"));
		//由动态生成的代理对象来orderService 代理执行程序
        orderService.save(UUID.randomUUID(), "aoho");
    }

}

Результат выглядит следующим образом:

---before invoke----
call save()方法,save:aoho
---after invoke----

3. Генерация байт-кода CGLIB

3.1 Классы для проксирования

CGLIB может генерировать прокси как для классов интерфейсов, так и для классов. В примере реализуйте прокси для класса.

public class OrderManager {
    private String user = null;

    public OrderManager() {
    }

    public OrderManager(String user) {
        this.setUser(user);
    }

	//...

    public void save(UUID orderId, String name) {
        System.out.println("call save()方法,save:" + name);
    }

    public void update(UUID orderId, String name) {
        System.out.println("call update()方法");
    }

    public String getByName(String name) {
        System.out.println("call getByName()方法");
        return "aoho";
    }
}

Реализация этого класса такая же, как и вышеприведенная реализация интерфейса, чтобы сохранить единство.

3.2 Класс динамического прокси CGLIB

public class CGLibProxy implements MethodInterceptor {
    	// CGLib需要代理的目标对象
    	private Object targetObject;

       public Object createProxyObject(Object obj) {
        this.targetObject = obj;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法 
        enhancer.setCallback(this);
        //增强后的目标类
        Object proxyObj = enhancer.create();
        // 返回代理对象
        return proxyObj;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args,
                            MethodProxy methodProxy) throws Throwable {
        Object obj = null;
        //切面逻辑(advise),此处是在目标类代码执行之前
        System.out.println("---before intercept----");
        obj = method.invoke(targetObject, args);
        System.out.println("---after intercept----");
        return obj;
    }
}

Выше реализован метод создания подкласса и метод прокси. Метод getProxy(SuperClass.class) создает прокси-объект, расширяя класс родительского класса путем ввода байт-кода родительского класса. Метод intercept() перехватывает все вызовы методов целевого класса, obj представляет экземпляр целевого класса, method является объектом отражения метода целевого класса, args является динамическим входным параметром метода, а methodProxy является экземпляр прокси-класса. method.invoke(targetObject, args) вызывает метод в родительском классе через прокси-класс.

3.3 Тестирование

public class AOPTest {
    public static void main(String[] args) {
        OrderManager order = (OrderManager) new CGLibProxy().createProxyObject(new OrderManager("aoho"));
        order.save(UUID.randomUUID(), "aoho");
    }

Результат выглядит следующим образом:

---before intercept----
call save()方法,save:aoho
---after intercept----

4. Резюме

В этой статье в основном рассказывается о двух способах реализации динамического прокси-сервера Spring Aop и соответственно представляются их преимущества и недостатки. Предпосылка применения динамического прокси jdk заключается в том, что целевой класс основан на унифицированном интерфейсе. Без этой предпосылки нельзя применить динамический прокси-сервер jdk. Видно, что динамический прокси jdk имеет определенные ограничения.Динамический прокси, реализованный сторонней библиотекой классов, такой как cglib, более широко используется и имеет больше преимуществ в эффективности.

Механизм динамического прокси-сервера JDK представляет собой механизм делегирования. Он не требует сторонней библиотеки. Пока требуется среда JDK, прокси-сервер можно использовать для динамической реализации класса интерфейса. В динамически сгенерированном классе реализации обработчик делегируется для вызова исходного метода класса реализации; CGLib должен полагаться на Библиотека классов CGLib использует механизм наследования, который представляет собой отношение наследования между прокси-классом и прокси-классом, поэтому прокси-класс может быть назначен прокси-классу. Если прокси-класс имеет интерфейс, прокси-класс также может быть назначен интерфейсу.

Подписывайтесь на свежие статьи, приглашаю обратить внимание на мой публичный номер

微信公众号

Ссылаться на

  1. Исследование принципа динамического прокси-прокси jdk и прокси-сервера cglib
  2. Базовая реализация АОП — динамический прокси-сервер CGLIB и динамический прокси-сервер JDK.