Пусть люди, которых я встречу, вещи, которые я испытаю, даже если я стану немного лучше, я буду удовлетворен.
1. Что такое агент?
Прокси — это распространенный шаблон проектирования, целью которого является предоставление прокси для других объектов для управления доступом к объекту. Прокси-класс отвечает за предварительную обработку сообщений для класса делегата, фильтрацию и пересылку сообщений, а также выполнение последующей обработки после того, как сообщение будет выполнено классом делегата.
UML-диаграмма шаблона прокси:
2. Класс динамического прокси Java
Динамический прокси-класс Java находится в пакете java.lang.reflect и обычно включает следующие два класса:
(1) Интерфейс InvocationHandler: в этом интерфейсе определен только один метод.
public object invoke(Object obj,Method method, Object[] args)
На практике первый параметр obj обычно относится к прокси-классу, method — это прокси-метод, такой как request() в приведенном выше примере, а args — это массив параметров метода. Этот абстрактный метод динамически реализуется в прокси-классе.
(2) Прокси: этот класс является классом динамического прокси, который в основном включает следующее содержимое:
защищенный прокси-сервер (InvocationHandler h): конструктор, используемый для присвоения значения внутреннему объекту h.
static Class getProxyClass(
ClassLoader loader,
Class[] interfaces): получает прокси-класс, где loader — это загрузчик класса, а interfaces — массив всех интерфейсов, которые есть у реального класса.
static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h): возвращает экземпляр прокси-класса, и возвращенный прокси-класс можно использовать в качестве прокси-класса (можно использовать метод, объявленный в интерфейсе Subject прокси-класса). )
Так называемый DynamicProxy является таким классом: это класс, сгенерированный во время выполнения, вы должны предоставить ему набор интерфейсов при его генерации, а затем класс объявляет, что он реализует эти интерфейсы. Конечно, вы можете использовать экземпляр класса в качестве любого из этих интерфейсов. Конечно, этот DynamicProxy на самом деле является прокси, он не будет выполнять фактическую работу за вас, вы должны предоставить обработчик при создании его экземпляра, который возьмет на себя фактическую работу.
При использовании динамических прокси-классов мы должны реализовать интерфейс InvocationHandler.
Таким образом, проксируемый объект (RealSubject) может быть динамически изменен во время выполнения, управляемый интерфейс (интерфейс субъекта) может быть изменен во время выполнения, и метод управления (класс DynamicSubject) также может быть динамически изменен, таким образом достигая очень гибкие динамичные агентские отношения.
Шаги динамического прокси:
-
Создайте класс, реализующий интерфейс InvocationHandler, он должен реализовать метод вызова
-
Создание прокси-классов и интерфейсов
-
Статические методы через прокси
newProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandler h) создает прокси
- Вызов метода через прокси
3. Как использовать динамический прокси JDK?
1. Интерфейсы, требующие динамического прокси:
package jiankunking;
/**
* 需要动态代理的接口
*/
public interface Subject {
/**
* 你好
*
* @param name
* @return
*/
public String SayHello(String name);
/**
* 再见
*
* @return
*/
public String SayGoodBye();
}
2. Фактический объект, который необходимо проксировать
package jiankunking;
/**
* 实际对象
*/
public class RealSubject implements Subject {
/**
* 你好
*
* @param name
* @return
*/
@Override
public String SayHello(String name) {
return "hello " + name;
}
/**
* 再见
*
* @return
*/
@Override
public String SayGoodBye() {
return " good bye ";
}
}
3. Вызов класса реализации процессора (такое ощущение, что это легендарный АОП)
package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 调用处理器实现类
* 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
*/
public class InvocationHandlerImpl implements InvocationHandler {
/**
* 这个就是我们要代理的真实对象
*/
private Object subject;
/**
* 构造方法,给我们要代理的真实对象赋初值
*
* @param subject
*/
public InvocationHandlerImpl(Object subject) {
this.subject = subject;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*
* @param proxy 代理类实例
* @param method 被调用的方法对象
* @param args 调用参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("在调用之前,我要干点啥呢?");
System.out.println("Method:" + method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnValue = method.invoke(subject, args);
//在代理真实对象后我们也可以添加一些自己的操作
System.out.println("在调用之后,我要干点啥呢?");
return returnValue;
}
}
4. Тест
package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 动态代理演示
*/
public class DynamicProxyDemonstration {
public static void main(String[] args) {
//代理的真实对象
Subject realSubject = new RealSubject();
/**
* InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
* 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
* 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = handler.getClass().getClassLoader();
Class<?>[] interfaces = realSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("动态代理对象的类型:" + subject.getClass().getName());
String hello = subject.SayHello("jiankunking");
System.out.println(hello);
// String goodbye = subject.SayGoodBye();
// System.out.println(goodbye);
}
}
5. Результаты вывода следующие:
В-четвертых, как добиться динамического прокси?
Как видно из кода использования, ключевыми моментами являются:
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
Это можно увидеть, проследив код подсказки: когда прокси-объект вызывает метод реального объекта, он автоматически переходит к методу вызова объекта-обработчика, связанного с вызываемым прокси-объектом.
То есть, когда код выполняется для предложения: subject.SayHello("jiankunking"), автоматически вызывается метод вызова InvocationHandlerImpl. Почему это?
Далее идет процесс кода и анализа, друзья, которые не хотят читать, могут сразу прочитать заключение
Следующий код взят из: JDK1.8.0_92.
Поскольку прокси-объект генерируется с использованием статической стороны newProxyInstance класса Proxy, давайте перейдем к его исходному коду, чтобы посмотреть, что он сделал?
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
*
* <p>{@code Proxy.newProxyInstance} throws
* {@code IllegalArgumentException} for the same reasons that
* {@code Proxy.getProxyClass} does.
*
* @param loader the class loader to define the proxy class
* @param interfaces the list of interfaces for the proxy class
* to implement
* @param h the invocation handler to dispatch method invocations to
* @return a proxy instance with the specified invocation handler of a
* proxy class that is defined by the specified class loader
* and that implements the specified interfaces
* @throws IllegalArgumentException if any of the restrictions on the
* parameters that may be passed to {@code getProxyClass}
* are violated
* @throws SecurityException if a security manager, <em>s</em>, is present
* and any of the following conditions is met:
* <ul>
* <li> the given {@code loader} is {@code null} and
* the caller's class loader is not {@code null} and the
* invocation of {@link SecurityManager#checkPermission
* s.checkPermission} with
* {@code RuntimePermission("getClassLoader")} permission
* denies access;</li>
* <li> for each proxy interface, {@code intf},
* the caller's class loader is not the same as or an
* ancestor of the class loader for {@code intf} and
* invocation of {@link SecurityManager#checkPackageAccess
* s.checkPackageAccess()} denies access to {@code intf};</li>
* <li> any of the given proxy interfaces is non-public and the
* caller class is not in the same {@linkplain Package runtime package}
* as the non-public interface and the invocation of
* {@link SecurityManager#checkPermission s.checkPermission} with
* {@code ReflectPermission("newProxyInPackage.{package name}")}
* permission denies access.</li>
* </ul>
* @throws NullPointerException if the {@code interfaces} array
* argument or any of its elements are {@code null}, or
* if the invocation handler, {@code h}, is
* {@code null}
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//检查h 不为空,否则抛异常
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 获得与指定类装载器和一组接口相关的代理类类型对象
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 通过反射获取构造函数对象并生成代理类实例
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//生成代理类的实例并把InvocationHandlerImpl的实例传给它的构造方法
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
Давайте перейдем к методу getProxyClass0 и посмотрим:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
Правда все еще не здесь, идем дальше и взглянем на proxyClassCache
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
О, так я использовал кеш.
Итак, каков соответствующий метод get?
/**
* Look-up the value through the cache. This always evaluates the
* {@code subKeyFactory} function and optionally evaluates
* {@code valueFactory} function if there is no entry in the cache for given
* pair of (key, subKey) or the entry has already been cleared.
*
* @param key possibly null key
* @param parameter parameter used together with key to create sub-key and
* value (should not be null)
* @return the cached value (never null)
* @throws NullPointerException if {@code parameter} passed in or
* {@code sub-key} calculated by
* {@code subKeyFactory} or {@code value}
* calculated by {@code valueFactory} is null.
*/
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
Object cacheKey = CacheKey.valueOf(key, refQueue);
// lazily install the 2nd level valuesMap for the particular cacheKey
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
if (valuesMap == null) {
//putIfAbsent这个方法在key不存在的时候加入一个值,如果key存在就不放入
ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// else no supplier in cache
// or a supplier that returned null (could be a cleared CacheValue
// or a Factory that wasn't successful in installing the CacheValue)
// lazily construct a Factory
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// else retry with winning supplier
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
Мы видим, что он вызывает supplier.get(); для получения динамического прокси-класса, где поставщиком является Factory, определенный внутри WeakCach.
Давайте посмотрим, что делается в get?
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// something changed while we were waiting:
// might be that we were replaced by a CacheValue
// or were removed because of failure ->
// return null to signal WeakCache.get() to retry
// the loop
return null;
}
// else still us (supplier == this)
// create new value
V value = null;
try {
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null;
// wrap value with CacheValue (WeakReference)
CacheValue<V> cacheValue = new CacheValue<>(value);
// try replacing us with CacheValue (this should always succeed)
if (valuesMap.replace(subKey, this, cacheValue)) {
// put also in reverseMap
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
// successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}
Обнаружено, что ключевая точка все еще там, но мы видим, что она вызывает метод valueFactory.apply(key, parameter):
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
/*
* Verify that the class loader resolves the name of this
* interface to the same Class object.
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* Verify that the Class object actually represents an
* interface.
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* Verify that this interface is not a duplicate.
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
Я наконец нашел точку, посмотрев на код:
//生成字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
Затем давайте также воспользуемся тестом, чтобы увидеть, как выглядит байт-код, сгенерированный этим методом:
package jiankunking;
import sun.misc.ProxyGenerator;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 动态代理演示
*/
public class DynamicProxyDemonstration {
public static void main(String[] args) {
//代理的真实对象
Subject realSubject = new RealSubject();
/**
* InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
* 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
* 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader loader = handler.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
System.out.println("动态代理对象的类型:"+subject.getClass().getName());
String hello = subject.SayHello("jiankunking");
System.out.println(hello);
// 将生成的字节码保存到本地,
createProxyClassFile();
}
private static void createProxyClassFile(){
String name = "ProxySubject";
byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{Subject.class});
FileOutputStream out =null;
try {
out = new FileOutputStream(name+".class");
System.out.println((new File("hello")).getAbsolutePath());
out.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(null!=out) try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Взгляните на типы прокси-объектов здесь:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import jiankunking.Subject;
public final class ProxySubject extends Proxy implements Subject {
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;
public ProxySubject(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject){
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException localError){
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String SayGoodBye() {
try{
return (String)this.h.invoke(this, m3, null);
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String SayHello(String paramString) {
try {
return (String)this.h.invoke(this, m4, new Object[] { paramString });
} catch (Error|RuntimeException localError){
throw localError;
} catch (Throwable localThrowable){
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);
m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
Это последний настоящий прокси-класс, который наследуется от Proxy и реализует определенный нами интерфейс Subject, то есть:
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
Субъект здесь на самом деле является экземпляром этого класса, поэтому мы называем его:
public final String SayHello(String paramString)
Это вызов метода вызова InvocationHandlerImpl, который мы определили:
Выше идет процесс кода и анализа.Друзья, которые не хотят его видеть, могут сразу увидеть вывод.
V. Заключение
Вот, наконец, ответ:
Когда subject.SayHello("jiankunking") является предложением, почему автоматически вызывается метод вызова InvocationHandlerImpl?
Поскольку окончательный реальный прокси-класс, сгенерированный JDK, наследуется от Proxy и реализует определенный нами интерфейс Subject, внутри метода, который реализует интерфейс Subject, вызывается метод InvocationHandlerImpl через отражение.
Анализируя код, мы можем увидеть динамический прокси Java, который состоит из следующих четырех шагов:
-
Создайте свой собственный обработчик вызова, реализовав интерфейс InvocationHandler;
-
Создайте динамический прокси-класс, указав объект ClassLoader и набор интерфейсов для прокси-класса;
-
Конструктор динамического прокси-класса получается через механизм отражения, и его единственным типом параметра является тип интерфейса вызывающего процессора;
-
Экземпляр динамического прокси-класса создается с помощью конструктора, а объект-обработчик передается в качестве параметра во время построения.
Загрузка демо-кода:GitHub.com/jiankun король…
Шесть, информация о блоггерах
Личный публичный аккаунт WeChat: