Фреймворк Freehand - реализация AOP

Java Spring Java EE

Оригинальный адрес:Блог о острых бобах

В предыдущей лекции мы объяснили реализацию IoC в Spring. Вы можете проверить мой блогнажмите на ссылку, в этой лекции мы продолжаем рассказывать о еще одной важной особенности Spring, АОП. Большинство руководств, которые я читал ранее, не очень подробно объясняли реализацию Spring Aop. В большинстве статей было представлено использование динамического прокси для базовой технологии Spring Aop. Что касается конкретной реализации Spring Aop, это неясно. После прочтения такой статьи в голове всплывает следующая картина:

Моя идея состоит в том, чтобы помочь вам сначала разобраться с реализацией Spring Aop, а затем скрыть детали и самостоятельно реализовать инфраструктуру Aop. Углубите свое понимание Spring Aop. Поняв шаги 1–4 выше, добавьте дополнительные сведения между шагами 4 и 5.

Прочитав эту статью, вы узнаете:

  • Что такое АОП?
  • Зачем использовать АОП?
  • Какова идея Spring по реализации АОП?
  • Реализовать структуру АОП в соответствии с мышлением Spring.

Что такое АОП?

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

Зачем вам нужно использовать АОП?

Аспектно-ориентированное программирование на самом деле представляет собой технологию, которая единообразно добавляет функции в исходную программу без изменения исходного кода посредством предварительной компиляции или технологии динамического прокси. Давайте рассмотрим несколько ключевых слов.Первое — «технология динамического прокси», которая является базовой технологией, реализованной в Spring Aop. Второе «не изменять исходный код», это самая важная часть АОП, которую мы обычно называем неинвазивной. . Третья «добавить функцию» добавляет функции в программу без изменения исходного кода.

Например: если вам нужно посчитать время выполнения нескольких методов за один день, если вы не используете технологию AOP, все, что вам нужно сделать, это получить время начала для каждого запуска метода и получить время окончания, когда метод заканчивается. Разница заключается во времени выполнения метода. Если вы сделаете это для каждого метода, которому нужна статистика, код станет катастрофой. Если мы используем метод Aop, не изменяя код, добавим кусочек времени выполнения статистического метода. Код становится очень элегантным. Как достичь этого аспекта конкретно? Вы узнаете, прочитав статью ниже.

Как реализован Spring Aop?

Так называемый:

компьютерная программа = структура данных + алгоритм

Прочитав исходный код Spring, вы поймете это утверждение более глубоко.

Код, реализованный Spring Aop, очень и очень извилист. Другими словами, Spring сделал очень глубокую абстракцию для обеспечения гибкости. В то же время Spring совместим с@AspectJПротокол Aop использует множество шаблонов адаптера (адаптера) для дальнейшего увеличения сложности кода. Реализация Spring Aop в основном включает следующие шаги:

  1. Инициализируйте контейнер Aop.
  2. Прочтите файл конфигурации.
  3. Замените файл конфигурации с помощью структуры данных, которую AOP может распознать -Advisor. Давайте поговорим об этом советнике здесь. Объект Advisor содержит две важные структуры данных, одна из которыхAdvice,одинPointcut.AdviceРоль заключается в описании поведения аспекта,pointcutОписывает расположение среза. Комбинация двух узлов данных — это «где, что». такAdvisorВключая информацию «Где и какую» вы можете полностью описать аспект.
  4. Spring преобразует этот советник в структуру данных, которую он может распознать —AdvicedSupport. Spring динамически вплетает эти перехватчики методов в соответствующие методы.
  5. Создавайте динамические прокси-серверы.
  6. Обеспечивает вызов. При использовании вызывающая сторона вызывает метод прокси. То есть методы, в которые уже вплетены методы улучшения.

Реализуйте структуру АОП самостоятельно.

Точно так же я также имею в виду дизайн Aop. Реализованы только перехватчики на основе методов. Многие детали реализации были удалены.

Управляйте объектами с помощью инфраструктуры IoC из предыдущей лекции. Используйте Cglib в качестве базового класса для динамических прокси. Используйте maven для управления пакетами и модулями jar. Таким образом, упомянутый выше фреймворк IoC будет внедрен в проект в виде модуля.

Теперь давайте реализуем нашу структуру Aop.

Давайте сначала посмотрим на основную структуру кода.

Структура кода намного сложнее, чем упомянутый выше IoC. Сначала мы кратко расскажем о том, что делает каждый пакет.

  • invocationОписывает вызов метода. Обратите внимание, что это относится к «вызову метода», а не к вызову действия.
  • interceptorСамый знакомый перехватчик, цель перехватчика перехватитьinvcationзвонки внутри пакета.
  • advisorОбъекты в этом пакете — это все структуры данных, используемые для описания аспектов.
  • adapterВнутри этого пакета есть несколько методов адаптера. Для учащихся, которые не знают «Адаптер», вы можете перейти к «Режиму адаптации» в «Режиме дизайна». Его роль заключается вadviceОбъекты в пакете адаптированы какinterceptor.
  • beanОбъект, описывающий наш конфигурационный файл json.
  • coreОсновная логика нашего фреймворка.

