Давайте разгадаем тайны АОП и технологии динамических прокси

Java

Я Кайт, программист с глубиной и широтой, и пастырский кодер, который планировал писать стихи, но написал код!

Статьи будут включены вJavaNewBeeТакже есть карта знаний Java back-end, и в ней путь от Xiaobai до Daniel. Официальный аккаунт отвечает на «666», чтобы получить изображение высокой четкости.

Серия статей Spring Cloud завершена, и вы можете перейти кмой гитхабПолную серию смотрите на . Официальный аккаунт отвечает на «pdf», чтобы получить версию в формате pdf.

Принцип базовой технологии Spring IOC был представлен ранее.Статья "Package You Know Series" четко объясняет принцип и процесс реализации Spring IoC., Сегодня я представлю еще одну базовую технологию АОП и принцип ее реализации: технологию динамического прокси.

Что такое АОП

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

Почему лицом к лицу, разве мне не полезно изменить исходный код напрямую? Конечно без проблем, если ситуация позволяет. Но учитывая следующие ситуации, я изначально написал 1000 методов. Однажды я хочу добавить некоторые элементы управления. Я хочу проверить некоторые системные параметры перед выполнением логики метода. Если нет проблем с проверкой параметров, логика будет выполнена. , иначе он не будет выполнен. Что мне делать в этой ситуации, нужно ли модифицировать эти 1000 методов, это будет катастрофа. Кроме того, некоторая онлайн-логика выполняется медленно, но я не хочу перераспределять среду, потому что это повлияет на онлайн-бизнес.В этом случае также можно рассмотреть АОП.Btrace является таким артефактом для онлайн-проверки производительности.

Использование Spring АОП

Аспектно-ориентированное программирование, название кажется крутым, но использование было инкапсулировано Spring очень просто, и требуется только простая настройка. Метод использования не является предметом этой статьи.Далее демонстрируется только самое простое и основное использование, которое реализует трудоемкие вычисления вызываемого метода и распечатывает его.

Описание среды: JDK 1.8, Spring mvc версии 4.3.2.RELEASE

1. Во-первых, обратитесь к пакету maven, связанному с Spring mvc. Если их слишком много, он не будет указан. Будет указан только соответствующий Spring-aop.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version> 4.3.2.RELEASE </version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

2. Добавьте конфигурацию AOP в файл конфигурации Spring mvc следующим образом:

<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true"
 xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="  
       http://www.springframework.org/schema/beans   
       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://www.springframework.org/schema/mvc   
       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
       http://www.springframework.org/schema/context  
       http://www.springframework.org/schema/context/spring-context-4.3.xsd
       http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

 <!-- 自动扫描与装配bean -->
 <context:component-scan base-package="kite.lab.spring"></context:component-scan>
           <!-- 启动 @AspectJ 支持 -->
 <aop:aspectj-autoproxy proxy-target-class="true" />
</bean>

3. Создайте класс аспекта и установите аспект в методе в пакете kite.lab.spring.service, используйте@AroundМониторинг аннотаций, чтобы реализовать расчет и вывод времени выполнения, содержание выглядит следующим образом:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Component
@Aspect
public class PerformanceMonitor {
    
    //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
    @Pointcut("execution(* kite.lab.spring.service..*(..))")
    public void aspect(){ }

    @Around("aspect()")
    public Object methodTime(ProceedingJoinPoint pjp) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 开始
        Object retVal = pjp.proceed();
        stopWatch.stop();
        // 结束
        System.out.println(String.format("方法 %s 耗时 %s ms!",pjp.getSignature().toShortString(), stopWatch.getTotalTimeMillis()));
        return retVal;
    }
}

4. Классы, отслеживаемые аспектом, определяются следующим образом:

package kite.lab.spring.service;

public class Worker {
    public String dowork(){
        System.out.println("生活向来不易,我正在工作!");
        return "";
    }
}

5. Загрузите файл конфигурации Spring mvc и вызовите метод класса Worker.

public static void main(String[] args) {
        String filePath = "spring-servlet.xml"; 
        ApplicationContext ac = new FileSystemXmlApplicationContext(filePath);
        Worker worker = (Worker) ac.getBean("worker");
        worker.dowork();
    }

6. Результаты отображения следующие:

После знакомства с употреблением давайте снимем великолепные одежды АОП, нет, снимем ее таинственную маску, взглянем на ее красоту и поймем ее сердце.

Принцип весеннего АОП

Принцип реализации АОП заключается в динамическом создании прокси-класса.Процесс выполнения прокси-класса: выполнить добавленный нами код (например, ведение журнала методов) --> вызвать исходный метод --> добавленную логику кода. Легче понять, взглянув на картинку:

