[Фэй Чао] Прошлое и настоящее Dubbo-SPI и AOP

Java задняя часть
[Фэй Чао] Прошлое и настоящее Dubbo-SPI и AOP

предисловие

Эта статьяspiЧетвертая частьspiувеличился вAOP, или то же самое, что и предыдущий, давайте сначала введем АОП из знакомого спринга.

AOPЭто общая тема, и идеи не могут быть достигнуты в одночасье.Например, архитектурный дизайн начинается сAll in OneприбытьSOAЭто также постепенный процесс эволюции, поэтому в этой статье также говорится о процессе идеологической эволюции этого АОП.

Вопросы для интервью в прямом эфире

  • Вы упомянули, что spi в даббо тоже увеличилсяAOP, тогда скажите мне, какой шаблон проектирования используется и как это делает dubbo.

Сразу к делу

если мы возьмемAOPсамые распространенные сценарии事务Итак, каков был наш первоначальный подход?

Простой способ

public class EmployeeServiceImpl implements IEmployeeService {

    private TransactionManager txManager;

    @Override
    public void save() {
        try {
            txManager.begin();
            System.out.println("保存操作");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            txManager.begin();
            System.out.println("更新操作");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }
}

Проблемы с этими кодами очевидны, например

  • Много дублирования кода для обработки транзакций
  • Согласно идее разделения ответственности, в бизнес-методах нужно обрабатывать только бизнес-функции, а транзакции не обрабатывать.

Первое, о чем мы думаем для оптимизации кода, это шаблон проектирования, затем мы вводим следующую оптимизацию.

декоративный узор

public class APP {

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.update();
    }
}

Украшая шаблон проектирования, мы решаем две проблемы, с которыми столкнулись выше, но также вносим новые проблемы, на стороне клиента мы выставляем реальный объектEmployeeServiceImpl, это очень небезопасно, так можем ли мы скрыть настоящий объект, чтобы пользователь его не видел?

статический прокси

Таким образом, реальный объект в определенной степени скрыт от пользователя, но это также приводит к новым проблемам.

  • Если методов, которые необходимо проксировать, много, то каждый из них должен быть обработан, например, обрабатывается только фигура.saveметод, в случае, если методов много, его нужно обрабатывать много раз
  • После того, как интерфейс добавляет метод, помимо того, что все реализующие классы должны реализовать этот метод, все прокси-классы также должны реализовать этот метод (EmployeeServiceImplа такжеEmployeeServiceImplProxyдолжны быть изменены), что увеличивает сложность обслуживания кода
  • Интерфейс прокси-объекта обслуживает только определенный тип объекта, напримерEmployeeServiceImplProxyтолько дляIEmployeeServiceСервис интерфейса, если я добавлю новыйIRoleService, сделать другоеRoleServiceImplProxy, увеличивая сложность обслуживания

Ввиду поставленных выше вопросов, можем ли мы его оптимизировать? Ответ - да

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

Динамический прокси-класс динамически генерируется JVM посредством отражения и других механизмов во время работы программы, поэтому файл байт-кода прокси-класса отсутствует Отношение между прокси-объектом и реальным объектом определяется во время работы программы. .

Мы уже говорили о способах и отличиях динамического прокси, вот краткая демонстрация динамического прокси jdk.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JDKProxyTest {

    @Autowired
    private TransactionManagerInvocationHandle handle;

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.update();
    }
}
public class TransactionManagerInvocationHandle implements InvocationHandler {

    @Setter
    private TransactionManager txManager;
    @Setter
    private Object target;//真实对象

    //生成代理对象
    //泛型只是为了调用时不用强转,如果用Object的话调用时需要强转
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//类加载器
                target.getClass().getInterfaces(),//为哪些接口做代理(拦截什么方法)
                this);//为哪个类监听增强操作的方法(把这些方法拦截到哪里处理)
    }

    //如何做增强操作(被拦截的方法在这里增强处理)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object obj = null;

        try {
            txManager.begin();
            //原封不动调用之前的方法
            obj = method.invoke(target, args);
            txManager.commit();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }

        return obj;
    }
}

Таким образом, пользователю не нужно заботиться о логике транзакции.getProxyObjectВсе еще слишком сложно получить динамический прокси-объект, так как я могу не вызыватьgetProxyObjectКак насчет скрытого внедрения динамических прокси-объектов?Анализ исходного кода Dubbo — простой принцип, интеграция с Spring

dubbo-spi-aop

После просмотра стольких эволюций вы до сих пор этого не видите?dubboТень исходного кода?dubboделаетspiПри проектировании тоже идет процесс эволюции и оптимизации.Посмотрим как это делает dubbo

//dubbo spi中的aop
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

Приводятся следующие документы

Когда ExtensionLoader загружает точку расширения, если загруженная точка расширения имеет конструктор копирования, он определяется как класс-оболочка точки расширения.

Содержимое класса-оболочки:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;

public class XxxProtocolWrapper implemenets Protocol {
    Protocol impl;

    public XxxProtocol(Protocol protocol) { impl = protocol; }

    // 接口方法做一个操作后,再调用extension的方法
    public void refer() {
        //... 一些操作
        impl.refer();
        // ... 一些操作
    }

    // ...
}

Класс Wrapper может переместить всю общую логику точки расширения в Wrapper. Недавно добавленный Wrapper добавляет логику ко всем точкам расширения, что чем-то похоже на АОП, то есть Wrapper проксирует точки расширения.

Вы можете найти его здесь, в даббоspiПовысилсяaop, на самом деле является шаблоном проектирования декоратора. Но из приведенной выше эволюции мы обнаружили, что шаблон проектирования декоратора по-прежнему имеет много недостатков, за которыми следует постепенная эволюция и, наконец, динамический прокси. Как dubbo справляется с этим недостатком и постепенно развивается?

В даббо есть понятие扩展点自适应, то есть внедрение точки расширения в интерфейсAdaptiveinstance, пока не будет выполнен метод, реализация которого вызывается точкой расширения, об этом будет рассказано в следующей статье.AdaptiveОн будет представлен подробно, эта статья на самом деле является просветительской статьей следующей.

Стучите по доске, чтобы нарисовать ключевые моменты — советы

Поскольку в этой статье упоминается aop Spring, вот небольшая хитрость, SpringAOPВсего существует 5 методов улучшения, а именно:

Расширенный тип Сценарии применения
предварительное усиление Контроль разрешений, запись журнала вызовов
задний наддув Данные результатов статистического анализа
аномальное усиление Информация об исключении метода регистрации
окончательное улучшение освободить ресурсы
Улучшение объемного звучания Кэш, производительность, разрешения, управление транзакциями

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

//环绕
try {
    //前置
    System.out.println("=====");
    //后置
}catch (Exception e){
    //异常
}finally {
    //最终
}

На самом деле, его пять способов основаны наtry-catch-finallyПока вы помните идею этого дизайна, вы, естественно, не забудете эти 5 способов.Это то, что я неоднократно подчеркивал ранее.Если вы понимаете принципы и идеи дизайна, многие вещи все-в-одном.

напиши в конце

Feichao — это технический публичный аккаунт, в котором основное внимание уделяется принципам, исходному коду и навыкам разработки, оригинальному тематическому анализу исходного кода в аккаунте и реальному сражению принципов исходного кода в реальных сценах (ключевые моменты).Отсканируйте QR-код нижеОбратите внимание на Фей Чао, пусть те, кто должен строить ракеты, перестанут трахаться!