欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式:代理模式是什么,Spring AOP还和它有关系?

程序员文章站 2022-06-03 18:58:26
接着学习设计模式系列,今天讲解的是代理模式。 定义 什么是代理模式? 代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色: Subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。 RealSubject:具体主 ......

接着学习设计模式系列,今天讲解的是代理模式。

定义

什么是代理模式?

代理模式,也叫委托模式,其定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。它包含了三个角色:

subject:抽象主题角色。可以是抽象类也可以是接口,是一个最普通的业务类型定义。

realsubject:具体主题角色,也就是被代理的对象,是业务逻辑的具体执行者。

proxy:代理主题角色。负责读具体主题角色的引用,通过真实角色的业务逻辑方法来实现抽象方法,并在前后可以附加自己的操作。

用类图来表示的话大概如下:
设计模式:代理模式是什么,Spring AOP还和它有关系?

我们可以用举一个电影演员拍戏的例子,一般来说,演员最主要的工作就是演戏,其他的事可以交给他的经纪人去做,例如谈合同,安排档期等等,而负责这些场外工作的经纪人就相当于proxy,而负责核心业务的演员就是 realsubject

这就是代理模式的设计思路,除此之外,代理模式分为静态代理和动态代理,静态代理是我们自己创建一个代理类,而动态代理是程序自动帮我们生成一个代理类,可以在程序运行时再生成对象,下面分别对它们做介绍。

静态代理

静态代理在程序运行之前,代理类.class文件就已经被创建了。还是用上面演员演戏的例子,在静态代理模式中,我们要先创建一个抽象主题角色 star

public interface star {
    // 演戏
    void act();
}

接下来就是创建具体的主题角色和代理主题角色,分别实现这个接口,先创建一个具体的主题角色 actor

/**
 * 演员,也就是具体的主题角色
 *
 * @author tao
 * @since 2019/7/9 18:34
 */
public class actor implements star {
    public void act() {
        system.out.println("演员演戏~~~");
    }
}

然后就是创建代理主题角色,也就是代理类,代理类本身并不负责核心业务的执行流程,演戏这事还得明星自己来。所以在代理类中需要将真实对象引入,下面是具体的代码实现:

/**
 * 代理对象
 * @author tao
 * @since 2019/7/9 18:43
 */
public class agent implements star {
    /**
     * 接收真实的明星对象
     */
    private star star;

    /**
     * 通过构造方法传进来真实的明星对象
     *
     * @param star star
     */
    public agent(star star) {
        this.star = star;
    }

    public void act() {
        system.out.println("签合同");
        star.act();
        system.out.println("演完戏就收钱了");
    }
}

代码的逻辑还是比较清晰的,通过维护一个star对象,可以在act里调用具体主题角色的业务逻辑,并且在核心逻辑前后可以做一些辅助操作,比如签合同,收钱等,这样代理模式的角色就都分工完成了,最后用一个场景类来验证下:

public class client {
    public static void main(string[] args) {
        star actor = new actor();
        agent agent = new agent(actor);
        agent.act();
    }
}

运行的结果如下:

签合同
演员演戏~~~
演完戏就收钱了

动态代理

动态代理分为两种,分别是jdk动态代理和 cglib 动态代理,怎么又分了,代理模式分类真多,不过来都来了,就都学习一下吧。

jdk动态代理

前面说了,在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由jdk再运行时帮我们动态的来创建。

/**
 * 动态代理处理类
 *
 * @author tao
 * @since 2019/7/9 19:04
 */
public class jdkproxyhandler {

    /**
     * 用来接收真实明星对象
     */
    private object star;

    /**
     * 通过构造方法传进来真实的明星对象
     *
     * @param star star
     */
    public jdkproxyhandler(star star) {
        super();
        this.star = star;
    }

    /**
     * 给真实对象生成一个代理对象实例
     *
     * @return object
     */
    public object getproxyinstance() {
        return proxy.newproxyinstance(star.getclass().getclassloader(),
                star.getclass().getinterfaces(), (proxy, method, args) -> {

                    system.out.println("签合同");
                    // 执行具体的业务逻辑
                    object object = method.invoke(star, args);
                    system.out.println("演出完经纪人去收钱……");

                    return object;
                });
    }
}

这里说一下proxy.newproxyinstance 这个方法,该方法包含了三个参数,

  • classloader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的;
  • class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型;
  • invocationhandler:指定动态处理器,执行目标对象的方法时会触发事件处理器的方法。