Динамический прокси-сервер Spring AOP может использовать динамический прокси-сервер JDK или CGlib для динамического создания классов прокси.Используется динамический прокси-сервер JDK, в противном случае используется режим динамического прокси механизма генерации байт-кода CGlib.

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

Прочитав определение, я чувствую, что оно может решить проблему, решаемую методом аспектного программирования. На следующем рисунке показана базовая диаграмма шаблона статического прокси:

Динамический прокси означает, что класс реализации прокси создается динамически во время выполнения.Из-за механизма JVM необходимо напрямую манипулировать байт-кодом для создания нового файла байт-кода, то есть файла .class.

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

Режим динамического прокси JDK использует структуру байт-кода Sun ProxyGenerator. Следует отметить, что только классы, реализующие интерфейс, могут использовать технологию динамического прокси JDK, которая относительно проста в реализации.

1. Просто реализуйте интерфейс InvocationHandler и переопределите метод вызова. Конкретный код реализации выглядит следующим образом:

package kite.lab.spring.aop.jdkaop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JdkProxy
 *
 * @author fengzheng
 */
public class JdkProxy implements InvocationHandler {

    private Object target;

    /**
     * 绑定委托对象并返回一个代理类
     *
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        //取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }


    /**
     * 调用方法
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result = null;
        System.out.println("事物开始");
        //执行方法
        result = method.invoke(target, args);
        System.out.println("事物结束");
        return result;
    }
}

Метод Proxy.newProxyInstance используется для динамического создания фактически сгенерированного прокси-класса.Три параметра: загрузчик класса прокси-класса, интерфейс, реализованный прокси-классом, и текущий перехватчик прокси.

Мы можем добавить бизнес-логику, которую мы добавили в переопределенный вызов, а затем вызвать исходный метод.

2. Прокси-класс по-прежнему использует рабочий класс, представленный Spring aop, но нам нужно, чтобы этот класс реализовал свой собственный интерфейс, который определяется следующим образом:

package kite.lab.spring.service;

/**
 * IWorker
 *
**/
public interface IWorker {
    String dowork();
}

3. Фактический вызов выглядит следующим образом:

 public static void main(String[] args) {
        JdkProxy jdkProxy = new JdkProxy();
        IWorker worker = (IWorker) jdkProxy.bind(new Worker());
        worker.dowork();
}

Описание принципа: jdkProxy.bind сгенерирует фактический прокси-класс. Этот процесс генерации использует технологию генерации байт-кода. Сгенерированный прокси-класс реализует интерфейс IWorker. Когда мы вызываем метод dowork этого прокси-класса, прокси-класс фактически находится в прокси В середине вызывается метод вызова JdkProxy (то есть реализованный нами прокси-перехватчик), а затем выполняется реализованный нами метод вызова, который также выполняет добавленную нами логику, тем самым реализуя требования аспектного программирования.

Давайте декомпилируем динамически сгенерированный файл байт-кода прокси-класса, и мы разберемся. Из-за длинного кода извлекаются только соответствующие части.

Сначала посмотрите на интерфейс и отношения наследования класса:

public final class $Proxy0 extends Proxy implements IWorker

Класс прокси называется $Proxy0, наследует Proxy и реализует IWorker, что является ключевым моментом.

Найдите метод dowork, код следующий:

public final String dowork() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

super.h — это класс JdkProxy, который мы реализовали.Вы можете видеть, что вызывается метод invoke этого класса, и передается параметр m3. Давайте посмотрим, что такое m3.

m3 = Class.forName("kite.lab.spring.service.IWorker")
               .getMethod("dowork", new Class0);

Как видите, м3 — это доворк метод, и процесс понятен.

Однако не все проксируемые классы (аспектируемые классы) реализуют определенный интерфейс. Если интерфейс не реализован, динамический прокси JDK не будет работать. В настоящее время будет использоваться фреймворк байт-кода CGlib.

Динамический прокси CGLIB

Библиотека CGlib использует ASM, облегченную, но высокопроизводительную среду для обработки байт-кода, которая позволяет преобразовывать байт-коды и динамически генерировать подклассы на основе класса во время выполнения. Удивительно, независимо от того, есть интерфейс или нет, все классы могут быть унаследованы, с этой функцией, в принципе, он может перехватить любой код класса, чтобы достичь цели аспектного программирования.

CGlib не требует от нас особых знаний о формате файлов байт-кода (файлы .class) и может реализовывать операции с байт-кодом через простой API.

Основываясь на таких характеристиках, CGlib широко используется в средах АОП, основанных на режиме прокси, таких как Spring AOP.

Ниже приведен простой режим динамического прокси на основе CGlib.

1. Создайте класс перехвата для реализации интерфейса MethodInterceptor и переопределите метод перехвата. Добавьте логику, которую мы добавили в этот метод. Код выглядит следующим образом:

public class MyAopWithCGlib implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("嘿,你在干嘛?");
        methodProxy.invokeSuper(o, objects);
        System.out.println("是的,你说的没错。");
        return null;
    }