На данный момент мы примерно разобрались с маршрутом с точки зрения макроса.adaperбудетadvisorадаптирован кinterceptorперехватыватьinvoction.

Начнем с самого конца цепочки:

invcation

во-первыхMethodInvocationкак интерфейс для всех вызовов методов. Для описания вызова метода состоит из трех методов, получить сам методgetMethod, получить параметры методаgetArguments, и сам метод executeproceed().

public interface MethodInvocation {
    Method getMethod();
    Object[] getArguments();
    Object proceed() throws Throwable;
}

ProxyMethodInvocationКак видно из названия, это вызов метода прокси, и добавлен метод для получения прокси.

public interface ProxyMethodInvocation extends MethodInvocation {
    Object getProxy();
}

interceptor

AopMethodInterceptorЭто интерфейс, который должны реализовать все перехватчики контейнера Aop:

public interface AopMethodInterceptor {
    Object invoke(MethodInvocation mi) throws Throwable;
}

При этом мы реализовали два перехватчикаBeforeMethodAdviceInterceptorиAfterRunningAdviceInterceptor, как следует из названия, первый перехватывается до выполнения метода, а второй перехватывается после выполнения метода:

public class BeforeMethodAdviceInterceptor implements AopMethodInterceptor {
    private BeforeMethodAdvice advice;
    public BeforeMethodAdviceInterceptor(BeforeMethodAdvice advice) {
        this.advice = advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        advice.before(mi.getMethod(),mi.getArguments(),mi);
        return mi.proceed();
    }
}
public class AfterRunningAdviceInterceptor implements AopMethodInterceptor {
    private AfterRunningAdvice advice;

    public AfterRunningAdviceInterceptor(AfterRunningAdvice advice) {
        this.advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object returnVal = mi.proceed();
        advice.after(returnVal,mi.getMethod(),mi.getArguments(),mi);
        return returnVal;
    }
}

Глядя на приведенный выше код, мы обнаруживаем, что на самом делеmi.proceed()Это реализация оригинального метода. иadviceКак мы сказали выше, это структура данных, которая описывает, что «делает» расширенный метод, поэтому для этого перед перехватчиком мы помещаем расширенный метод, соответствующий совету, перед методом, который фактически выполняется. Для перехватчика after он помещается после метода, который фактически выполняется.

На этот раз давайте рассмотрим самое важноеReflectioveMethodeInvocation

public class ReflectioveMethodeInvocation implements ProxyMethodInvocation {
    public ReflectioveMethodeInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList) {
        this.proxy = proxy;
        this.target = target;
        this.method = method;
        this.arguments = arguments;
        this.interceptorList = interceptorList;
    }

    protected final Object proxy;

    protected final Object target;

    protected final Method method;

    protected Object[] arguments = new Object[0];

    //存储所有的拦截器
    protected final List<AopMethodInterceptor> interceptorList;

    private int currentInterceptorIndex = -1;

    @Override
    public Object getProxy() {
        return proxy;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    @Override
    public Object proceed() throws Throwable {

        //执行完所有的拦截器后,执行目标方法
        if(currentInterceptorIndex == this.interceptorList.size() - 1) {
            return invokeOriginal();
        }

        //迭代的执行拦截器。回顾上面的讲解,我们实现的拦击都会执行 im.proceed() 实际上又会调用这个方法。实现了一个递归的调用,直到执行完所有的拦截器。
        AopMethodInterceptor interceptor = interceptorList.get(++currentInterceptorIndex);
        return interceptor.invoke(this);

    }

    protected Object invokeOriginal() throws Throwable{
        return ReflectionUtils.invokeMethodUseReflection(target,method,arguments);
    }

}

На практике наш метод, вероятно, будет улучшен несколькими перехватчиками методов. Поэтому мы используем список для хранения всех перехватчиков. Поэтому нам нужно рекурсивно добавлять перехватчики. Когда все перехватчики обработаны, фактически вызывается расширенный метод. Мы можем думать, что здесь происходит описанный выше код динамического плетения.

public class CglibMethodInvocation extends ReflectioveMethodeInvocation {

    private MethodProxy methodProxy;

    public CglibMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, List<AopMethodInterceptor> interceptorList, MethodProxy methodProxy) {
        super(proxy, target, method, arguments, interceptorList);
        this.methodProxy = methodProxy;
    }

    @Override
    protected Object invokeOriginal() throws Throwable {
        return methodProxy.invoke(target,arguments);
    }
}

