话说小强作为一名资深的单身狗,终于也迎来了自己的春天,在他的穷追不舍下,同事小丽终于同意给他一次机会,给他一个月的考察期,如果在这一个月内表现良好,就正式和他交往,否则就不要再纠缠她。
别看小强母胎单身到现在,对女生的喜好可是没少研究,也知道追女生要送礼物,女生都喜欢化妆品,于是决定先送小丽一支口红,小丽一高兴或许就答应做他女盆友了呢,可是辗转反侧了一晚上,也没有挑到一支合适的,最后在同事小红那里得知,小丽一直喜欢韩国A公司的口红,可是这个牌子的口红在国内很难买到,这可急坏了小强。
为了心爱的美眉,赴汤蹈火也在所不辞,于是小强决定亲自前往韩国,去购买这支口红。可是看到来回的机票价格,不免又有些不舍,平时短裤都不舍得换,这次真的要大出血。但是一想到女神小丽收到口红后开心的扑到他怀里,又露出了姨妈般的笑容。最终小强决定前往韩国购买口红
口红工厂接口:
public interface KouHongFactory {
public void saleKouHong(String color);
}
生产口红的A工厂:
public class AaFactory implements KouHongFactory{
@Override
public void saleKouHong(String color) {
System.out.println("您购买的是"+color+"色号的口红");
}
}
小强前往A工厂的柜台购买:
public class XiaoQiang {
public static void main(String[] args) {
//A公司生产一种口红,是女神喜欢的
KouHongFactory factory = new AaFactory();
//小强乘坐飞机,历经千辛万苦来到工厂
//购买死亡芭比粉颜色的口红
factory.saleKouHong("死亡芭比粉");
}
}
上面小强前往韩国购买口红,由于不懂韩语,在韩国兜了很大的圈子才找到这款口红的专卖店,真是吃尽了苦头。另外来回的机票又是大几千,还占用了美好的周末。而且当回来之后小强就直接把口红送给了小丽,由于没有精美的包装,少了一些惊喜和仪式感,并且最重要的是色号并不是小丽喜欢的,小丽并没有表现出多么的高兴,想象中的拥抱和香吻自然也一个都没有。总之这次送礼物没有达到理想的效果,费时费力又费钱。
谁让他是打不死的小强呢,一次挫折并不能击垮他,他决定还要送礼物,挑选一支适合小丽的口红色号,但自己对色号又确实不懂。正当小强郁闷地刷着朋友圈时,突然看到小学同学小美发的朋友圈,原来小美去年嫁给了一个富二代,现在经常出国游玩,顺便帮朋友代购一些化妆品。这次小美正好在韩国,发朋友圈问有没有需要代购的朋友,还配了三张性感的写真照片,看的小强直流口水,想当初小美还是小强的梦中女郎呢,一想到自己的女神小丽身材也不错,又打起了斗志,赶紧联系小美,让她代购一支A公司的口红。和小美说了上次送口红的事,被小美好一顿嘲笑,小美决定帮他选择色号,让他发一张小丽的素颜照片,根据小丽肤色选择一款适合她的口红。
新加的小美做代理,同样继承口红工厂接口
//代理对象,和真实对象继承与同一个接口
public class XiaoMei implements KouHongFactory {
//小美不生产口红,所有要包含真实的口红厂商
public KouHongFactory factory;
public XiaoMei(KouHongFactory factory){
super();
this.factory = factory;
}
@Override
public void saleKouHong(String color) {
System.out.println("您想要购买"+color+"色号的口红");
//前置增强,推荐一种色号
color = before();
factory.saleKouHong(color);
//后置增强
after();
}
public String before(){
String color = "姨妈红";
System.out.println("为您推荐的色号为"+color);
return color;
}
public void after(){
System.out.println("为您精美包装,免费邮寄,七天可退换");
}
}
小强类
package Proxy;
public class XiaoQiang {
public static void main(String[] args) {
//A公司生产一种口红,是女神喜欢的
KouHongFactory factory = new AaFactory();
//小美是代理
XiaoMei xiaoMei = new XiaoMei(factory);
//小强让小美帮忙买一支死亡芭比粉颜色的口红
xiaoMei.saleKouHong("死亡芭比粉");
}
}
运行结果:
您想要购买死亡芭比粉色号的口红
为您推荐的色号为姨妈红
您购买的是姨妈红色号的口红
为您精美包装,免费邮寄,七天可退换
从结果中可以看出,小美并没有按照小强要求购买死亡芭比粉色号的口红,而是买了一支更适合小丽肤色的姨妈红色号的口红。并且提供了精美包装和免费邮寄,还可以七天可退换的售后服务。简直是一条龙服务,从买前咨询,到售后服务。小强收到快递后,拿着精美包装的口红,约小丽出来吃饭,并在吃饭的时候将口红送给了她,这次女神终于开心的笑了,看到女神的笑容,小强连孩子的名字都想好了。
代理模式定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的访问。
目的:
小美作为代理对A公司的业务进行了增强,并没有修改A公司的代码,是一种无侵入式的增强。第二点并没有影响客户端的调用,也就是小强的购买过程所需要的参数不变。
小丽对小强热情了没几天,又开始变的冷淡了,聊天总是回复哦和嗯,想约出来吃饭也是各种理由推脱,这下小强又陷入了苦恼之中,想着要不要再送一些礼物给她,既然口红已经送过了,就送一盒粉底和一套水乳吧,女神一定会喜欢。
于是他又联系了小美,这次小美正好在日本度假,过两天就回国,他把自己想买粉底和水乳的想法告诉了小美,可是小美目前却并没有代购这些产品,但是在小强的苦苦哀求下,说什么下辈子的幸福就全靠小美了,还望成全。本着宁拆十座庙,不毁一门婚的原则,小美就答应帮他代购粉底和水乳了。
粉底接口:
public interface FenDiFactory {
public void saleFenDi(String color);
}
B公司生产粉底并销售:
public class BbFactory implements FenDiFactory{
@Override
public void saleFenDi(String color) {
System.out.println("您购买的是"+color+"颜色的粉底");
}
}
这个时候小美该如何处理呢?继续加实现的接口?像下面这样,那如果还要代理水乳呢?接着实现接口吗,显然不合理,这违反了开闭原则(对扩展开放,对修改关闭)
public class XiaoMei implements KouHongFactory,FenDiFactory{
}
这时候小美已经开始忙不过来了,一边需要赶往韩国帮别人代购着口红,又要去日本帮小强代购粉底,于是她打算招几名员工,帮她跑腿,有的去韩国代购口红,有的去日本代购粉底,别忘了她老公可是富二代啊,有的是钱。说干就干,于是小美成立了小美代购公司,目前可以代购口红,粉底,水乳等多种化妆品。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class XiaoMeiCompany implements InvocationHandler {
//被代理的对象,为了通用使用Object类型,可以代理各种公司
private Object factory;
public Object getFactory(){
return factory;
}
public void setFactory(Object factory){
this.factory = factory;
}
//通过Proxy获取动态代理的对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(factory.getClass().getClassLoader(),factory.getClass().getInterfaces(),this);
}
//通过动态代理对象对方法进行增强
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
before();
//为了提高通用性,使用反射,因为可能需要增强很多方法,method为被增强的方法
Object ret = method.invoke(factory,objects);
after();
return ret;
}
public void before(){
System.out.println("您好,感谢咨询,请问有什么可以帮助到您的");
}
public void after(){
System.out.println("为您精美包装,免费邮寄,七天可退换");
}
}
这里使用到了Proxy类和InvocationHandler接口,需要简单的介绍一下:
在java中,代理公司就相当于JVM,它可以动态生成代理对象,相当于指定一名员工为某个客户服务。
Proxy提供一个静态方法,负责生成动态代理类以及它的实例,静态方法为newProxyInstance,源码如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader var0, Class<?>[] var1, InvocationHandler var2) throws IllegalArgumentException {
Objects.requireNonNull(var2);
Class[] var3 = (Class[])var1.clone();
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
checkProxyAccess(Reflection.getCallerClass(), var0, var3);
}
Class var5 = getProxyClass0(var0, var3);
try {
if (var4 != null) {
checkNewProxyPermission(Reflection.getCallerClass(), var5);
}
final Constructor var6 = var5.getConstructor(constructorParams);
if (!Modifier.isPublic(var5.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
var6.setAccessible(true);
return null;
}
});
}
return var6.newInstance(var2);
...
}
该方法返回指定接口的代理实例,该代理实例将方法调用调度到指定的调用处理程序。
InvocationHandler是一个接口,每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,该方法调用将被编码并分派到invoke
其调用处理程序的方法。invoke的功能是处理代理实例上的方法调用并返回结果。
public interface InvocationHandler {
Object invoke(Object var1, Method var2, Object[] var3) throws Throwable;
}
简单来说InvocationHandler接口相当于一种公司里的规范,invoke方法里规定了公司做一些什么事情,如在这个例子中,invoke里除了有本身的业务之外(代购商品)还有前置增强(买前提供咨询)和后置增强(包邮,售后服务),这些都属于一个公司的规范,由小美制定的,所有员工都必须遵守。
newProxyInstance中的三个参数,factory.getClass().getClassLoader()表示类加载器,factory.getClass().getInterfaces()表示实现的指定接口,this代表InvocationHandler。
InvocationHandler只负责增强业务,Proxy只负责创建对象,放在这个例子中就是Proxy用来指定公司里的员工,InvocationHandler规定了该员工对客户做哪些服务(买前咨询,代购,包邮,售后等)。这里也体现了单一职责原则。
小强通过小美的公司购买口红和粉底:
public class XiaoQiang {
public static void main(String[] args) {
//A公司生产一种口红,是女神喜欢的
KouHongFactory afactory = new AaFactory();
//B公司生产粉底
FenDiFactory bfactory = new BbFactory();
//小美的代购公司
XiaoMeiCompany company = new XiaoMeiCompany();
//代理A公司的口红
company.setFactory(afactory);
//命令1号员工去帮小强买口红
KouHongFactory staff1 = (KouHongFactory) company.getProxyInstance();
staff1.saleKouHong("姨妈红");
System.out.println("---------------");
company.setFactory(bfactory);
//命令2号员工去帮小强买粉底
FenDiFactory staff2 = (FenDiFactory) company.getProxyInstance();
staff2.saleFenDi("象牙白");
}
}
运行结果如下:
您好,感谢咨询,请问有什么可以帮助到您的
您购买的是姨妈红色号的口红
为您精美包装,免费邮寄,七天可退换
---------------
您好,感谢咨询,请问有什么可以帮助到您的
您购买的是象牙白颜色的粉底
为您精美包装,免费邮寄,七天可退换
首先看一下类的完整生命周期:
如图所示,动态代理生成动态对象是在哪个阶段完成的呢,是在编译阶段,因为动态代理的对象并没有对应的java源文件,那么字节码又是怎么生成的呢?生成字节码有下面三种方式:
1.从硬盘,编译java源文件。
2.从网络,如tomacat的热加载。
3.从内存,如动态代理。
JVM如何在内存中生成字节码的呢?来分析一下源码,首先入口就是我们调用的Proxy.newProxyInstance
上面画红线部分为关键,第一处为生成Java字节码和Class对象,后面两处为生成实例对象。我们要了解的重点就是如何生成的java字节码和Class对象,点进去查看getProxyClass0方法:
重点还是划线部分,继续查看这行代码的功能,点进get方法进入源码:
接收的第一个参数为类加载器,第二个是一个需要实现的接口数组,然后遍历这些接口做一些检查,检查部分的代码省略,再下面
生成一个序号,并且以包名+$Proxy+序号
的格式生成了代理类的名字。然后生成java字节码和Class对象。生成字节码的方法generateProxyClass
从划线部分看出,确实是生成了后缀为.class
的字节码文件。
写一个工具类,利用上面的方法生成代理对象的字节码并保存下来,并通过反编译查看java源码
用来保存字节码的工具类
public class ProxyUtils {
public static void generateClassFile(Class clazz,String proxyName){
//根据类的信息和代理类的名称生成字节码
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName,
new Class[]{
clazz});
String paths = clazz.getResource(".").getPath();
System.out.println(paths);
FileOutputStream out = null;
try {
out = new FileOutputStream(paths+proxyName+".class");
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在XiaoQiang类后面加上下面两行代码,调用上面的工具类:
ProxyUtils.generateClassFile(afactory.getClass(),staff1.getClass().getSimpleName());
ProxyUtils.generateClassFile(bfactory.getClass(),staff2.getClass().getSimpleName());
最后输出了保存的字节码文件路径,进入目录查看,如下,果然有 P r o x y 1. c l a s s 和 Proxy1.class和 Proxy1.class和Proxy2.class文件。
使用jad工具进行反编译,注意反编译之前需要将 P r o x y 1. c l a s s 和 Proxy1.class和 Proxy1.class和Proxy2.class的名字改一下,不能含有$符号,否则找不到这个文件,使用如下命令进行反编译:
查看生成的Proxy0.java
import Proxy.AaFactory;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy
implements AaFactory
{
public $Proxy0(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
//省略一些代码
//核心业务代码
public final void saleKouHong(String s)
{
try
{
super.h.invoke(this, m3, new Object[] {
s
});
return;
}
catch(Error _ex) {
}
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
//省略一些代码
}
最重要的是业务方法saleKouHong
,可以看出它调用了super.h.invoke()
,super指的是父类Proxy,属性h指的是InvocationHandler,而我们自己写的XiaoMeiCompany 实现了 InvocationHandler 接口,并且重写了invoke()方法
所有最终业务代码其实调用的是XiaoMeiCompany中的Invoke()方法,在这个方法里我们对业务进行了增强(前置方法和后置方法)。这就是动态代理会增强业务的核心原理。
代理的分类:
1.静态代理
静态代理就是在编译前,代理类就存在了。这种方法很不灵活,实际编程中很少会用到,不多展开。
关于动态代理的面试题:
1.Spring事务注解实现的原理
2.为什么MyBatis可以直接使用mapper接口访问数据库
3.Mybatis打开调试模式能够打印sql语句等日志信息,是怎么实现的?实现过程中使用了什么设计 模式
代理的应用:
添加日志切面,添加事务特性,加入缓存代理,系统性能监控,方法拦截,权限控制,流量控制,负载均衡等等。
由于篇幅有限,应用部分不再讲解,更多内容请关注我的公众号【程序员修炼】。