代理模式
决定你的人生高度的,不是你的才能,而是你的人生态度!
代理模式在生活中随处可见,比如说房产中介,快递小哥,黄牛党等等,都是替客户办事,这种形式在代码世界称之为代理模式,今天我们来深入探讨代理模式。
1. 代理模式
定义
指为其他对象提供一种代理,以控制对这个对象的访问
代理对象在客户端和目标对象中间起到中介作用
属于结构型设计模式
适用场景
保护目标对象
增强目标对象
缺点
会造成系统设计中类的数目增加
在客户端和目标对象之间增加乐意个代理对象,请求处理速度变慢
增加了系统的复杂度
2. 一般代理
代码
public interface ISubject {
void request();
}
public class Subject implements ISubject {
public void request() {
System.out.println("请求主题");
}
}
public class Proxy {
private ISubject subject;
public Proxy(ISubject subject) {
this.subject = subject;
}
public void request() {
before();
subject.request();
after();
}
public void before() {
System.out.println("开始");
}
public void after() {
System.out.println("结束");
}
}
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy(new Subject());
proxy.request();
}
}
类图
在代理类Proxy
中,before()
、after()
方法为增强方法,增强了request()
方法。
3. 静态代理
代码
public interface IPerson {
void findLove();
}
public class Lisi implements IPerson {
public void findLove() {
System.out.println("要求:肤白貌美大长腿!");
}
}
public class LiLaoSi implements IPerson {
private Lisi lisi;
public LiLaoSi(Lisi lisi) {
this.lisi = lisi;
}
public void findLove() {
System.out.println("开始物色");
lisi.findLove();
System.out.println("找到了");
}
}
public class Test {
public static void main(String[] args) {
LiLaoSi liLaoSi = new LiLaoSi(new Lisi());
liLaoSi.findLove();
}
}
开始物色
要求:肤白貌美大长腿!
找到了
类图
代理类LiLaoSi
为儿子LiSi
物色对象,体现了静态代理的特点,只能代理一个人,比如说另外一个张三
也想让LiLaoSi
物色对象,那么就没有办法了。动态代理可以解决这个问题。
4. 动态代理
4.1 JDK动态代理
代码
public interface IPerson {
void findLove();
}
public class Lisi implements IPerson {
public void findLove() {
System.out.println("要求:肤白貌美大长腿!");
}
}
public class Wangwu implements IPerson {
public void findLove() {
System.out.println("要求:有车有房!");
}
}
public class JdkProxy implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target) {
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void after() {
System.out.println("OK的话, 开始办事儿!");
}
private void before() {
System.out.println("我是媒婆,我给你找对象,已经确认你的需求");
System.out.println("开始物色");
}
}
public class Test {
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy();
IPerson lisi = proxy.getInstance(new Lisi());
lisi.findLove();
IPerson wangwu = proxy.getInstance(new Wangwu());
wangwu.findLove();
}
}
我是媒婆,我给你找对象,已经确认你的需求
开始物色
要求:肤白貌美大长腿!
OK的话, 开始办事儿!
我是媒婆,我给你找对象,已经确认你的需求
开始物色
要求:有车有房!
OK的话, 开始办事儿!
JDK
动态代理需要实现InvocationHandler
接口。如果被代理对象又有了新的需求,那么只需要IPerson
增加方法,实现类实现方法,Test
类直接调用这个方法,并不需要修改任何JDKProxy
代理类,就能达到目的。我们看到,JDKProxy
的return (IPerson)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
clazz.getInterfaces()
说明JDK代理只能针对接口代理,一般的class
是无法代理的。
4.2 Cglib动态代理
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
public Object getInstance(Class<?> calzz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(calzz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
private void after() {
System.out.println("结束");
}
private void before() {
System.out.println("开始");
}
}
public class Test {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Lisi instance = (Lisi)cglibProxy.getInstance(Lisi.class);
instance.findLove();
}
}
开始
要求:肤白貌美大长腿!
结束
Cglib
的主要用法就是这样,一般开发用不到。Spring
中用的是Cglib
代理。我们可以看到,Cglib
是创造了一个类,然后这个类继承了被代理对象,说明代理类不仅仅可以代理接口,还能代理一般的class
4.3 JDK与Cglib动态代理对比
Cglib是继承的方式,覆盖父类的方法
JDK采用实现的方式,必须要求代理的目标对象啊你给一定要实现一个接口
两者的思想:都是通过生成字节码,重组成一个新的类
JDK Proxy对于用户而言,以来更强,调用页更复杂
Cglib对目标类没有任何要求
Cglib 效率更高,性能也更高,底层没有用到反射
JDK Proxy生成逻辑较为简单,执行效率要低,每次都要用反射
Cglib 有个坑,目标对象不能有final的方法,因为final方法无法被重写,所以会自动忽略final方法
5. JDK动态代理分析
思考一个问题:IPerson lisi = proxy.getInstance(new Lisi());
生成的lisi
是Lisi
生成的对象还是代理生成的对象呢?
证明方法:程序打断点。
lisi
在内存中是$Proxy0
的对象,并不是Lisi
对象,说明这个对象是通过代理生成的。那我们可以把这个内存中的对象序列化到本地磁盘,看看这个类是怎么样的。
public static void main(String[] args){
JdkProxy proxy = new JdkProxy();
IPerson lisi = proxy.getInstance(new Lisi());
lisi.findLove();
byte[] ss = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{IPerson.class});
try {
FileOutputStream os = new FileOutputStream("E://$Proxy0.class");
os.write(ss);
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
我们把$Proxy0
写到了磁盘$Proxy0.class
文件。如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.test.proxy.dynamicproxy.IPerson;
public final class $Proxy0 extends Proxy implements IPerson {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m4;
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 findLove() 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 void baby() throws {
try {
super.h.invoke(this, m4, (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("org.test.proxy.dynamicproxy.IPerson").getMethod("findLove");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("org.test.proxy.dynamicproxy.IPerson").getMethod("baby");
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
,实现了IPerson
接口,说明JDK
代理为我们生成了一个被代理对象的子类。
如果自己手动实现动态代理这个过程,要怎么写呢?需要如下步骤:
1.动态生成.java文件,可以通过用StringBuilder的方式重组一个字符串java文件
2.java文件输出到磁盘,保存为$Proxy0.java文件
3.把$Proxy0.java文件翻译成$Proxy0.class文件
4.把生成的class文件加载到JVM中
5.返回这个代理对象
6. Cglib动态代理分析
同样的方法,我们debug看Cglib生成的代理对象是什么样的。
7. Spring中的代理选择原则
当Bean有实现接口的时候,Spring就会使用JDK的动态代理。
当Bean没有实现接口时,Spring选择Cglib代理
Spring可以通过配置强制使用CGlib,只需要在Spring的配置文件中加入以下代码:
<aop:aspectj-autoproxy proxy-target-class=“true” />
8. 代理模式总结
在工作中,基本就这两种代理模式,作为一个架构师,必须要掌握这两中代理模式的实现方式。即使字节码重组过程不清楚,但整体步骤需要了解。实际开发中,用到代理模式,也不需要我们自己去实现重组过程,我们也只是站在巨人的肩膀上,选择JDK
或者是Cglib
模式,实现我们自己的Proxy
类就行了,增强调用方法的前后处理过程。
感谢您阅读本文,如果您觉得文章写的对您有用的话,请您点击上面的“关注”,点个赞,这样您就可以持续收到《JAVA架构师之路》的最新文章了。文章内容属于自己的一点点心得,难免有不对的地方,欢迎在下方评论区探讨,你们的关注是我创作优质文章的动力。