CglibMethodInvocationтолько что переписанinvokeOriginalметод. Используйте прокси-класс для вызова расширенного метода.

advisor

Этот пакет содержит несколько структур данных, описывающих аспекты, и мы объясняем две важные из них.

@Data
public class Advisor {
    //干什么
    private Advice advice;
    //在哪里
    private Pointcut pointcut;

}

Как было сказано выше, советник описывает, где и что делать.

@Data
public class AdvisedSupport extends Advisor {
    //目标对象
    private TargetSource targetSource;
    //拦截器列表
    private List<AopMethodInterceptor> list = new LinkedList<>();

    public void addAopMethodInterceptor(AopMethodInterceptor interceptor){
        list.add(interceptor);
    }

    public void addAopMethodInterceptors(List<AopMethodInterceptor> interceptors){
        list.addAll(interceptors);
    }

}

этоAdvisedSupportЭто структура данных, которую может понять наш AOP-фреймворк.В настоящее время проблема становится — для какой цели, какие перехватчики следует добавить.

core

После вышеуказанных приготовлений мы начинаем объяснять основную логику.

@Data
public class CglibAopProxy implements AopProxy{
    private AdvisedSupport advised;
    private Object[] constructorArgs;
    private Class<?>[] constructorArgTypes;
    public CglibAopProxy(AdvisedSupport config){
        this.advised = config;
    }

    @Override
    public Object getProxy() {
        return getProxy(null);
    }
    @Override
    public Object getProxy(ClassLoader classLoader) {
        Class<?> rootClass = advised.getTargetSource().getTagetClass();
        if(classLoader == null){
            classLoader = ClassUtils.getDefultClassLoader();
        }
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(rootClass.getSuperclass());
        //增加拦截器的核心方法
        Callback callbacks = getCallBack(advised);
        enhancer.setCallback(callbacks);
        enhancer.setClassLoader(classLoader);
        if(constructorArgs != null && constructorArgs.length > 0){
            return enhancer.create(constructorArgTypes,constructorArgs);
        }
        return enhancer.create();
    }
    private Callback getCallBack(AdvisedSupport advised) {
        return new DynamicAdvisedIcnterceptor(advised.getList(),advised.getTargetSource());
    }
}

CglibAopProxyЭто основной метод генерации нашего прокси-объекта. Сгенерируйте прокси-классы, используя cglib. Мы можем работать с кодом предыдущего фреймворка ioc. Разница обнаруживается в сравнении:

    Callback callbacks = getCallBack(advised);
    enhancer.setCallback(callbacks);

Обратный вызов отличается от предыдущего, ноgetCallback()метод, давайте взглянем на getCallback внутриDynamicAdvisedIcnterceptorЧто ты сделал.

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

public class DynamicAdvisedInterceptor implements MethodInterceptor{

    protected final List<AopMethodInterceptor> interceptorList;
    protected final TargetSource targetSource;

    public DynamicAdvisedInterceptor(List<AopMethodInterceptor> interceptorList, TargetSource targetSource) {
        this.interceptorList = interceptorList;
        this.targetSource = targetSource;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);
        return invocation.proceed();
    }
}

Обратите внимание,DynamicAdvisedInterceptorMethodInterceptor, реализованный этим классом, является интерфейсом gclib, а не нашим предыдущим AopMethodInterceptor.

Когда мы внимательно смотрим на метод перехвата, мы видим:

MethodInvocation invocation = new CglibMethodInvocation(obj,targetSource.getTagetObject(),method, args,interceptorList,proxy);

С этой строкой кода вся наша логика окончательно связана. То есть этот динамический перехватчик, который проводит нас черезCglibMethodInvocationВплетая метод расширения кода, делегирует cglib для создания прокси-объектов.

На данный момент основная функция нашего АОП реализована.

AopBeanFactoryImpl

public class AopBeanFactoryImpl extends BeanFactoryImpl{

    private static final ConcurrentHashMap<String,AopBeanDefinition> aopBeanDefinitionMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,Object> aopBeanMap = new ConcurrentHashMap<>();

    @Override
    public Object getBean(String name) throws Exception {
        Object aopBean = aopBeanMap.get(name);

        if(aopBean != null){
            return aopBean;
        }
        if(aopBeanDefinitionMap.containsKey(name)){
            AopBeanDefinition aopBeanDefinition = aopBeanDefinitionMap.get(name);
            AdvisedSupport advisedSupport = getAdvisedSupport(aopBeanDefinition);
            aopBean = new CglibAopProxy(advisedSupport).getProxy();
            aopBeanMap.put(name,aopBean);
            return aopBean;
        }

        return super.getBean(name);
    }
    protected void registerBean(String name, AopBeanDefinition aopBeanDefinition){
        aopBeanDefinitionMap.put(name,aopBeanDefinition);
    }