写完了动态代理实现类,我们写个场景类测试下,

public class client {
    public static void main(string[] args) {
        star actor = new actor();
        // 创建动态代理对象实例
        star jdkproxy = (star) new jdkproxyhandler(actor).getproxyinstance();
        jdkproxy.act();
    }
}

执行结果正常输出:

签合同
演员演戏~~~
演出完代理去收钱……

由此可见,jdk 动态代理确实发挥了代理的功能,相对于静态代理,jdk 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但它同样有缺陷,就是动态代理的实现类需要类实现接口来完成代理的业务,也就是说它始终无法摆脱仅支持interface代理的桎梏,这是设计上的缺陷。而这时cglib 动态代理就派上用场了。

cglib 动态代理

cglib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。下面我们写一个关于cglib的动态代理类,值得说下的是,cglib所在的依赖包不是jdk本身就有的,所以我们需要额外引入,如果是用maven来管理的话,就可以直接引入如下的依赖:

<dependencies>
    <dependency>
        <groupid>cglib</groupid>
        <artifactid>cglib</artifactid>
        <version>3.2.3</version>
    </dependency>
</dependencies>

使用 cglib 需要实现 methodinterceptor 接口,并重写intercept 方法,在该方法中对原始要执行的方法前后做增强处理。该类的代理对象可以使用代码中的字节码增强器来获取。具体的代码如下:

public class cglibproxy implements methodinterceptor {
    /**
     * 维护目标对象
     */
    private object target;

    public object getproxyinstance(final object target) {
        this.target = target;
        // enhancer类是cglib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展
        enhancer enhancer = new enhancer();
        // 将被代理的对象设置成父类
        enhancer.setsuperclass(this.target.getclass());
        // 回调方法,设置拦截器
        enhancer.setcallback(this);
        // 动态创建一个代理类
        return enhancer.create();
    }

    public object intercept(object o, method method, object[] objects, methodproxy methodproxy) throws throwable {
        system.out.println("签合同");
        // 执行具体的业务逻辑
        object result = methodproxy.invoke(o, objects);
        system.out.println("演出完经纪人去收钱……");
        return result;
    }
}

场景测试类:
public class client {
    public static void main(string[] args) {
        star actor = new actor();
        // 创建动态代理对象实例
        star proxy = (star) new cglibproxy().getproxyinstance(actor);
        proxy.act();
    }
}

可以看出,测试类的逻辑和jdk动态代理差不多,其实套路都是一样的,其实技术实现不同。

总结一下cglib代理模式: cglib创建的动态代理对象比jdk创建的动态代理对象的性能更高,但是cglib创建代理对象时所花费的时间却比jdk多得多。所以对于单例的对象,因为无需频繁创建对象,用cglib合适,反之使用jdk方式要更为合适一些。同时由于cglib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

扩展知识

这里扩展一个知识点,那就是spring aop的底层实现,为什么在这里提及呢?因为spring aop的底层实现就是基于代理模式,而jdk 动态代理和 cglib 动态代理均是实现 spring aop 的基础。我们可以看下aop的部分底层源码:

public class defaultaopproxyfactory implements aopproxyfactory, serializable {

    @override
    public aopproxy createaopproxy(advisedsupport config) throws aopconfigexception {
        if (config.isoptimize() || config.isproxytargetclass() || hasnousersuppliedproxyinterfaces(config)) {
            class<?> targetclass = config.gettargetclass();
            if (targetclass == null) {
                throw new aopconfigexception("targetsource cannot determine target class: " +
                        "either an interface or a target is required for proxy creation.");
            }
            // 判断目标类是否是接口或者目标类是否proxy类型,若是则使用jdk动态代理
            if (targetclass.isinterface() || proxy.isproxyclass(targetclass)) {
                return new jdkdynamicaopproxy(config);
            }
            // 使用cglib的方式创建代理对象
            return new objenesiscglibaopproxy(config);
        }
        else {
            // 上面条件都不满足就使用jdk的提供的代理方式生成代理对象
            return new jdkdynamicaopproxy(config);
        }
    }
}

源码的判断逻辑并不难,主要是根据目标类是否是接口或者proxy类型来判断使用哪种代理模式创建代理对象,使用的代理模式正是jdk动态代理和cglib 动态代理技术。由此可见,了解代理模式还是很重要的,起码以后面试官问aop的底层实现时,我们还能吹一波呢,哈哈~~~