设计模式------代理模式(图文教学)


在学习Spring框架的时候,有一个重要的思想就是AOP,面向切面编程,利用AOP的思想结合Spring的一些API可以实现核心业务与辅助业务的分离,即可以在执行核心业务时,将一些辅助的业务加进来,而辅助业务(如日志,权限控制等)一般是一些公共业务,这样就实现了两者的分离,使得核心业务的代码更加纯粹,而且辅助业务也能得到复用,其实AOP底层就是通过动态代理来实现的

介绍

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图

中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

开闭原则,增加功能代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

可能你看官方的术语没有看懂,没事,我们来看看这个案例

打一个最简单的比方,我现在想要学习,那么就必须得把书包拿过来,把书掏出来,准备好纸笔,然后开始学习,等学完了我还得收拾书,把书塞回书包里,还得整理一下书包,这是一个完整的学习的过程,但是我很懒,不想动弹,只想学习,那可能就得让妈妈帮我把书包拿过来,把书打开,我只管学习就好了,学完以后,妈妈再帮我把书整理好放回去.(我这是打个什么破比方…),在这里,妈妈就代表了一个代理对象,要学习的人是我,而我只管学习,这样效率才最高,至于其他的交给代理对象(妈妈)做就好了,画一个丑陋的的图表示一下:

在这里插入图片描述

应用实例

1、Windows 里面的快捷方式。
2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。
3、买火车票不一定在火车站买,也可以去代售点。
4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
5、spring aop。

优点

1、职责清晰。
2、高扩展性。
3、智能化。

缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

实现

代理模式主要有两种实现方式:静态代理和动态代理

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

一句话,自己手写代理类就是静态代理。

先来看看我们最开始接触jdbc操作数据库的时候,业务层每一个方法,都需要

1.打开数据库连接

2.执行我们想要的操作

3.关闭数据库连接

这样就使得业务层代码不够纯粹,我的功能是查询用户数据,打开和关闭数据库连接关我毛事?我干嘛要去干这件事?这就是传统开发中存在的一个问题,现在通过代码演示一下:

/**
 * 简单业务层接口,只有一个save方法
 */
interface UserService{
    public void saveUser();
}

/**
 * 业务层实现类,实现save方法
 */
class UserServiceImpl implements UserService{
    @Override
    public void saveUser() {
        System.out.println("1:打开数据库连接");
        //真正的核心业务是保存用户信息到数据库
        System.out.println("2:保存用户信息");
        System.out.println("3:关闭数据库连接");
    }
}

我们可以看到其实这个方法的实现是有问题的,核心业务与辅助业务写在了一个方法中,不但业务冗余了不说,像开关数据库连接这样的公共操作也大量的重复,这时候就出现了代理模式的思想,我们可以使用代理模式的思想改写一下上面的代码:

package com.wang.proxy;
/**
 * 简单业务层接口,只有一个save方法
 */
interface UserService{
    public void saveUser();
}

/**
 * 代理类
 */
class UserServiceProxy implements UserService{

    private UserService userService;
    
    public UserServiceProxy(UserService userService) {
        super();
        this.userService = userService;
    }

    public void open(){
        System.out.println("1:打开数据库连接");
    }
    public void close(){
        System.out.println("3:关闭数据库连接");
    }
    @Override
    public void saveUser() {
        this.open();
        userService.saveUser();
        this.close();
    }
}

/**
 * 业务层实现类,实现save方法
 */
class UserServiceImpl implements UserService{
    @Override
    public void saveUser() {
        System.out.println("2:保存用户信息");
    }
}
/**
 * 测试类
 */
public class TestProxy {
    public static void main(String[] args) {
        UserService userService =new UserServiceProxy(new UserServiceImpl());
        userService.saveUser();
    }
}

