面试装X之--代理模式

代理模式(Proxy pattern)

是指为目标对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到中介作用.
主要目的 : 
1. 保护目标对象
2. 增强目标对象

静态代理

简单来讲就是代理类拿到目标类的引用,然后在代理类中调用.在调用代理类的前后会有增强目标对象的代码.
示例 : 父亲为儿子findlove

  1. 共同接口 (Person)
public interface Person{
	public void findlove();
}
  1. 真实对象(Son)
public class Son  implements Person {
    public void findlove() {
        System.out.println("找对象....");
    }
}
  1. 代理类(Father)
public class Father implements Person{
    private Person person;

    public Father(Person person) {
        this.person = person;
    }
    public void findlove() {
        System.out.println("物色");
        this.person.findlove();
        System.out.println("父母是否同意");
    }
}
  1. 测试类
public class StaticProxyTest  {
    public static void main(String[] args) {
        //父亲只能帮儿子找对象
        // 不能帮其他人找对象,没有办法扩展
        Son son = new Son();
        Father father = new Father(son);
        father.findlove();
    }
}

从以上示例可以看出,代理类是通过手动完成代理操作的,如果目标类增加新的方法,代理类需要同步新增,违背了开闭原则.
更加明显的是,如果代理类需要代理其他不同的目标类时,1.只能在代理类中生成不同的代理方法.或者2.直接生成不同的代理类.第一种会导致代理类的膨胀,带来很多不必要判断逻辑.第二种会增加代码量,仅仅是因为不同的目标对象.




有没有一种更好的解决方案呢


动态代理

动态代理的出现就是为了解决静态代理的缺点,通过使用动态代理,我们可以在运行时,动态生成一个持有目标对象,并实现代理接口的Proxy,同时注入我们相同的扩展逻辑.,在这个过程中,哪怕你要代理的是对象,甚至是不同的方法,都能通过动态代理,来扩展功能(Proxy的创建都是自动的并且在运行期生成的).

JDK实现的方式

1.示例 : 专业的事交给专业的人去做,所以我们请了媒婆来findlove
  1. 目标对象(mimo)
public class mimo implements Person {
// 找对象要求
    public void findlove() {
        System.out.println("性格好一点");
        System.out.println("漂亮一点");
    }
}
  1. 代理对象(JDKmeipo)
public class JDKmeipo implements InvocationHandler {
    // 被代理的对象,把引用保存下来
    private mimo target;
    public Object getInstance(mimo target) throws Exception {
        this.target = target;
        Class<?> clazz = target.getClass();
        // 用来生成一个新的对象(字节码重组实现)
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.before();
        Object object = method.invoke(this.target, args);
        this.after();
        return object;
    }
    public void before() {
        System.out.println("我是媒婆:我要给你找对象,已经获取到需求");
        System.out.println("开始物色");
    }

    public void after() {
        System.out.println("如果合适的话,就准备交往");
    }
}
  1. 测试代码(jdkProxyTest)
