温馨提示!
请带着对Proxy代理的四问阅读本篇文章:是什么? 有哪些? 怎么用? 用哪个?
一、代理模式(Proxy Pattern) 是什么?
1、概念
代理模式是23种设计模式中一种比较好理解的模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。(AOP)
2、举例
人们常说的经纪人与明星之间的关系,就是代理与被代理的关系。一般情况下,明星也就是被代理对象为主要内容,经纪人也就是代理对象,经纪人在明星表演前后做准备和收尾工作。
二、Java中代理模式有哪些?
1、Java的三种代理模式 :
静态代理、JDK动态代理(核心:反射机制)、 CGLIB动态代理(核心:ASM机制)
2、反射机制与ASM机制
a、反射
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。
Java 反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理。
b、ASM
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
CGLIB动态代理底层主要就是通过ASM来修改操作被代理类。
ASM的具体操作涉及到汇编语言,不做过多描述。
三、三种代理应该怎么用?
1、静态代理
a、概念
所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和被代理类的关系在运行前就确定了。所以代理对象必须在编译时写出,如果接口层发生了变化,代理对象的代码也要进行维护。
静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。
b、实例演示
- 定义一个接口及其实现类
/**
* 一个唱歌方法的接口
*/
public interface ISinger {
void sing();
}
/**
* 被代理类
*/
public class Singer implements ISinger{
public void sing(){
System.out.println("唱一首歌");
}
}
2.创建一个代理类实现被代理类相同的接口并重写接口中的所有方法
/**
* 代理类和目标类实现相同的接口ISinger
*/
public class SingerProxy implements ISinger{
private ISinger target; //注入被代理对象,以便调用sing方法
public SingerProxy(ISinger target){ //代理类的构造方法,传入被代理对象
this.target=target;
}
// 在重写接口对应的方法中对被代理对象的sing方法进行增强
public void sing() {
System.out.println("向观众问好"); //静态代理前置内容
target.sing(); //调用被代理对象方法
System.out.println("谢谢大家"); //静态代理后置内容
}
}
3、调用方式展示
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
//被代理对象
ISinger target = new Singer();
//代理对象
ISinger proxy = new SingerProxy(target); //传入被代理对象
//执行的是代理的方法
proxy.sing();
}
}
运行测试类的结果:
向观众问好
唱一首歌
谢谢大家
c、优缺点
缺点:静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。
优点:运行时效率会稍微好一点。
如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼, 不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
2、JDK动态代理
a、概念
为解决静态代理对象必须实现接口的所有方法的问题,Java给出了动态代理,动态代理具有如下特点:
1.Proxy对象不需要implements接口;
2.Proxy对象的生成利用JDK的Api,在JVM内存中动态的构建Proxy对象。需要使用java.lang.reflect.Proxy类的newProxyInstance方法。
b、实例演示
- 同样定义一个接口及其实现类
/**
* 一个唱歌方法的接口
*/
public interface ISinger {
void sing();
}
/**
* 被代理类
*/
public class Singer implements ISinger{
public void sing(){
System.out.println("唱一首歌");
}
}
2.构建代理内容及重写invoke对被代理对象的方法进行增强
/**
* 代理内容
* */
public class SingerDynamicProxy implements InvocationHandler {
private Ising target; //注入被代理对象
public SingerDynamicProxy(Ising target) {//代理类的构造方法,传入被代理对象
this.target = target;
}
// 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,
// 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
// InvocationHandler这个接口的唯一一个方法 invoke 方法:
// 该方法接收3个参数:
// target: 指代被代理对象
// method: 指代的是被代理对象的某个方法的Method对象
// args: 指代的是调用被代理对象某个方法时接受的参数
@Override
public Object invoke(Object target, Method method, Object[] args) throws Throwable {
System.out.println("向观众问好 ...JDK动态代理前置内容 ");// 在代理真实对象前我们可以添加一些自己的操作
Object obj = method.invoke(target, args); // 当代理对象调用被代理对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
System.out.println("谢谢大家 ...JDK动态代理后置内容 ");// 在proxy被代理对象后我们也可以添加一些自己的操作
return obj;
}
}
3.调用方式展示
public class Test2{
public static void main(String[] args) {
//被代理对象
Ising target=new Singer();
//代理对象
// 我们要proxy哪个被代理对象,就将该对象传进去,最后是通过该被代理对象来调用其方法的
InvocationHandler proxy = new SingerDynamicProxy(target);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,三个参数:
* 第一个参数 :加载器用于定义代理类的类加载器 ,handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数: 被代理对象的类的接口列表实现 ,target.getClass().getInterfaces(),我们这里为代理对象提供的接口是被代理对象所实行的接口,表示我要代理的是该被代理对象,这样我就能调用这组接口中的方法了
* 第三个参数: 设置调用处理程序,将方法调用分派给的代理对象, handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Ising ising = (Ising) Proxy.newProxyInstance(
proxy.getClass().getClassLoader(),
target.getClass().getInterfaces(),
proxy); //生成SingerDynamicProxy且实现ISinger接口的一个类
ising.sing();
}
}
运行测试类的结果:
向观众问好 ...JDK动态代理前置内容
唱一首歌
谢谢大家 ...JDK动态代理后置内容
4.核心方法newProxyInstance源码解析
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);
}
}
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);
}
c、优缺点
优点:解决了静态代理的必须在编译时写出代理类的具体内容,且接口发生变动需要同时修改代理类和被代理类的问题。
缺点:可以看出静态代理和JDK代理有一个共同的缺点, 就是目标对象必须实现一个或多个接口,如果没有,则可以使用Cglib代理。
3、CGLIB动态代理
a、概念
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB 原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB缺点:对于final方法,无法进行代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
下图是Enhancer 类的继承关系
ClassGenerator是Cglib的核心接口,其中核心方法generateClass方法用于产生目标类;
b、实例演示
1.不同于 JDK 动态代理不需要额外的依赖。CGLIB实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2.直接定义一个被代理类
/**
* 被代理类
*/
public class Singer{
public void sing(){
System.out.println("唱一首歌");
}
}
3.自定义 MethodInterceptor(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("向观众问好 ...CGLIB动态代理前置内容 ");
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("谢谢大家 ...CGLIB动态代理后置内容 ");
return object;
}
}
4.获取代理类
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
5.调用方式展示
public class Test3{
public static void main(String[] args) {
Singer singer = (Singer) CglibProxyFactory.getProxy(Singer.class);
singer.sing();
}
}
运行测试类的结果:
向观众问好 ...CGLIB动态代理前置内容
唱一首歌
谢谢大家 ...CGLIB动态代理后置内容
6.源码分析
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;
//调用AbstractClassGenerator的create()方法
Object result = super.create(key);
return result;
}
protected Object create(Object key) {
try {
//获取set进去的类加载器
ClassLoader loader = getClassLoader();
Map<ClassLoader, ClassLoaderData> cache = CACHE;
//根据类加载器获取缓存的代理类,若没用则新建一个
ClassLoaderData data = cache.get(loader);
if (data == null) {
synchronized (AbstractClassGenerator.class) {
cache = CACHE;
data = cache.get(loader);
if (data == null) {
Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
//调用AbstractClassGenerator的generate()方法生成代理类
data = new ClassLoaderData(loader);
//将生成的代理类放入缓存中
newCache.put(loader, data);
//创建实例
CACHE = newCache;
}
}
}
this.key = key;
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
//返回生成的代理类的实例
return firstInstance((Class) obj);
}
return nextInstance(obj);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
public ClassLoaderData(ClassLoader classLoader) {
if (classLoader == null) {
throw new IllegalArgumentException("classLoader == null is not yet supported");
}
this.classLoader = new WeakReference<ClassLoader>(classLoader);
Function<AbstractClassGenerator, Object> load =
new Function<AbstractClassGenerator, Object>() {
public Object apply(AbstractClassGenerator gen) {
//调用generate()方法
Class klass = gen.generate()(ClassLoaderData.this);
return gen.wrapCachedClass(klass);
}
};
generatedClasses = new LoadingCache<AbstractClassGenerator, Object, Object>(GET_KEY, load);
}
protected Class generate(ClassLoaderData data) {
Class gen;
Object save = CURRENT.get();
CURRENT.set(this);
try {
ClassLoader classLoader = data.getClassLoader();
if (classLoader == null) {
throw new IllegalStateException("ClassLoader is null while trying to define class " +
getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " +
"Please file an issue at cglib's issue tracker.");
}
synchronized (classLoader) {
String name = generateClassName(data.getUniqueNamePredicate());
data.reserveName(name);
this.setClassName(name);
}
if (attemptLoad) {
try {
gen = classLoader.loadClass(getClassName());
return gen;
} catch (ClassNotFoundException e) {
// ignore
}
}
//该方法最终是使用ClassGenerator类的generate方法生成代理类
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
ProtectionDomain protectionDomain = getProtectionDomain();
synchronized (classLoader) { // just in case
if (protectionDomain == null) {
gen = ReflectUtils.defineClass(className, b, classLoader);
} else {
gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
}
}
return gen;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
} finally {
CURRENT.set(save);
}
}
public interface ClassGenerator {
//最终生成代理类的方法
void generateClass(ClassVisitor v) throws Exception;
}
c、优缺点
优点:执行效率高、被代理类无需实现接口;
缺点:创建效率低、字节码库需要进行更新以保证在新版java上能运行;
四、在哪种情况下用哪个?
1、动态代理 VS 静态代理
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,非常麻烦。
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的
SO~ 静态代理OUT!!!
2、JDK VS CGLIB
- JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 大部分情况都是 JDK 动态代理更稳定,随着 JDK 版本的升级,这个优势更加明显。CGLIB无需实现接口,达到代理类无侵入,只操作关心的类,而不必为其他相关类增加工作量,高性能。
- 作为Java领域的标杆项目Spring,很多功能都选择使用CGLIB来实现。(那我们就都用CGLIB? no,比如追求稳定、少维护的企业,还是选择更可靠的JDK动态代理好一些)
额~ 伯仲之间,根据实际情况选择即可,over。