Понять три прокси-режима Java в считанные секунды

Java задняя часть
Понять три прокси-режима Java в считанные секунды

предисловие

Прокси-режим — этоструктурныйШаблоны проектирования, обеспечивающие дополнительный доступ к целевому объекту;即通过代理对象访问目标对象。

image-20210724125009890

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

Здесь используется идея в программировании: не модифицируйте код или методы, которые написали другие, по своему желанию.Если вам нужно это изменить, вы можете расширить метод по доверенности.

В режиме прокси есть примерно три роли:

  • Real Subject: реальный класс, то есть класс-посредник и класс делегата. Используется для действительно завершения функции бизнес-услуг;
  • Proxy: Прокси-класс, который реализует свой собственный запрос с функцией, соответствующей Реальному Субъекту, а объект прокси-класса на самом деле не реализует свою бизнес-функцию;
  • Subject: определяет интерфейс, который должны реализовывать роли RealSubject и Proxy.

image-20210724125955100

Существует три типа режима прокси: статический прокси, динамический прокси (прокси JDK, прокси интерфейса), прокси Cglib (динамическое создание подкласса целевого объекта в памяти)

текст

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

Статический прокси-сервер должен сначала определить интерфейс, а прокси-объект и прокси-объект реализуют один и тот же интерфейс, а затем通过调用相同的方法来调用目标对象的方法.

image-20210726222112024

Видно, что прокси-класс — это не что иное, как добавление некоторых операций до и после вызова метода класса делегата. Различие в классе делегата также приводит к различию в прокси-классе.

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

Пример кода:

Телевизионная установка:

public class TV {

    private String name;//名称

    private String address;//生产地

    public TV(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "TV{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

Создадим интерфейс компании:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();
}

Заводы компании производят телевизоры:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }
}

Агент переходит к оформлению заказа на получение товара (статический класс агента):

public class TVProxy implements TVCompany{

    private TVCompany tvCompany;

    public TVProxy(){

    }

    @Override
    public TV produceTV() {
        System.out.println("TV proxy get order .... ");
        System.out.println("TV proxy start produce .... ");
        if(Objects.isNull(tvCompany)){
            System.out.println("machine proxy find factory .... ");
            tvCompany = new TVFactory();
        }
        return tvCompany.produceTV();
    }
}

Потребители получают товары через агентов (использование агентов):

public class TVConsumer {

    public static void main(String[] args) {
        TVProxy tvProxy = new TVProxy();
        TV tv = tvProxy.produceTV();
        System.out.println(tv);
    }
}

Выходной результат:

TV proxy get order .... 
TV proxy start produce .... 
machine proxy find factory .... 
TV factory produce TV...
TV{name='小米电视机', address='合肥'}

Process finished with exit code 0

резюме:

  • преимущество:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。

  • недостаток:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

Как решить недостатки статического прокси? Ответ заключается в том, что вы можете использовать метод динамического прокси.

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

image-20210726224121229

Динамический агент имеет следующие характеристики:

  1. Динамический прокси-объект JDK не должен реализовывать интерфейс, только целевой объект должен реализовывать интерфейс.

  2. Реализация динамического прокси-сервера на основе интерфейса требует использования API в JDK для его динамической сборки в памяти JVM.Proxy对象.

  3. нужно использоватьjava.lang.reflect.Proxy, и этоnewProxyInstanceметод, но метод должен получить три параметра.

image-20210724132028289

Обратите внимание, что этот метод является статическим методом в классе Proxy и получает три параметра:

  • ClassLoader loader: указывает, что текущий целевой объект использует загрузчик класса, и метод получения загрузчика является фиксированным.
  • Class<?>[] interfaces: Тип интерфейса, реализованного целевым объектом, с использованием дженериков для подтверждения типа.
  • InvocationHandler h: обработка события, при выполнении метода целевого объекта будет запущен метод обработчика события, а метод текущего выполняемого целевого объекта будет передан в качестве параметра.

В один прекрасный день компания расширила свой бизнес и продавала все больше и больше продукции, а послепродажное обслуживание также нуждалось в улучшении. Однако компания обнаружила, что первоначальный агент нуждался в переобучении, чтобы завершить весь бизнес, поэтому она нашла другого агента.动态代理商B.代理商BОн обещает беспроблемно подключить весь бизнес компании, какой бы новый бизнес не добавлялся, его можно пройти без дополнительного обучения.

Пример кода:

Компания добавила техническое обслуживание:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();

    /**
     * 维修电视机
     * @param tv 电视机
     * @return 电视机
     */
    public TV repair(TV tv);
}

