前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >理解Java里面的代理模式

理解Java里面的代理模式

作者头像
我是攻城师
发布2018-10-19 16:44:02
9840
发布2018-10-19 16:44:02
举报
文章被收录于专栏:我是攻城师我是攻城师

前言

代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看成是代理模式的缩影,或者再形象点各种浏览器都可以看成是我们上网的代理角色。

代理模式的定义

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的组成

抽象角色:通过接口或抽象类声明真实角色实现的业务方法。

代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。

真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。

代理模式的优点

(1).职责清晰 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。

(3).高扩展性

代理模式的应用场景

(1)最经典的就是Spring AOP的实现,通过代理实现了日志记录,事务,方法耗时等非核心业务本职责的功能的统一控制。

(2)在Hibernate类似相关的框架中,仅仅定义接口就可以通过代理动态实现CURD相关操作。

(3)其他各种字节码操纵的场景。

Java里面的代理

在Java里面总体上有三种代理实现:

(1)静态代理

例子如下: 定义一个接口

代码语言:javascript
复制
public interface Animal {

    public String run();

}

定义一个实现类:

代码语言:javascript
复制
public class Dog implements Animal {

    @Override
    public String run() {
     return    " 小狗跑..... ";
    }
}

定义一个代理类,同样实现该接口:

代码语言:javascript
复制
public class ProxyRole implements Animal {

    private Animal proxy;

    public ProxyRole(Animal proxy) {
        this.proxy = proxy;
    }

    @Override
    public String run() {

        System.out.println(" before execute......  ");
        String result=proxy.run();
        System.out.println(result);
        System.out.println(" after execute......  ");

        return result;
    }
}

如何使用:

代码语言:javascript
复制
public class TestMain {


    public static void main(String[] args) {

        //真实角色.
        Animal dog=new Dog();
        //代理角色.
        ProxyRole proxyRole=new ProxyRole(dog);
        //代替执行.
        proxyRole.run();
        }
}
(2)JDK动态接口代理

JDK动态接口的代理的特点是,其代理角色可以在运行时动态生成而不需要在编译的时候提前定义,正是因为这个特点,所以扩展性和灵活性更强,其原理是在运行时会动态生成一个类,该类实现了所定义的接口,然后在我们把我们还需要定义一个InvocationHandler的实现类,在这个类里面重写invoke方法,并附加自己与业务无关的逻辑,比如计数或者方法耗时,日志等等,最终通过反射调用真实角色的业务方法。

例子如下:

代码语言:javascript
复制
public class CountTimeProxyInvocation implements InvocationHandler {

    private Object target;

    public CountTimeProxyInvocation(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        System.out.println(proxy+"  "+method+" "+ args.toString());
        System.out.println("name:"+proxy.getClass().getName());
        System.out.println(method+"  "+Arrays.toString(args));//传入的参数类型
        long start=System.nanoTime();
        System.out.println("调用之前.......");
//        Object result=ms.get(method.getName()).invoke(target,args);
        Object result=method.invoke(target,args);
        long cost=System.nanoTime()-start;
        System.out.println("调用之后,"+method.getName()+" 耗时: "+cost+" ns");

        return result;
    }
}

上面这个实现了InvocationHandler的类,实现了方法耗时的统计。

代码语言:javascript
复制
private static void testMap(){
        Map map= (Map) Proxy.newProxyInstance(TestCountTimeProxy.class.getClassLoader(),
                new Class[]{Map.class},//必须接口类型,可以有多个接口
                new CountTimeProxyInvocation(new HashMap())
        );

        map.put("t",12);
        map.get("1");
        map.size();
    }

然后,我们就能感受它的强大之处,只要是有接口声明的类,都可以采用这个方法来统计耗时,比如上面的Map例子,正这里如果是List或者是Set同样适用。

(3)Cglib框架的动态类代理

JDK动态代理要求每一个类必须要有接口定义才可以实现动态代理功能,所以有时候我们可能仅仅是为了一个普通类代理,这样以来就必须绑定接口了,而cglib框架可以实现普通类的代理,对于类里面不包含final修饰的方法都可以实现代理,例子如下:

定义一个普通的类:

代码语言:javascript
复制
public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

实现代理访问:

代码语言:javascript
复制
// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法.
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象.
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

cglib并不是jdk自带的方案,它需要引入maven的依赖才能使用:

代码语言:javascript
复制
<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.8</version>
        </dependency>

动态代理原理分析

代理模式的技术核心有三点:

(1)动态字节码生成

(2)动态字节码类的装载

(3)反射调用真实角色的方法

理解了上面的内容学习代理模式就非常容易,先从JDK动态代理说起,前面说到JDK动态代理的会在运行时生成接口的实现类,并且该实现类会继承Proxy类,所以也注定了JDK动态代理只能代理接口不能代理类,因为Java不支持多继承,在使用的时候我们执行方法调用会从代理角色经过,然后由代理角色调用我们实现的Invocation的invoke方法,在invoke方法里面我们可以自定义额外的功能,最后通过反射调用真实角色方法,完成整个调用链。

而cglib代理的原理与JDK动态代理类似,不同之处在于cglib代理在运行时候,动态的生成了一个继承了真实的角色的代理类,然后实现了cglib的Factory接口,在运行的时候,同样通过代理类转发请求调用,先调用我们设置的callback里面回调的Interceptor方法,这样我们就可以在这个方法里面控制与核心业务无关的逻辑,最后通过反射调用真实角色的方法完成整个调用链。cglib代理功能更强大,其底层集成了ASM直接操作字节码的类库,并且可以控制仅仅对需要代理的方法执行代理,至于其他的方法可以忽略,这样以来可以拥有更好的性能。

总结

本文主要介绍了代理模式的应用,并结合实际的例子详细的描述了Java里面三种代理模式的原理和实现,代理模式是一项非常有用的技术,通过代理角色转发请求,可以实现非常灵活的扩展功能。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 代理模式的定义
  • 代理模式的组成
  • 代理模式的优点
  • 代理模式的应用场景
  • Java里面的代理
    • (1)静态代理
      • (2)JDK动态接口代理
        • (3)Cglib框架的动态类代理
        • 动态代理原理分析
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档