Класс расширения Java (6) Отражение и динамический прокси (JDK Proxy и Cglib)

Java

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

1. Отражение

Механизм отражения — это базовая функция, предоставляемая языком Java, которая дает программе время выполнения.самоанализ(самоанализ, официальный термин) способность. С помощью отражения мы можем напрямую манипулировать классами или объектами, например получать определение класса объекта, получать свойства и методы, объявленные классом, вызывать методы или создавать объекты и даже изменять определение класса во время выполнения.

1. Получите объект класса

Есть три способа получить объект класса:

  • Через forName() -> Пример: Class.forName("PeopleImpl")
  • Через getClass() -> Пример: new PeopleImpl().getClass()
  • Получить .class напрямую -> Пример: PeopleImpl.class

2. Общие методы классов

  • getName(): получить полный метод класса;
  • getSuperclass(): получить суперкласс класса;
  • newInstance(): создать экземпляр объекта;
  • getFields(): получить все свойства текущего класса и общедоступную модификацию родительского класса;
  • getDeclaredFields(): получить все объявленные свойства текущего класса (за исключением родительского класса);
  • getMethod(): получить все методы публичной модификации текущего класса и родительского класса;
  • getDeclaredMethods(): получить все объявленные методы текущего класса (за исключением родительского класса);

Больше способов:IC computer.API go.to/blog/class-…

3. Вызов метода класса

Чтобы вызвать метод в классе путем отражения, его нужно реализовать через ключевой метод "invoke()". Существует три типа вызовов методов:

  • вызов статического метода
  • обычный вызов метода
  • вызов частного метода

Далее будут продемонстрированы коды реализации различных вызовов и общие части кода различных вызовов, а именно:

// 此段代码为公共代码
interface People {
    int parentAge = 18;
    public void sayHi(String name);
}
class PeopleImpl implements People {
    private String privSex = "男";
    public String race = "汉族";
    @Override
    public void sayHi(String name) {
        System.out.println("hello," + name);
    }
    private void prvSayHi() {
        System.out.println("prvSayHi~");
    }
    public static void getSex() {
        System.out.println("18岁");
    }
}

3.1 Вызовы статических методов

// 核心代码(省略了抛出异常的声明)
public static void main(String[] args) {
    Class myClass = Class.forName("example.PeopleImpl");
    // 调用静态(static)方法
    Method getSex = myClass.getMethod("getSex");
    getSex.invoke(myClass);
}

Вызов статических методов относительно прост.Используйте getMethod(xx) для получения соответствующего метода и непосредственно используйте invoke(xx).

3.2 Обычный вызов метода

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

Class myClass = Class.forName("example.PeopleImpl");
Object object = myClass.newInstance();
Method method = myClass.getMethod("sayHi",String.class);
method.invoke(object,"老王");

getMethod Get, вы можете объявить тип передаваемых параметров.

3.3 Вызов приватных методов

Чтобы вызвать закрытый метод, вы должны использовать "getDeclaredMethod(xx)", чтобы получить все методы этого класса. Код выглядит следующим образом:

Class myClass = Class.forName("example.PeopleImpl");
Object object = myClass.newInstance();
Method privSayHi = myClass.getDeclaredMethod("privSayHi");
privSayHi.setAccessible(true); // 修改访问限制
privSayHi.invoke(object);

В дополнение к "getDeclaredMethod(xx)" видно, что ключом к вызову закрытого метода является установка атрибута setAccessible(true) и изменение ограничений доступа, чтобы вызов можно было выполнить после установки.

4. Резюме

1. Основными методами отражения являются newInstance() для получения экземпляров класса, getMethod(..) для получения методов, invoke(..) для вызова методов и setAccessible для изменения ограничений доступа к закрытым переменным/методам.