通过测试代码打印结果,和上面没有使用代理的代码是完全一样,但是通过修改可以清晰地看到,业务层代码变得很纯很纯的,只剩下最核心的保存用户信息的代码.通过代理模式,我们可以抽取出核心业务与辅助业务,但是问题随之而来了,我这里编写的UserServiceProxy是挺不错,可是它只能服务与UserService这个接口的对象啊,如果我有一千个业务,那岂不是要编写一千个代理类,毕竟一千个人心中就有一千个哈姆雷特啊,其实这种代理模式就是静态代理,它的缺点很明显,静态代理只能服务于一种类型的对象,不利于业务的扩展,那么我们就想了,能不能设计一个代理类可以服务于所有的业务对象呢?于是,这时候,动态代理就闪亮登场了.

动态代理

jdk代理

如果要实现动态代理,那么你要编写的那个代理类就需要实现一个InvocationHandle接口.这个接口所在位置是java.lang.reflect.InvocationHandler.看到reflect我们就能知道,动态代理肯定是通过反射来实现的了,这个接口中有一个方法:

//在代理实例上处理方法调用并返回结果
Object  invoke(Object proxy, Method method, Object[] args)

invoke方法其实是反射里边的一个方法,在这个方法中有三个参数:
Ojbect proxy:表示需要代理的对象
Method method:表示要操作的方法
Object[] args:method方法所需要传入的参数(可能没有为,null.也可能有多个)

如果要想让代理设计真正可用,我们还必须有一个代理类对象产生,这有用到了另一个类:java.lang.reflect.Proxy.我的中文jdk文档对他的描述是:

Proxy提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 

在这个类下面,我们找到了这样一个方法:

public static Object newProxyInstance(ClassLoader loader,
                                       Class<?>[] interfaces,
                                       InvocationHandler h)
                                  throws IllegalArgumentException

该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序.方法中有三个参数:

参数:

loader - 定义代理类的类加载器

interfaces - 代理类要实现的接口列表

h - 指派方法调用的调用处理程序

返回:

一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

ok,有了这些知识,我们就可以来编写一个万能的动态代理类了.

class ServiceProxy implements InvocationHandler {

    private Object target=null;//保存真实业务对象
    /**
     * 返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象
     * @param obj  包含有真实业务实现的对象
     * @return   返回代理对象
     */
    public Object getProxy(Object obj) {
        this.target=obj;//保存真实业务对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result=method.invoke(target, args);//通过反射调用真实业务对象的业务方法,并且返回
        return result;
    }

}

现在来测试一下好不好使:

/**
 * 测试类
 */
public class TestProxy {

    public static void main(String[] args) {
            UserService service=(UserService) new ServiceProxy().getProxy(new UserServiceImpl());
            service.saveUser();
    }
}
//打印结果:
//2.保存用户信息

看!!!完美,你说什么?为什么没有打开数据库连接,和关闭数据库连接呢? 简单,我们直接在ServiceProxy中加入两个方法,open()和close(),一个放到method.invoke()上面,一个放到下面,就可以了,注意,我们现在编写的是一个万能的动态代理类了,没有和任何的业务层接口关联,所以接下来你就可以为所欲为了.

下面来总结一下:

动态代理和静态代理相比较,最大的好处就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,就是invocke()方法.这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

​ 动态代理只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法的返回值是被代理接口的一个实现类。

那么有没有可能我们可以不依赖接口呢?这时候就需要CGLIB实现动态代理了,这个jar包可以让我们摆脱接口的烦恼

cglib代理

上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

Cglib子类代理实现方法:
1.需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入pring-core-3.2.5.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

引入jar包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.12</version>
</dependency>

目标对象类:UserDao.java

/**
 * 目标对象,没有实现任何接口
 */
public class UserDao {

    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

Cglib代理工厂:ProxyFactory.java

/**
 * Cglib子类代理工厂
 * 对UserDao在内存中动态构建一个子类对象
 */
public class ProxyFactory implements MethodInterceptor{
    //维护目标对象
    private Object target;

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

    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();

    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始事务...");

        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);

        System.out.println("提交事务...");

        return returnValue;
    }
}

测试类:

/**
 * 测试类
 */
public class App {
    @Test
    public void test(){
        //目标对象
        UserDao target = new UserDao();
        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.save();
    }
} 

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值