目录
一、代理模式:
对其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
二、不同代理模式详解
1、静态代理
创建步骤:
(1)、分别创建一个行为接口A,和该接口的实现类 B。实现接口中的具体行为业务方法;
(2)、创建一个代理类C实现行为接口A,并且该类通过构造函数引用了实现了行为接口的类B的对象。
(3)、代理类C中实现的接口A中的方法实现,是通过对象B的引用调用自己的所实现的业务方法sayHello。
代码实现:
public interface ISayHello {
void sayHello();
}
public class SayHelloImpl implements ISayHello{
@Override
public void sayHello() {
System.out.println("hello world");
}
}
public class SayHelloProxy implements ISayHello {
private ISayHello iSayHello;
public SayHelloProxy(ISayHello iSayHello) {
this.iSayHello = iSayHello;
}
@Override
public void sayHello() {
System.out.println("开始事务");
iSayHello.sayHello();
System.out.println("结束事务");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
SayHelloImpl sayHello = new SayHelloImpl();
SayHelloProxy sayHelloProxy = new SayHelloProxy(sayHello);
sayHelloProxy.sayHello();
}
}
结果:
开始事务
hello world
结束事务
缺点:
- (1)、因为代理对象需要与目标对象同时要实现相同的接口,造成一个代理类对应一个目标类的局面,或者都集中写在一个 代理类 中那么没增加一个行为接口的代理,就要增加implemets,和注入该目标对象的引用。如果目标类太多,则不 可控;
- (2)、并且如果行为接口中增加行为,或者变更行为,那么代理对象和目标对象都要同时维护;维护成本也会越来越高;
解决方案:动态代理就是为了解决静态代理的缺点的。
2、动态代理模式
实现步骤:
(1)、通过实现InvocationHandler接口创建自己的调用处理器。
(2)、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类。
(3)、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型。
(4)、通过构造函数创建动态代理类实例,构造时调用处理器对象作。
代码实现如下:
源码解析:
- target.getClass().getClassLoader() : 获取代理对象的类加载器。
- target.getClass().getInterfaces(): 获取代理对象实现的接口数组。
- new InvocationHandler():创建执行代理对象指定方法的事件处理接口的实例。
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);
}
}
我们通过解析源码来了解,动态代理是一个怎么的实现;
(1)、Objects.requireNonNull(h); 的实现如下:
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
如果传入的h,是null则会抛出空指针的异常;保证了h不为null才能继续走下去;
(2)、final Class<?>[] intfs = interfaces.clone();
首先我们来解析interfaces是怎么来的,通过发射获得代理对象实现的接口组;
源码如下:
public Class<?>[] getInterfaces() {
ReflectionData<T> rd = reflectionData();
if (rd == null) {
// no cloning required
return getInterfaces0();
} else {
Class<?>[] interfaces = rd.interfaces;
if (interfaces == null) {
interfaces = getInterfaces0();
rd.interfaces = interfaces;
}
// defensively copy before handing over to user code
return interfaces.clone();
}
}
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
ReflectionData为了提高反射的性能,缓存显然是必须的。class类内部有一个useCaches静态变量来标记是否使用缓存,这个值可以通过外部配置项sun.reflect.noCaches进行开关。
class类内部提供了一个ReflectionData内部类用来存放反射数据的缓存,并声明了一个reflectionData域,由于稍后进行按需延迟加载并缓存,所以这个域并没有指向一个实例化的ReflectionData对象。
从class内部的reflectionData缓存中读取数据interfaces,如果没有缓存数据,那么就从jvm中去请求数据,然后设置到缓存中,供下次使用。这种情况下是不需要interfaces.clone(); 如果缓存中有数据则需要interfaces.clone();一份返回;
(3)、克隆一份接口实例组;
final Class<?>[] intfs = interfaces.clone();
找出缓存proxyClassCache中的代理类/接口, 如果没有就生成一个;代理接口数组不能大于65535,否则抛异常;
Class<?> cl = getProxyClass0(loader, intfs);
(4)、SecurityManager 是安全验证,正常情况下是null,不需要验证;
(5)、final Constructor<?> cons = cl.getConstructor(constructorParams);
private static final Class<?>[] constructorParams =
{ InvocationHandler.class };
constructorParams:标明代理类构造器的参数类型。
调用代理对象的构造函数(代理对象的构造函数$Proxy0(InvocationHandler h);通过反射工厂类,copy一份代理类的构造器出来;
(6)、生成代理类的实例,并把MyInvocationHander的实例作为构造函数参数传入;
return cons.newInstance(new Object[]{h});
看完源码我们已经了解代理类的实例的创建过程了,但是我们可能还有个疑问就是
invoke方法是怎么调用的?
其实刚才我已经说了,“ 生成代理类的实例,并把MyInvocationHander的实例作为构造函数参数传入;“
通过字节码反编译可以查看生成的代理类:
生成的代码文件是:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.sjms.ISayHello;
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 ISayHello {
private static Method m1;
private static Method m3;
private static Method m2;
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 void sayHello() throws {
try {
super.h.invoke(this, m3, (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 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"));
m3 = Class.forName("com.sjms.ISayHello").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
看到代理对象,编译后的代码,就明白为什么invoke方法被调用了;
JDK动态代理有个缺点,那就是不能对类进行代理,只能对接口进行代理,如果我们的类没有实现任何接口,
那么就不能使用这种方式进行动态代理(因为$Proxy()这个类集成了Proxy,Java的集成不允许出现多个父类)。
3、动态代理的另外一种实现:cglib代理
(1)、定义:
有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理。(也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展)。
(2)、应用场景:
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用;
例如Spring AOP和synaop,为他们提供方法的interception(拦截) Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
Cglib子类代理实现方法:
- 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core.jar即可.
- 引入功能包后,就可以在内存中动态构建子类
- 代理的类不能为final,否则报错 d.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
package com.sjms.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Description:
* User: zhurong
* Date: 2018-08-24 22:58
*/
public class SayHello {
public static void main(String[] args) {
SayHello sayHello = new SayHello();
SayHelloCglib cglib = new SayHelloCglib();
SayHelloCglib cglibProxy=(SayHelloCglib)cglib.getInstance(sayHello);
cglibProxy.sayHello();
}
public void sayHello(){
System.out.println("hello world");
}
class SayHelloCglib implements MethodInterceptor {
private Object target;//业务类对象,供代理方法中进行真正的业务方法调用
//相当于JDK动态代理中的绑定
public Object getInstance(Object target) {
this.target = target;
//创建加强器,用来创建动态代理类
Enhancer enhancer = new Enhancer();
//为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
enhancer.setSuperclass(this.target.getClass());
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
// 实现回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("处理前");
proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
System.out.println("处理后");
return null;
}
}
}
总结:
三、CGLIB和Java动态代理的区别
- Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);
- CGLIB能够代理普通类;
- Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效。