    private AdvisedSupport getAdvisedSupport(AopBeanDefinition aopBeanDefinition) throws Exception {

        AdvisedSupport advisedSupport = new AdvisedSupport();
        List<String> interceptorNames = aopBeanDefinition.getInterceptorNames();
        if(interceptorNames != null && !interceptorNames.isEmpty()){
            for (String interceptorName : interceptorNames) {

                Advice advice = (Advice) getBean(interceptorName);

                Advisor advisor = new Advisor();
                advisor.setAdvice(advice);

                if(advice instanceof BeforeMethodAdvice){
                    AopMethodInterceptor interceptor = BeforeMethodAdviceAdapter.getInstants().getInterceptor(advisor);
                    advisedSupport.addAopMethodInterceptor(interceptor);
                }

                if(advice instanceof AfterRunningAdvice){
                    AopMethodInterceptor interceptor = AfterRunningAdviceAdapter.getInstants().getInterceptor(advisor);
                    advisedSupport.addAopMethodInterceptor(interceptor);
                }

            }
        }

        TargetSource targetSource = new TargetSource();
        Object object = getBean(aopBeanDefinition.getTarget());
        targetSource.setTagetClass(object.getClass());
        targetSource.setTagetObject(object);
        advisedSupport.setTargetSource(targetSource);
        return advisedSupport;

    }

}

AopBeanFactoryImplЭто фабричный класс, который генерирует прокси-объекты и наследует BeanFactoryImpl контейнера IoC, который мы реализовали в предыдущей главе. Переписан метод getBean.Если это прокси-класс аспекта, мы используем фреймворк Aop для генерации прокси-класса.Если это обычный объект, мы используем исходный контейнер IoC для внедрения зависимостей.getAdvisedSupportЭто нужно для получения структуры данных, распознаваемой фреймворком Aop.

Остальные классы, которые не были упомянуты, относительно просты, достаточно посмотреть исходный код. Ничего общего с базовой логикой.

написать метод для проверки

Нам нужно подсчитать время выполнения метода. Что мы должны делать перед лицом этого требования?

public class StartTimeBeforeMethod implements BeforeMethodAdvice{
    @Override
    public void before(Method method, Object[] args, Object target) {
        long startTime = System.currentTimeMillis();
        System.out.println("开始计时");
        ThreadLocalUtils.set(startTime);
    }
}
public class EndTimeAfterMethod implements AfterRunningAdvice {
    @Override
    public Object after(Object returnVal, Method method, Object[] args, Object target) {
        long endTime = System.currentTimeMillis();
        long startTime = ThreadLocalUtils.get();
        ThreadLocalUtils.remove();
        System.out.println("方法耗时:" + (endTime - startTime) + "ms");
        return returnVal;
    }
}

Перед запуском метода запишите время и сохраните его в ThredLocal.По завершении метода запишите время и распечатайте разницу во времени. Полная статистика.

целевой класс:

public class TestService {
    public void testMethod() throws InterruptedException {
        System.out.println("this is a test method");
        Thread.sleep(1000);
    }
}

Конфигурационный файл:

[
  {
    "name":"beforeMethod",
    "className":"com.xilidou.framework.aop.test.StartTimeBeforeMethod"
  },
  {
    "name":"afterMethod",
    "className":"com.xilidou.framework.aop.test.EndTimeAfterMethod"
  },
  {
    "name":"testService",
    "className":"com.xilidou.framework.aop.test.TestService"
  },
  {
    "name":"testServiceProxy",
    "className":"com.xilidou.framework.aop.core.ProxyFactoryBean",
    "target":"testService",
    "interceptorNames":[
      "beforeMethod",
      "afterMethod"
    ]
  }
]

Тестовый класс:

public class MainTest {
    public static void main(String[] args) throws Exception {
        AopApplictionContext aopApplictionContext = new AopApplictionContext("application.json");
        aopApplictionContext.init();
        TestService testService = (TestService) aopApplictionContext.getBean("testServiceProxy");
        testService.testMethod();
    }
}

Конечный результат нашего исполнения:

开始计时
this is a test method
方法耗时:1015ms

Process finished with exit code 0

На данный момент структура Aop завершена.

постскриптум

Были объяснены две основные функции Spring, IoC и Aop, и я надеюсь, что вы сможете глубоко понять эти две функции благодаря двум написанным мной статьям.

Исходный код Spring действительно сложен, и его часто неприятно читать, но до тех пор, пока вы можете придерживаться его и применять некоторые эффективные методы. Все еще в состоянии понять код Spring. и получать от него питание.

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

Наконец

гитхаб:Github.com/ Диспетчерский центр 007 ...