从炒股说起
最近的股市很疯狂,时常1秒钟就能错过一个亿。但是上班时间,又没法花太多的时间关注股市。于是决定找个操盘手,让他替我炒股(以上情节重属虚构,怎么可能把股票账户给别人,露富了咋办~)。于是想到了设计模式中的代理模式,下面结合这个例子介绍一下。
静态代理
先用静态代理的方法,实现一下代理炒股这种方式。
先定义一个投资者接口
public interface IInvestor {
/**
* 登录股票账户
* @param user
* @param password
*/
void login(String user, String password);
/**
* 买股票
*/
void buyStock();
/**
* 卖股票
*/
void sellStock();
}
真正的投资者类
public class Investor implements IInvestor {
private String mName;
public Investor(String name){
this.mName = name;
}
@Override
public void login(String user, String password) {
System.out.println(this.mName + "登录成功!");
}
@Override
public void buyStock() {
System.out.println(this.mName + "在买股票!");
}
@Override
public void sellStock() {
System.out.println(this.mName + "在卖股票!");
}
}
操盘手类
public class InvestorProxy implements IInvestor {
private IInvestor mInvestor;
public InvestorProxy(IInvestor investor){
this.mInvestor = investor;
}
@Override
public void login(String user, String password) {
mInvestor.login(user, password);
}
@Override
public void buyStock() {
mInvestor.buyStock();
fee();
}
@Override
public void sellStock() {
mInvestor.sellStock();
fee();
}
public void fee(){
System.out.println("买卖股票费用: 100元");
}
}
场景类
public class Client {
public static void main(String[] args) {
//操盘手投资
IInvestor investor = new Investor("张三");
IInvestor proxy = new InvestorProxy(investor);
proxy.login("zhangsan", "123");
proxy.buyStock();
proxy.sellStock();
}
}
看一下结果
张三登录成功!
张三在买股票!
买卖股票费用: 100元
张三在卖股票!
买卖股票费用: 100元
通过上面的演示发现,真正的投资者,我们什么都不需要做,就有人给我们买卖股票了。当然了,雇别人炒股也的给人家一定的费用的。这就是静态代理模式,下面看看静态代理模式的通用实现。
静态代理通用代码实现
抽象主题类
public interface Subject {
void doSomething();
}
真实主题类
public class RealSubject implements Subject {
@Override
public void doSomething() {
}
}
代理主题类
public class Proxy implements Subject {
/** 要代理哪个实现类 */
private Subject subject = null;
public Proxy(Subject subject){
this.subject = subject;
}
@Override
public void doSomething() {
before();
subject.doSomething();
after();
}
/**
* 预处理
*/
private void before(){
}
/**
* 善后处理
*/
private void after(){
}
}
看上面的代码,有没有发现代理类Proxy中多了两个方法 before()和after(),通过这两个方法可以引出一种 崭新的编程模式,别急,下面会讲。
代理模式的优点
- 职责清晰
真实的角色只关心起本身职责,一些附加的任务,可以在代理类中处理。
- 高扩展性
真实的角色是随时都会发生变化的,只要它实现了接口,代理类不需要任何修改。
代理模式的使用场景
上面的场景中,我们炒股,其实只关心能否攒钱,至于买什么股票,买多少,这些工作完全可以交给理财师来做。就像我们打官司雇律师一样,我们其实不关心打官司的过程,只关心结果。这就是代理模式的使用场景。前面讲了静态代理,但是代理模式中,使用最多的是动态代理模式,下面接着介绍。
动态代理
在静态代理中,我们需要为每一个被代理类生成一个代理类。但是在动态代理中,这个代理类可以自动生成。另外现在很流行的一个名词叫面向切面编程(AOP),其核心就是用了动态代理机制。下面还是以炒股来看看动态代理的实现。
接口类和真实类还是使用上面的代码,然后再定义一个InvestorIH,实现InvocationHandler接口,如下
public class InvestorIH implements InvocationHandler {
/** 被代理者 */
private Class mCls = null;
/** 被代理的实例 */
private Object mObj = null;
public InvestorIH(Object obj){
this.mObj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//切入点
Object result = method.invoke(this.mObj, args);
//切入点
return result;
}
}
动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”。动态代理已经实现了所有的接口方法,但是默认是没有逻辑的,返回值都是空,但是没有任何逻辑该怎么办,通过InvocationHandler,所有的方法都由InvocationHandler接管处理。
看看场景类
public class Client {
public static void main(String[] args) {
IInvestor investor = new Investor("张三");
InvocationHandler handler = new InvestorIH(investor);
ClassLoader cl = investor.getClass().getClassLoader();
IInvestor proxy = (IInvestor) Proxy.newProxyInstance(cl, new Class[]{IInvestor.class}, handler);
proxy.login("zhangsan", "123");
proxy.buyStock();
proxy.sellStock();
}
}
从上面的代码可以发现,Proxy.newProxyInstance 会给我们生成一个代理类。那么代理类中的逻辑需要我们在InvestorIH,来实现,这就是动态代理的JDK实现。因为我们使用了jdk中的api去生成代理类。另外这种动态代理的实现方式只能代理接口,不能代理类。但是上面的静态代理是可以代理抽象类的,至于为什么,下面讲jdk的动态代理实现原理的时候,给出答案。
Retrofit动态代理
上面讲了动态代理的基本的用法和使用场景,为了加深理解,下面介绍一个他在Retorfit中的一个经典使用。
Retrofit基本使用
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<User>> repos = service.groupList(123, "123");
repos.enqueue(new Callback<List<User>>() {
@Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
}
@Override
public void onFailure(Call<List<User>> call, Throwable t) {
}
});
retrofit.create 方法就是创建一个代理,让代理去进行真正的网络请求。
所以我们主要看看下面的代码
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
可以看到它也是创建了一个InvocationHandler内部类,所有的逻辑都在invoke方法里。invoke方法中最重要的是最后一句
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
看看loadServiceMethod(method)和invoke方法中做了什么
loadServiceMethod方法代码
ServiceMethod<?> loadServiceMethod(Method method) {
ServiceMethod<?> result = serviceMethodCache.get(method);
if (result != null) return result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = ServiceMethod.parseAnnotations(this, method);
serviceMethodCache.put(method, result);
}
}
return result;
}
这个方法主要的是作用是,把我们在GitHubService中定义的请求方法的注解解析出来,为我们调用OkHttp请求做准备。那么真正的请求就到了ServiceMethod的invoke方法中,看看它的实现
@Override ReturnT invoke(Object[] args) {
return callAdapter.adapt(
new OkHttpCall<>(requestFactory, args, callFactory, responseConverter));
}
现在回想一下,Retrofit为什么要这样设计。我们调用请求方法时,其实并不关心OkHttp是怎么调用的,我们只要结果。但是每个请求方法执行都流程都是差不多的,都需要请求参数的构建,一大堆的请求配置。这些繁琐的操作都让代理类去完成了,这就是Retrofit采用动态代理的初衷吧。这只是我个人的理解。
JDK中动态代理的原理
在上面的内容中,我们了解了动态代理的基本使用方式和使用场景,并且大致看了一下Retrofit中关于动态代理的实现。但是,动态代理中,有个地方我们使用起来是不放心的,那就是动态代理中,代理类的生成。java只告诉我们你调用 Proxy中的
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
方法就可以了,但是,他里面这个代理类是怎么生成的,我们很好奇,下面就看看它的实现。
这个方法需要传入3个参数,先看看他们的作用
- loader 一个类加载器
- interfaces
一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口
- h 上文中多次提到的handler
返回一个代理对象
直接看注释吧
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
//克隆要被代理的接口
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//查找或者生成特定的代理类 class
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取参数类型是InvocationHandler.class的代理类构造器
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;
}
});
}
//传入InvocationHandler实例去构造一个代理类的实例
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);
}
}
所以获取代理类的实例,重点到了下面的这行代码中
//查找或者生成特定的代理类 class
Class<?> cl = getProxyClass0(loader, intfs);
看看getProxy方法的实现
/**
* 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
//如果缓存中有,则使用缓存,否则通过ProxyClassFactory创建
return proxyClassCache.get(loader, interfaces);
}
获取代理Class其实通过proxyClassCache获取的,关于从缓存中获取这块的逻辑我们先略过。通过注释我们知道 这个真正proxy class 是通过ProxyClassFactory类生成的,它主要的逻辑在apply方法中,下面就看看它的逻辑。
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
for (Class<?> intf : interfaces) {
//整个for循环主要做一样验证判断
/*
* 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");
}
}
}
//代理类都放到默认的包下:com.sun.proxy
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.
*/
//生成特定的.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());
}
}
}
通过上面的逻辑,我们看到,proxy class的生成其实是在 apply方法中通过ProxyGenerator.generateProxyClass()生成了代理类的.class文件,也就是Class文件。这里要稍微解释一下,Java程序的执行只依赖于Class文件,和Java文件是没有关系的。这个Class文件描述了一个类的信息,当我们需要使用到一个类时,Java虚拟机就会提前去加载这个类的Class文件并进行初始化和相关的检验工作,Java虚拟机能够保证在你使用到这个类之前就会完成这些工作,我们只需要安心的去使用它就好了,而不必关心Java虚拟机是怎样加载它的。当然,Class文件并不一定非得通过编译Java文件而来,你甚至可以直接通过文本编辑器来编写Class文件。在这里,JDK动态代理就是通过程序来动态生成Class文件的。到这里我们就知道动态代理的这个代理类是怎么生成的了。
但到底这个代理类长什么样子,能否看一下它的真容,当然可以,下面就看看上面例子中动态生成的代理类
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.cfp.pattern.proxy.dynamic.IInvestor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements IInvestor {
private static Method m1;
private static Method m5;
private static Method m4;
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})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void sellStock() throws {
try {
super.h.invoke(this, m5, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void buyStock() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 int login(String var1, String var2) throws {
try {
return ((Integer)super.h.invoke(this, m3, new Object[]{var1, var2})).intValue();
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m5 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("sellStock", new Class[0]);
m4 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("buyStock", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.cfp.pattern.proxy.dynamic.IInvestor").getMethod("login", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.String")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
对于这个类的抓取,有个技巧,因为这个类默认是在内存中的,所以需要我们把它存下来,只需要在main方法中添加
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
即可,很简单,然后在项目的根目录就会看到这个类。
通过上面的$Proxy0,我们可以解释我们之前的一些疑问。
-
代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口。
-
代理方法都会去调用InvocationHandler的invoke()方法,需要重写InvocationHandler的invoke()方法。以及Invoke中各个参数的意义。 通过上面的分析我们已经理解了JDK中动态代理的实现。
总结
构思这篇文章的时候,基本是按从易到难,由浅入深的方式去做的。但是中间涉及到了一些别的概念,比如aop,java的字节码技术,jvm类加载机制等。其实每一个都是很深的话题,但是这些我都简单的滤过了。很大的原因是自己的理解也不深,希望有机会专门写文章学习一下。
关于我
- 公众号: CodingDev