2. Класс прокси по-прежнему является классом Worker, описанным выше, и интерфейс не требуется.

3. Код для вызова прокси-метода клиентом выглядит следующим образом:

 public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
        MyAopWithCGlib aop = new MyAopWithCGlib();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Worker.class);
        enhancer.setCallback(aop);
        Worker worker = (Worker) enhancer.create();
        worker.dowork();
    }

Первая строка кода предназначена для сохранения динамически сгенерированного файла байт-кода на диск для легкой декомпиляции и наблюдения.

Используйте объект Enhancer из CGlib, установите его родительский класс наследования, установите класс обратного вызова, то есть класс перехвата, реализованный выше, а затем используйте метод create для создания класса Worker, который фактически является подклассом класса Worker, и затем вызовите метод dowork. Результат выполнения следующий:

Вы можете видеть, что наш сплетенный код работает.

4. Приведенная выше функция относительно проста, она будет врезаться во все методы прокси-класса по горизонтали, а мы ее немного усложним. Контроль, пусть какие-то методы вплетены в код, какие-то не вплетены в код, имитируем Spring aop, добавляем аннотацию, чтобы аннотировать, какие методы нужно вырезать по горизонтали. Примечания следующие:

package kite.lab.spring.aop.AopWithCGlib;

import java.lang.annotation.*;

/**
 * CGLIB
 *
 * @author fengzheng
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CGLIB {
    String value() default "";
}

5. Затем добавьте метод в Worker и примените приведенные выше аннотации.

package kite.lab.spring.service;

import kite.lab.spring.aop.AopWithCGlib.CGLIB;

/**
 * Worker
 *
 * @author fengzheng
 */
public class Worker  {
    public String dowork(){
        System.out.println("生活向来不易,我正在工作!");
        return "";
    }

    @CGLIB(value = "cglib")
    public void dowork2(){
        System.out.println("生活如此艰难,我在奔命!");
    }
}

Мы применили вышеуказанные аннотации к dowrok2

6. Добавьте логику оценки аннотаций в метод перехвата.Если указанная выше аннотация добавлена, добавляется логика вплетенного кода, в противном случае она не добавляется.Код выглядит следующим образом:

@Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Annotation[] annotations = method.getDeclaredAnnotations();
        boolean isCglib = false;
        for(Annotation annotation: annotations){
            if (annotation.annotationType().getName().equals("kite.lab.spring.aop.AopWithCGlib.CGLIB")){
                isCglib = true;
            }
        }
        if(isCglib) {
            System.out.println("嘿,你在干嘛?");
            methodProxy.invokeSuper(o, objects);
            System.out.println("是的,你说的没错。");
        }
        return null;
    }

7. Метод вызова следующий:

public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "cglib");
        MyAopWithCGlib aop = new MyAopWithCGlib();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Worker.class);
        enhancer.setCallback(aop);
        Worker worker = (Worker) enhancer.create();
        worker.dowork();

        worker.dowork2();
    }

Результатом выполнения должно быть то, что dowork не выполняет вплетенную логику, а dowork2 выполняет вплетенную логику кода.Результаты выполнения следующие:

Кроме того, CGlib не поддерживает конечные классы.Скорость выполнения CGlib относительно высокая, но скорость создания относительно низкая.Поэтому в сценарии, где применимы как динамические прокси, так и создается большое количество динамических прокси-классов, используйте режим динамического прокси JDK. , в противном случае можно использовать CGlib.

Стандартная среда Spring MVC обычно представляет собой класс интерфейса службы, соответствующий классу реализации, поэтому, согласно логике суждения Spring AOP, в большинстве случаев следует использовать режим динамического прокси JDK. Конечно, вы также можете вручную перейти в режим CGlib.

В конце концов

Освойте принцип реализации АОП, и вы освоите еще одну базовую технологию. Оглядываясь назад, это было довольно просто.


Не жди сильного мужика, сначала поставь лайк, меня вечно трахают зря, и мое тело этого не выдерживает!

Я коршун, общественный номер "Воздушный змей в древности". Программист-поощритель с глубиной и широтой, пастырский фермер-кодировщик, который планировал писать стихи, но написал код! Вы можете подписаться на меня сейчас или никогда не поздно читать исторические статьи.