2. Разница между наличием «Объявленных» при получении свойств/методов заключается в том, что метод или свойство с модификацией «Объявленное» могут получать все методы или свойства этого класса (от частного к общедоступному), но не могут получать какую-либо информацию о родительском классе; Необъявленные измененные методы или свойства могут получать только общедоступные измененные методы или свойства и могут получать информацию о родительском классе, такую ​​как getMethod(..) и getDeclaredMethod(..).

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

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

Существует множество способов реализации динамического прокси, например, динамический прокси, предоставляемый самим JDK, в основном использует упомянутый выше механизм отражения. Существуют и другие реализации, такие как использование легендарного высокопроизводительного механизма манипулирования байт-кодом, такого как ASM, cglib (на основе ASM) и т. д.

Проблемы, которые решают динамические прокси?

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

1. Динамический прокси-сервер JDK Proxy

JDK Proxy реализуется путем реализации интерфейса InvocationHandler, код выглядит следующим образом:

interface Animal {
    void eat();
}
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("The dog is eating");
    }
}
class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("The cat is eating");
    }
}

// JDK 代理类
class AnimalProxy implements InvocationHandler {
    private Object target; // 代理对象
    public Object getInstance(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 {
        System.out.println("调用前");
        Object result = method.invoke(target, args); // 方法调用
        System.out.println("调用后");
        return result;
    }
}

public static void main(String[] args) {
    // JDK 动态代理调用
    AnimalProxy proxy = new AnimalProxy();
    Animal dogProxy = (Animal) proxy.getInstance(new Dog());
    dogProxy.eat();
}

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

Уведомление:JDK Proxy может проксировать только классы, которые реализуют интерфейсы (даже расширения, наследующие классы, не могут быть проксированы).

Почему JDK Proxy может использовать только прокси-классы, реализующие интерфейсы?

Эта проблема начинается с исходного кода метода реализации newProxyInstance динамического прокси:

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
// 省略其他代码

Посмотрите на описание первых двух параметров исходного кода:

* @param   loader the class loader to define the proxy class
* @param   interfaces the list of interfaces for the proxy class to implement
  • загрузчик: это загрузчик классов, то есть target.getClass().getClassLoader()
  • interfaces: Список реализаций интерфейса прокси-класса интерфейса.

Таким образом, источник этой проблемы кроется в исходном дизайне JDK Proxy. Если вы хотите настаивать на динамическом прокси, класс реализации без интерфейса сообщит об ошибке:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx

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

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

На Cglib может напрямую ссылаться Maven, адрес версии Maven:MV внутри репозитория.com/artifact/success…

В этой статье используется последняя версия Cglib 3.2.9 и добавлена ​​следующая ссылка на pom.xml:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.9</version>
</dependency>

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

class Panda {
    public void eat() {
        System.out.println("The panda is eating");
    }
}
class CglibProxy implements MethodInterceptor {
    private Object target; // 代理对象
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        // 设置父类为实例类
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("调用前");
        Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
        System.out.println("调用后");
        return result;
    }
}

public static void main(String[] args) {
    // CGLIB 动态代理调用
    CglibProxy proxy = new CglibProxy();
    Panda panda = (Panda)proxy.getInstance(new Panda());
    panda.eat();
}

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

3. Прокси-сервер JDK против Cglib

Преимущества прокси-сервера JDK:

  • Минимизация зависимостей, сокращение зависимостей означает упрощение разработки и обслуживания, а поддержка самого JDK более надежна;
  • Плавно обновите версию JDK, и библиотеку классов байт-кода обычно необходимо обновить, чтобы гарантировать, что ее можно будет использовать в новой версии;

Преимущества фреймворка Cglib:

  • Общие классы можно вызывать без реализации интерфейсов;
  • высокая производительность;

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

Весь пример кода в этой статье:GitHub.com/VIP stone/Срочно…

4. Справочные документы

Основные технологии Java 36 лекций:t.cn/EwUJvWA

Отражение Java и динамический прокси:woo woo woo.cn blog on.com/Han Ganglin/…


Обратите внимание на публичный аккаунт автора:

公众号

Если эта статья оказалась для вас полезной, пригласите меня на чашечку кофе.