Бизнес по обслуживанию заводов также должен заниматься ими:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }

    @Override
    public TV repair(TV tv) {
        System.out.println("tv is repair finished...");
        return new TV("小米电视机","合肥");
    }
}

Агент БКомплексное агентство всего бизнеса компании. использоватьProxy.newProxyInstanceМетод генерирует прокси-объект, который реализуетInvocationHandlerсерединаinvokeметод, вinvokeВ методе через отражение вызывается метод прокси-класса и предоставляется метод расширения.

public class TVProxyFactory {

    private Object target;

    public TVProxyFactory(Object o){
        this.target = o;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TV proxy find factory for tv.... ");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });
    }
}

Покупка и обслуживание этих двух предприятий B代理Это можно сделать напрямую. Позже компания увеличит свой бизнес, и агент Б тоже может сделать то же самое.

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany target = new TVFactory();
        TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
    }
}

Выходной результат:

TV proxy find factory for tv.... 
TV factory produce TV...
TV proxy find factory for tv.... 
tv is repair finished...

Process finished with exit code 0

резюме:

  1. 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。

  2. 动态代理的方式中,所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。

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

Как решить эту проблему? Мы можем использовать механизм динамического прокси CGLIB.

Cglib-прокси

И статические прокси, и прокси JDK требуют объекта для реализации интерфейса. Иногда прокси-объект представляет собой всего один объект. В этом случае можно использовать прокси Cglib.

image-20210726224750356

Прокси Cglib можно назвать прокси подкласса, который создает объект подкласса в памяти для расширения функции целевого объекта.

Агент С хочет представлять не только компанию, но и продукцию нескольких заводов.

Cglib черезEnhancerдля создания прокси-классов путем реализацииMethodInterceptorинтерфейс и реализоватьinterceptметод, в котором можно добавлять методы улучшения и использовать преимущества отраженияMethodилиMethodProxyНаследуйте класс для вызова исходного метода.

ВидетьB代理商Займитесь различными делами компании (интерфейс), то в это времяC代理商Я также нашел новые возможности для бизнеса.B может представлять только продукцию компании, но я не только хочу представлять продукцию компании, но и связываться с различными фабриками, чтобы я мог получать больше товаров и зарабатывать больше денег. Поэтому используется Cglib.

Пример кода:

public class TVProxyCglib implements MethodInterceptor {

    //给目标对象创建一个代理对象
    public Object getProxyInstance(Class c){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(c);
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("TVProxyFactory enhancement.....");
        Object object = methodProxy.invokeSuper(o, objects);
        return object;
    }
}

Фабрика B нового агента

public class TVFactoryB {

    public TV produceTVB() {
        System.out.println("tv factory B producing tv.... ");
        return new TV("华为电视机", "南京");
    }

    public TV repairB(TV tv) {
        System.out.println("tv B is repair finished.... ");
        return tv;
    }
}

C代理Вы можете работать напрямую с компанией или иметь дело с фабрикой. И может быть агентом продукции любой фабрики.

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
        System.out.println("==============================");

        TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
        TV tv = tvFactoryB.produceTVB();
        tvFactoryB.repairB(tv);
    }
}

Выходной результат:

TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv.... 
TVProxyFactory enhancement.....
tv B is repair finished.... 

Process finished with exit code 0

AOP использует прокси в Spring

В Spring есть две реализации АОП, JDK и Cglib, как показано ниже:

image-20210724133134109

Если целевому объекту необходимо реализовать интерфейс, используется прокси-сервер JDK.

Если целевому объекту не нужно реализовывать интерфейс, используется прокси-сервер Cglib.

Суммировать

  1. 静态代理: И прокси-класс, и целевой класс должны реализовать интерфейсный метод, чтобы прокси мог расширить свою функцию.

  2. JDK动态代理: Прокси-класс должен реализовать интерфейс, использоватьProxy.newProxyInstanceМетод генерирует прокси-класс и реализуетInvocationHandlerсерединаinvokeспособ реализации улучшений.

  3. Cglib动态代理: ни один прокси-класс не реализует интерфейс, используйтеCblibсерединаEnhancerдля создания подклассов прокси-объектов и реализацииMethodInterceptorсерединаinterceptметод, в котором могут быть реализованы усовершенствования.

наконец

Я кодер, которого бьют, и я пытаюсь двигаться вперед. Если статья была вам полезна, не забудьте поставить лайк и подписаться, спасибо!