Во время собеседования интервьюер часто спрашивает, что лежит в основе принципа Spring AOP, а основным принципом AOP является динамический прокси. Каждый программист JAVA в основном знаком с ним. Разница между динамическим прокси jdk и динамическим прокси cglib также очевидна. динамический прокси может быть основан только на интерфейсе, а cglib в этом не нуждается.
Итак, почему динамический прокси jdk основан на интерфейсе?
Давайте сначала реализуем простой динамический прокси jdk
Класс интерфейса
public interface Person {
void say();
}
Класс реализации
public class Man implements Person{
@Override
public void say() {
System.out.println("man");
}
}
Прокси-класс реализует интерфейс InvocationHandler и переопределяет метод вызова.
public class JDKDynamicProxy implements InvocationHandler {
private Object target;
public JDKDynamicProxy(Object target) {
this.target = target;
}
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Do something before");
Object result = method.invoke(target, args);
System.out.println("Do something after");
return result;
}
}
назови это
public class Test {
public static void main(String[] args) {
Person person = new JDKDynamicProxy(new Man()).getProxy();
person.say();
}
}
Результат вызова следующий
Do something before
man
Do something after
Указывает, что динамический прокси действует.
Затем возникает проблема, динамический прокси jdk требует, чтобы класс прокси реализовывал интерфейс InvocationHandler, а динамический прокси можно реализовать, переписав метод вызова, а конструктор можно внедрить в объект.Нет требования, чтобы объект был на основе интерфейса и, по сути, getProxy() получает класс прокси (исходный класс Object) может полностью наследовать объект.
Итак, определите еще один WonMan.class, не реализуйте интерфейс, давайте протестируем его.
public class Woman {
public void say(){
System.out.println("woman");
}
}
интерфейс вызова
Woman woman = new JDKDynamicProxy(new Woman()).getProxy();
woman.say();
Результат выглядит следующим образом
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to com.ganhuojun.demo.proxy.Woman
at com.ganhuojun.demo.proxy.Test.main(Test.java:8)
Обратите внимание, что динамический прокси объекта без интерфейса действительно не поддерживается.
Далее давайте проанализируем исходный код и выясним, почему поддерживаются только интерфейсы. Поскольку ядром динамического прокси является получение класса прокси, давайте начнем со следующего метода.
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
Поскольку исходных кодов много, я расскажу только о основной части.Исходный код выглядит следующим образом, а красная часть предназначена для получения прокси-класса.
Посмотрите еще раз на метод getProxyClass0.Для облегчения понимания основная часть извлекается следующим образом.
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
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 — это WeakCache, посмотрите на метод get, посмотрите шаг за шагом и, наконец, посмотрите на метод apply(), вызываемый для класса ProxyClassFactory, как показано ниже.
Здесь найдено, что красная часть является истинным методом для создания прокси-классов, продолжайте изучать его и обнаружили, что нужно только установить saveGeneratedFiles true, вы можете увидеть локально сгенерированный прокси-класс, как показано ниже.
Значение saveGeneratedFiles следующее:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
На этом этапе мы изменим тестовый код на следующий
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Person person = new JDKDynamicProxy(new Man()).getProxy();
person.say();
После выполнения обнаруживается, что локально есть дополнительный прокси-класс, как показано ниже
Откройте его и посмотрите.Исходный код выглядит следующим образом.Обнаружено, что $Proxy0 сам наследует класс Proxy.Поскольку java не поддерживает множественное наследование, динамический прокси не может быть реализован.
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.ganhuojun.demo.proxy.Person").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
В заключение:
Сгенерированный прокси-класс наследует прокси. Поскольку Java является одним наследством, он может реализовать только интерфейс, который реализован через интерфейс.