public class jdkProxytest {
    public static void main(String[] args) {
        try {
            Person obj = (Person) new JDKmeipo().getInstance(new mimo());
            obj.findlove(); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 结果
    在这里插入图片描述
2. JDK实现代理详细分析

JDK 采用字节重组,重新生成对象代替原始的对象以达到动态代理的目的.


JDK Proxy生成代理对象的步骤:

  1. 拿到被代理对象的引用,并获取到它的所有的接口,通过反射获取
  2. JDK Proxy 类重新生成一个新的类,同时新的类要实现目标类所有实现的所有的接口
  3. 动态生成Java代码,将新加的业务逻辑方法由一定逻辑代码去调用(增强代码,在代码中体现)
  4. 编译新生成的java代码.class
  5. 重新加载到JVM中运行

以上这个过程被称为字节码重组,在classpath下,所有以$开头的.class文件,都是自动生成的,我们想要获取这个自动生成的代码(也就是JDKProxy生成的代理类),怎么办呢???

通过反编译工具可以获取代理类,然后持久化到磁盘之中

  public class jdkProxytest {
        public static void main(String[] args) {
            try {
                Person obj = (Person) new JDKmeipo().getInstance(new mimo());
                obj.findlove();
                byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
                FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
                os.write(bytes);
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

生成的代码经jad反编译:
$Proxy0.class
我们发现新生成的类继承了Proxy,并且实现了Person接口,而且重写findlove()方法.
并且在静态块中保存了目标对象所有方法,而且保存了所有的方法的引用,
在重写的方法中会调用这些引用(使用反射的方法来调用目标对象的方法),
这些代码就是JDK为我们自动生成的.

那么Proxy,java这个类是怎么样为我们生成这个代理对象的呢?
Proxy.java详细分析:
首先先分析按自己的理解创建一个代理对象需要的步骤:

  1. 动态生成源代码.java文件
  2. Java文件输出到磁盘(jdk在实现的时候应该会存在内存之中)
  3. 将生成的.java文件编译成.class文件
  4. 将编译生成的.class文件加载到JVM中去
  5. 返回字节码重组之后的代理对象

现在我们就一步一步来分析源码是怎么实现的
在这里插入图片描述
以上就是JDK的动态代理使用字节码重组得到代理对象的过程


CJLIB实现的方式

CGLIB是覆盖父类的方法,
示例 :

  1. 目标类
public class zhangsan  {
    public void findlove(){
        System.out.println("性格好就行");
    }
}
  1. 生成类
public class Cjlbmeipo implements MethodInterceptor {
    public Object getInstance(Class<?> clazz) throws Exception {
        Enhancer enhancer = new Enhancer();
        // 要把哪个类设置为即将生成的新类父类[也就是目标类]
        enhancer.setSuperclass(clazz);
        //设置生成类(也就是实现增强代码的类)
        enhancer.setCallback(this);

        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
       // 业务的增强代码
        System.out.println("我是meipo,我要给你找Object");
        System.out.println("开始物色");
        methodProxy.invokeSuper(o,objects);
        System.out.println("合适,准备");
        return o;
    }
}
  1. 测试代码
 public static void main(String[] args) {
        try {
        //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"E://cglib_proxy_class/");
            zhangsan obj = (zhangsan) new Cjlbmeipo().getInstance(zhangsan.class);
            System.out.println("-------------------------------");
           // System.out.println(obj);
            obj.findlove();
            
        } catch (Exception e) {
        }
    }
}
  1. 结果
    在这里插入图片描述
    CGLIB代理的详细分析 :
    将生成的代理类代码反编译后分析代理类代码可知(代码太长了,贴不完) :
  • 首先生成的代理类继承了目标类(当前也就是zhangsan)
    在这里插入图片描述

  • 重写了目标类的所有的方法,代理类会获得所有从父类继承来的方法,并会有MethodProxy与之对应.

  • 在这里插入图片描述- 调用顺序 :
    代理对象调用this.findlove()方法 > 调用拦截器(intercept) > methodProxy.invokeSuper(o,objects); > CGLIB$setPerson$0; > 被代理对象的findlove()方法

  • MethodProxy起到的作用:
    在这里插入图片描述
    CGLIB动态代理的执行效率之所以比JDK高是因为Cglib采用了FastClass机制:
    为代理类和目标类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int).这个index作为入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK动态代理通过反射调用高


总结

  1. JDK的动态代理实现了被代理类的接口,CGLIB是继承了被代理对象
  2. JDK和cglib都是在运行期生成字节码,JDK是直接写字节码,CGLIB使用ASM框架写Class字节码,CGLIB代理实现更复杂,生成代理类效率比JDK低
  3. JDK调用代理方法,是通过反射机制调用的,CGLIB是通过FastClass机制直接调用方法,CGLIB执行效率更高.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值