文章目录
1. 代理模式
1.1 什么是代理模式?
在访问对象时,直接访问对象会给使用者或者系统结构带来很多麻烦,因此我们就可以在访问次对象的基础上加上一个对此对象的访问层。给某一个对象提供一个代理,并由代理对象来控制真实对象的访问。
1.2 代理模式的结构
一般来说,代理模式中,需要出现4个角色来。首先是真实对象,然后就这个真实对象的代理者,以及真实对象与代理对象共同实现的接口,最后还有一个就是client。
我们以手机为例,来描述一个代理结构。首先,我们有一个手机实体类MiPhone,然后还有一个代理类MiPhoneProxy,这两个类实现了一个共同的接口MI。
其类关系如图:
1.3 分类
java的字节码文件(即.class文件),主要的生成方式有两种:
- 编译.java文件生成;
- 直接编写.class文件。
根据字节码的创建时机不同,我们可以将其分为静态代理和动态代理。 - 静态代理:所谓静态代理,就是在程序运行前就已经创建好代理类的字节码文件,代理类和真实类之间的关系,早在运行前就已经确定了。
- 动态代理:动态代理的字节码文件则是在程序运行期间,由jvm根据反射机制等动态的生成,所以在运行前并不会存在代理类的字节码文件。
2.静态代理
2.1 实例
静态代理比较简单,即字节码文件在程序运行前就已经存在了。我们一般在编写完java文件后,使用javac指令编译即可。
以上面手机的类图关系为依据,写出如下代码:
首先是接口类MI.class
public interface MI {
void phone();
}
然后是实现了该接口的实例类MiPhone.class
public class MiPhone implements MI {
public String name;
private Long id;
public MiPhone(String name) {
this.name = name;
}
@Override
public void phone() {
System.out.println("i am a mi Phone named " + name);
}
}
实现了接口的代理类MiPhoneProxy.class
public class MiPhoneProxy implements MI {
private MI mi;
public MiPhoneProxy(MI mi) {
this.mi = mi;
}
@Override
public void phone() {
checkStore();
mi.phone();
getPackage();
}
private void checkStore() {
System.out.println("check check");
}
private void getPackage() {
System.out.println("package Package");
}
}
最后就是我们的Client.java类
public class Client {
public static void main(String[] args) {
MiPhoneProxy mix = new MiPhoneProxy(new MiPhone("Mix"));
mix.phone();
}
}
编译上面的程序,我们可以看到MiPhoneProxy.class文件已经生成,并且在程序运行结束后,我们可以得到如下的输出:
2.2 优缺点
使用静态代理的优点:
- 不破坏原来结构的基础之上,我们可以增加一些其他的操作,增强了原来的功能。
- 没有入侵原代码
- 实现较为简单。
但是静态代理的缺点也很明显,比方说现在又出现了MiWatch.java和MiLaptop.java,要想继续使用静态代理的话,有两种做法:
- 1.新建多个代理类,每个不同类别的产品就多一个代理结构的类。这样会早成系统中存在大量这样的类,难以维护。
- 2.那就只维护一个代理类,由这个类实现多个接口,但是这个代理类也会过于庞大,难以维护。
而且当接口需要增加、删除或者修改方法的时候,代理类与实际的类都需要修改,我懒不想改那么多就算了,还容易出错,不想改这么多。
3.动态代理
上面静态代理的缺点那么明显,就只能引入动态代理来解决这个问题了。
3.1 为什么字节码可以动态生成?
这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口
由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
- 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
- 从网络中获取,典型的应用是 Applet
- 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流
- 由其它文件生成,典型应用是JSP,即由JSP文件生成对应的Class类
- 从数据库中获取等等
所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。
3.2 动态代理的两个实现方式
动态代理最常见的实现方式有两个:
- JDK动态代理
- CGLIB动态代理
3.2.1 JDK动态代理
静态代理与动态代理相比较而言,只是proxy类的字节码生成的时期不同,那么我们继续使用上面的类关系,只需要改变代理类即可。代理类这里不再需要实现共同的接口,需要实现InvocationHandler.class。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MiPhoneProxy implements InvocationHandler {
private Object mi;
public MiPhoneProxy(Object mi) {
this.mi = mi;
}
private void checkStore() {
System.out.println("check check");
}
private void getPackage() {
System.out.println("package Package");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
checkStore();
Object invoke = method.invoke(mi, args);
getPackage();
return invoke;
}
}
在Client中的调用方法也发生了一些改变:
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
MiPhoneProxy mix = new MiPhoneProxy(new MiPhone("mix"));
Object o = Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{MI.class}, mix);
((Mi)o).phone();
}
}
当我们运行的时候,会得到和静态代理一样的输出。
那这个动态代理有没有改善静态代理局限性呢?如果此时再增加一个MiWatch.java要怎么办呢?
这个时候,我们不需要增加新的代理类或者扩展接口?都不需要!只需要我们在Client类中调用的时候改变传入的参数即可。例如有如下Watch接口和RedMIWatch实体类。
Watch.java
public interface Watch {
void showWatch();
}
RedMiWatch.java
public class RedMiWatch implements Watch {
@Override
public void showWatch() {
System.out.println("i am a redMi watch");
}
}
不需要增加新的代理类,我们不需要!!
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
MiPhoneProxy miPhoneProxy = new MiPhoneProxy(new RedMiWatch());
Object o = Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Watch.class}, miPhoneProxy);
((Watch)o).showWatch();
}
}
我们得到了如下的输出
在这里,突然就有了一个疑问,为什么一定要在这里声明一个接口呢?直接用一个RedMiWatch类不行吗?
这让我想到之前就看到的一句话:JDK代理只能代理接口,不可以是类。为什么呢?因为JAVA是不支持多继承的。
那么就让我们跟一下源码,看看到底是不是这个原因!
这个是Proxy类中的静态方法newProxyInstance,一共三个参数,第一个是classloader,第二个是class数组,第三个是InvocationHandler。
/**
* 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
{
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<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
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;
}
});
}
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<?> cl = getProxyClass0(loader, intfs)点进去,可以进入静态类
/**
* 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调用get方法,其中proxyClassCache为
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
传入的参数时两个factory,其中ProxyClassFactory是一个BiFunction,是一个私有类,即
/**
* 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());
}
}
}
重点关注其apply方法,该方法中的
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
将产生字节码文件。我们可以把字节码文件打印出来。
怎么做呢?添加一个方法到client中
package com.example.dl;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
RedMiWatch mi = new RedMiWatch();
MiPhoneProxy miPhoneProxy = new MiPhoneProxy(mi);
Object o = Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Watch.class}, miPhoneProxy);
((Watch)o).showWatch();
saveFile();
}
private static void saveFile() {
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", RedMiWatch.class.getInterfaces());
FileOutputStream outputStream = null;
try{
outputStream = new FileOutputStream( "MiPhoneProxy.class");
outputStream.write(classFile);
outputStream.flush();
}
catch (Exception e) {
e.printStackTrace();
}finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
打印出的字节码文件如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.example.dl.Watch;
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 Watch {
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 showWatch() 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.example.dl.Watch").getMethod("showWatch");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到,当前的字节码文件中,类$Proxy0继承了类Proxy,同时实现了Watch接口。至此,一目了然:java是不支持多继承,由于已经继承了Proxy类,只能实现接口了。
3.2.1 CGLIB代理
还是使用上面的例子,这里就不需要那么多接口了,我们首先要有实际被代理的类(不变)以及代理类。
代理类CGLIBProxy.java如下:
package com.example.dl;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBProxy implements MethodInterceptor {
private void checkStore() {
System.out.println("check check");
}
private void getPackage() {
System.out.println("package Package");
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
checkStore();
Object o1 = methodProxy.invokeSuper(o, objects);
getPackage();
return o1;
}
}
那么在Client中如何调用呢?
public class Client {
public static void main(String[] args) {
CGLIBProxy cglibProxy = new CGLIBProxy();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RedMiWatch.class);
enhancer.setCallback(cglibProxy);
Object o = enhancer.create();
((RedMiWatch)o).showWatch();
}
}
这样执行完以后,打印的结果如下:
这个源码暂时不想看了,埋个坑,有空再填。
3.3 两种动态代理方式的对比
JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。
cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
JDK Proxy 的优势:
- 最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
- 平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
- 代码实现简单。
基于类似 cglib 框架的优势:
- 无需实现接口,达到代理类无侵入
- 只操作我们关心的类,而不必为其他相关类增加工作量。
- 高性能