写在前面
- 代理模式
代理模式,指的是给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。
为什么要引入这个代理对象呢,两个目的:
- 通过代理对象来间接访问目标对象,这样能防止直接访问目标对象带来的一些不必要的复杂性。
(什么不必要的麻烦呢?例如如果客户端需要访问的对象在服务端,客户端直接访问需要处理网络等一系列复杂的问题,如果使用代理模式那么客户端只需要和代理打交道,客户端不需要知道代理怎么和服务端交互。) -
通过代理对象来访问目标对象,能通过代理对象对原有业务进行增强。
- 代理模式有三种实现方式:
静态代理,Jdk动态代理,Cglib动态代理。
其中,静态代理和动态代理的区别在于代理类的生成时间不同。
静态代理
静态代理三大要素:
- 抽象对象(接口,约定了服务能够提供的功能)
- 真实对象(实现类)
- 代理对象
真实对象及代理对象都必须实现这个接口
代理对象必须包含真实对象。(它不提供服务,只是服务的搬运工)
面向对象设计开发的几个原则之一:
开闭原则(开放-封闭原则):程序对外扩展开放,对修改关闭。换句话说,当需求发生变化时,我们可以通过添加新模块来满足新需求,而不是通过修改原来的实现代码来满足新需求。
静态代理作为代理模式的第一个实现版本,违反了开闭原则:
- 让这个类的可扩展性大打折扣。
- 可维护性下降,牵一发,动全身。
而动态代理,很好的解决了这个缺点。
动态代理
何谓动态?动态指的是代理对象不是固定对象,而是一个动态对象。
动态代理没有源文件,直接在内存生成类的字节码文件。
- Jdk动态代理
*Tips*
在研究Jdk动态代理之前,先提出几点疑问:
a. 为什么Jdk动态代理一定要基于接口?
b. 怎么在内存生成字节码?
c. 生成的字节码长什么样?
d. 多个对象,实现同一个接口,若对这多个对象分别进行代理,会生成多个代理对象吗?
e. 多个对象,实现不同接口,若对这多个对象分别进行代理,是否会生成多个代理对象呢?
接下来,带着这几个问题来研究Jdk动态代理的底层源码。
1.1 Jdk动态代理的两大关键点:Proxy类和InvocationHandler接口。
Proxy - 调度器,生成代理对象
JdkProxy dynamicProxy=new JdkProxy(hello);
Greeting target1=(Greeting) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), dynamicProxy);
InvocationHandler - 代理对象到底有什么业务能力
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
before();
result=method.invoke(target, args);
after();
return result;
}
public void before(){
System.out.println("[JdkProxy] Come to someone.");
}
public void after(){
System.out.println("[JdkProxy] Back to his own corner");
}
}
1.2 探究Proxy类
Proxy类位于java.lang.reflect包中,追踪Proxy类的newProxyInstance方法,会定位到ProxyBuilder的如下方法,截取关键部分:
private static final class ProxyBuilder {
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
//......
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg.isEmpty()
? proxyClassNamePrefix + num
: proxyPkg + "." + proxyClassNamePrefix + num;
//......
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
try {
Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
0, proxyClassFile.length,
loader, null);
reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
return pc;
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
其中,generateProxyClass就是在内存里直接生成字节码的地方!它内部调用了generateClassFile方法(参见附录),这个方法就会根据Class文件的结构,来动态拼接出代理对象的字节码。
而UNSAFE.defineClass,就是直接根据刚才生成的字节码,生成Class对象。
这个defineClass方法,最终调用的是一个native方法。native方法是用C写的本地方法,直接调用操作系统的类库,来进行生成Class对象。
public native Class<?> defineClass0(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
1.3 看一看动态生成的字节码文件
既然已经在内存中生成了字节码文件,那么就把它保存到硬盘上,满足一下好奇心,看看里面是个什么玩意儿吧。
在ProxyGenerator中,有这样一个属性,它询问是否需要将动态代理的对象存到硬盘。(这里是基于jdk9的demo,如果用的是其它版本的jdk,那么属性可以会略有不同)
/** debugging flag for saving generated class files */
private static final boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"jdk.proxy.ProxyGenerator.saveGeneratedFiles")).booleanValue();
既然如此,那么只要将这个属性设为true,就能直接查看硬盘上的Class文件了。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
果不其然,在项目目录下,生成了com/sun/proxy子目录,下面有$Proxy0.class字节码文件。这个类继承了Proxy类,并且实现了Greeting接口。
public final class $Proxy0 extends Proxy implements Greeting {
}
//接口代理方法
public final void doGreet()
{
try
{
// invocation handler的 invoke方法在这里被调用
super.h.invoke(this, m3, null);
return;
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
1.4 关于InvocationHandler
1.4.1 InvocationHandler是一个接口。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
它被作为参数传给底层方法newProxyInstance(caller, cons, h)。这个方法里有一行注释:
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager
Constructor<?> cons,
InvocationHandler h) {
/*
* Invoke its constructor with the designated invocation handler.
*/
//......
}
1.4.2 追踪这个方法,会发现最终调用了一个native方法,InvocationHandler被作为参数传过去了。
private static native Object newInstance0(Constructor<?> c, Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException;
这个类实际做的事情就是:
- 根据前面生成的类$Proxy0的字节码,来实例化这个类
- 并且,将invocationHandler传递给$Proxy0的父类Proxy的构造函数,初始化Proxy的属性h
/**
* Constructs a new {@code Proxy} instance from a subclass
* (typically, a dynamic proxy class) with the specified value
* for its invocation handler.
*
* @param h the invocation handler for this proxy instance
*
* @throws NullPointerException if the given invocation handler, {@code h},
* is {@code null}.
*/
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
1.4.3 invoke方法的参数
- InvocationHandler接口定义了一个invoke方法,它接收三个参数。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
第二个参数method,和第三个参数args,指的是代理类被调用的方法和参数。
而第一个参数proxy,很少会被用到,其实它指的就是代理对象。将代理对象传递进来有两个用处:
可以通过反射获取代理对象的信息。
可以将代理对象返回进行连续调用。
- 用一个例子感受一下。(截取部分代码,完整代码见附录)
实现InvocationHandler的invoke方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();
method.invoke(target, args);
after();
System.out.println("JdkProxy.this is : " + this.getClass());
System.out.println("proxy is : " + proxy.getClass());
return proxy;
}
通过代理类调用目标方法。
Meeting videoMeeting = new VideoMeeting();
dynamicProxy = new JdkProxy(videoMeeting);
Meeting target4 = (Meeting) Proxy.newProxyInstance(videoMeeting.getClass().getClassLoader(),
videoMeeting.getClass().getInterfaces(), dynamicProxy);
target4.haveMeeting().haveMeeting();
执行,通过输出,可以发现proxy就是动态生成的代理类$Proxy1,而目标方法通过连续调用,被代理类调用了两次。
[JdkProxy] Come to someone.
Meeting by video .
[JdkProxy] Back to his own corner
JdkProxy.this is : class spring.core.aop.jdk.JdkProxy
proxy is : class com.sun.proxy.$Proxy1
[JdkProxy] Come to someone.
Meeting by video .
[JdkProxy] Back to his own corner
JdkProxy.this is : class spring.core.aop.jdk.JdkProxy
proxy is : class com.sun.proxy.$Proxy1
1.5 为什么Jdk动态代理必须基于接口
这是由jdk动态代理的设计决定的
通过查看动态代理类的代码,发现它继承了Proxy类。而Java是单继承,不能同时继承两个类,所以我们需要和想要代理的类建立联系,那就只能通过接口来实现。那么问题来了,jdk动态代理为什么要设计成只允许动态代理接口呢?
在代理模式中,代理类只做一些额外的拦截处理,实际处理是转发到原始类做的。
如果允许动态代理一个类,那么代理对象也会继承类的字段,而这些字段实际上是没有使用的,因为代理对象只做转发处理,对象的字段存取都是在原始对象上处理,所以如果代理一个类,对内存空间是一种浪费。
1.6 缓存机制
调用过程中发现,对于不同的类做动态代理,生成的代理对象,有时候是同一个对象。这是因为当需要生成代理对象字节码之前,会先查看缓存,是否已经存在同样classLoader&同样interface的代理对象,如果存在,则直接复用缓存中的class字节码。
- Cglib动态代理
(Code Generator Library)
*Tips*
同样,在研究Cglib动态代理之前,同样提出几个问题:
a. 为什么Cglib动态代理不用基于接口?
b. 怎么在内存生成字节码?
c. 生成的字节码长什么样?
2.1 Cglib动态代理的两大关键点
- Enhancer类
类似于jdk动态代理中的Proxy类,只不过,Enhancer类既能代理普通的类,也能够代理接口。
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls, this);
}
- MethodInterceptor接口
重写接口中的intercept方法对目标方法进行拦截。
@Override
public Object intercept(Object obj, Method method, Object[] arg,
MethodProxy proxy) throws Throwable {
Object result=null;
try {
before();
result= proxy.invokeSuper(obj, arg);
after();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
2.2 追踪Enhancer.create()方法
会发现Cglib的底层是调用了ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来进行处理。
private Object createHelper() {
preValidate();
Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID);
this.currentKey = key;
Object result = super.create(key);
return result;
}
2.3 关于MethodInterceptor
方法拦截器
2.4 动态代理对象的字节码文件
public class SayHello$$EnhancerByCGLIB$$4da4ebaf extends SayHello
implements Factory
{
}
2.5 当被代理方法或者对象被final修饰
- 被代理方法被final修饰时,若进行代理,则增强无效。
- 被代理对象被final修饰时,若进行代理,则运行时报错,如下。
Exception in thread "main" java.lang.IllegalArgumentException: Cannot subclass final class spring.core.aop.SayHello
- 比较jdk动态代理与cglib动态代理
3.1 各自局限
JDK动态代理:只能代理实现了接口的类。
Cglib动态代理:由于它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,采用了继承,所以不能对final修饰的类进行代理。
3.2 各自优势
JDK动态代理:最小化依赖关系,减少依赖意味着简化开发和维护,由于JDK本身的支持,可能比Cglib更可靠。
Cglib动态代理:可以代理没有接口的类。并且性能相对更高。
3.3 性能比较(未考证)
jdk8之前,cglib效率更高。
jdk8及之后,jdk效率更高。
附上部分底层代码及示例代码
底层方法
generateClassFile
private byte[] generateClassFile() {
/* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/
/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
/*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
/* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
/* ============================================================
* Step 3: Write the final class file.
*/
/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout); // (write constant pool)
// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));
// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}
// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}
// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
return bout.toByteArray();
}
完整示例代码-静态代理
public interface Greeting {
public void doGreet();
}
public class SayHello implements Greeting {
@Override
public void doGreet() {
System.out.println("Greeting by say 'hello' .");
}
}
public class GreetStaticProxy implements Greeting {
private Greeting hello;//被代理对象
public GreetStaticProxy(Greeting hello){
this.hello=hello;
}
@Override
public void doGreet() {
before();//执行其他操作
this.hello.doGreet();//调用目标方法
after();//执行其他操作
}
public void before(){
System.out.println("[StaticProxy] Come to someone.");
}
public void after(){
System.out.println("[StaticProxy] Back to his own corner");
}
}
public class Main {
public static void main(String[] args) {
Greeting hello=new SayHello();
//静态代理
GreetStaticProxy staticHelloProxy=new GreetStaticProxy(hello);
staticHelloProxy.doGreet();
System.out.println();
}
}
完整示例代码-JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JdkProxy implements InvocationHandler {
private Object target;
public JdkProxy(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
before();
method.invoke(target, args);
after();
System.out.println("JdkProxy.this is : " + this.getClass());
System.out.println("proxy is : " + proxy.getClass());
return proxy;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public void before(){
System.out.println("[JdkProxy] Come to someone.");
}
public void after(){
System.out.println("[JdkProxy] Back to his own corner");
}
}
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Greeting hello=new SayHello();
Greeting shakeHands=new ShakeHands();
//jdk动态代理
JdkProxy dynamicProxy=new JdkProxy(hello);
Greeting target1=(Greeting) Proxy.newProxyInstance(hello.getClass().getClassLoader(),
hello.getClass().getInterfaces(), dynamicProxy);
target1.doGreet();
System.out.println();
}
}
完整示例代码-CGLIB动态代理
public final class SayHello {
public final void doGreet() {
System.out.println("Greeting by say 'hello' .");
}
}
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
public static CglibProxy proxy=new CglibProxy();
private CglibProxy(){}
public static CglibProxy getInstance(){
return proxy;
}
public <T> T getProxy(Class<T> cls){
return (T) Enhancer.create(cls, this);
}
@Override
public Object intercept(Object obj, Method method, Object[] arg,
MethodProxy proxy) throws Throwable {
Object result=null;
try {
before();
result= proxy.invokeSuper(obj, arg);
after();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
public void before(){
System.out.println("[cglib] Come to someone.");
}
public void after(){
System.out.println("[cglib] Back to his own corner.");
}
}
public class Main {
public static void main(String[] args) {
//cglib代理
SayHello targetProxy=CglibProxy.getInstance().getProxy(SayHello.class);
targetProxy.doGreet();
System.out.println();
CglibProxy.getInstance().getInstance().getProxy(KissHello.class).doGreet();
}
}