设计模式之代理模式

1. 引言

我们先通过现有业务层存在的问题来引出代理

定义业务接口

public interface UserService {
    void save(String name);
    void delete(String id);
    void update();
    String findAll(String name);
    String findOne(String id);
}

定义业务接口的实现类

// 原始业务逻辑对象
public class UserServiceImpl implements UserService{
    // 开启事务   处理业务   调用dao
    @Override
    public void save(String name) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void delete(String id) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public String findAll(String name) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return name;
    }

    @Override
    public String findOne(String id) {
        try {
            System.out.println("开启事务");
            System.out.println("处理事务逻辑,调用DAO~~~");
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return id;
    }
}

我们发现业务接口的实现类中的每个方法都有着开启事务、提交事务、回滚事务,也就是存在着代码冗余,那么为了解决这个问题,使业务逻辑对象能够更加专注的处理业务逻辑,我们就需要使用代理这种设计模式。

2. 代理

什么是代理

  • 代理指的是java中的一些设计模式

为什么需要代理

  • 很多时候除了当前类能够提供的功能外,我们还需要补充一些额外功能

代理的作用

  • 代理对象可以在目标对象和客户对象之间起到中介的作用,从而为目标对象增添额外的功能

在这里插入图片描述

2.1 静态代理

目标类|对象(target):被代理类称之为目标类|或者被代理的对象的称之为目标对象

开发代理的原则: 代理类和目标类功能一致且实现相同的接口,同时代理类中依赖于目标类对象(调用目标类对象的方法)

开发静态代理类

//静态代理类
//开发原则:代理类和目标类实现相同接口,依赖于真正的目标类
public class UserServiceStaticProxy implements UserService{

    // 依赖原始业务逻辑对象 // Target:目标对象|被代理对象称之为目标对象 原始业务逻辑对象
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void save(String name) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.save(name);
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void delete(String id) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.delete(id);
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            userService.update();
            System.out.println("提交事务");
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
    }

    @Override
    public String findAll(String name) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            String all = userService.findAll(name);
            System.out.println("提交事务");
            return all;
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String findOne(String id) {
        try {
            System.out.println("开启事务");
            // 调用原始业务逻辑对象的方法
            String one = userService.findOne(id);
            System.out.println("提交事务");
            return one;
        } catch (Exception e) {
            System.out.println("回滚事务");
            e.printStackTrace();
        }
        return null;
    }
}

更改目标实现类

// 原始业务逻辑对象
public class UserServiceImpl implements UserService{
    // 开启事务   处理业务   调用dao
    @Override
    public void save(String name) {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public void delete(String id) {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public void update() {
        System.out.println("处理事务逻辑,调用DAO~~~");
    }

    @Override
    public String findAll(String name) {
        System.out.println("处理事务逻辑,调用DAO~~~");
        return name;
    }

    @Override
    public String findOne(String id) {
        System.out.println("处理事务逻辑,调用DAO~~~");
        return id;
    }
}

静态文件的编写

<!--管理Service-->
<bean class="staticproxy.UserServiceImpl" id="userService"></bean>

<!--管理Service中proxy-->
<bean class="staticproxy.UserServiceStaticProxy" id="userServiceStaticProxy">
    <!--依赖于真正的业务逻辑对象-->
    <property name="userService" ref="userService"/>
</bean>

调用代理方法

ApplicationContext context = new ClassPathXmlApplicationContext("staticproxy/spring.xml");

UserServiceStaticProxy userServiceStaticProxy = (UserServiceStaticProxy) 
    
context.getBean("userServiceStaticProxy");

userServiceStaticProxy.findAll("小陈");

总结

  • 代理对象原始业务逻辑对象实现相同的接口代理类和真正的业务逻辑对象实现的目标其实是一致的代理可以在保证原始业务逻辑不变的情况下去额外增加一些操作,而为了达到相同的目标,代理对象一般都要调用原始业务逻辑对象的方法,因此可以在代理类中声明一个类型为原始业务逻辑对象成员变量,使用SET方式注入,在代理类实现接口时调用原始业务逻辑对象的方法,并增加一些额外的操作代理对象依赖原始业务逻辑对象)。

  • 一旦我们开发了静态代理类,之后我们就不能使用业务逻辑对象去调用方法了,而要使用代理对象去调用

  • 使用代理的设计模式可以让原始业务逻辑对象更加专注于处理事务逻辑

# 补充

新的问题:往往在开发我们书写的不仅仅是一个业务层,两个业务层,而我们的业务层会有很多,如果为每一个业务层开发一个静态代理类,不仅没有减轻工作量,甚至让我们的工作量多了一倍不止怎么解决以上这个问题呢?

解决方案: 为业务层在运行过程中动态创建代理类,通过动态代理类去解决我们现有业务层中业务代码冗余的问题 。

2.2 动态代理

我们知道静态代理的开发需要为每一个类都开发一个静态代理类,如果有很多个类,却仍然使用静态代理开发代理类,这样是非常麻烦的。这时候就需要用到动态代理类了,它是在程序运行的过程中通过代码的方式去创建代理对象的,非常方便。

什么是动态代理呢

动态代理就是在程序运行的过程中动态的通过代码底层的方式去创建一些代理类,这些代理对象是通过代码通过jvm底层执行我们的代码去帮我们创建出来的代理类。我们可以通过动态代理对象去解决现有业务层的代码冗余问题或者可以通过动态代理对象执行一些附加的操作。由于动态代理是在程序运行过程中动态生成的,它不需要我们为每一个业务层开发一个动态代理,只需要我们写一段固定的代码,日后它就不断地通过这个固定的代码在程序运行过程中生成动态代理对象就可以了。

下面我们通过一段代码来学习一下动态代理:

  • 首先是我们需要用到的UserService接口

    public interface UserService {
        void save(String name);
        void delete(String id);
        void update();
        String findAll(String name);
        String findOne(String id);
    }
    
  • 之后是我们的原始逻辑对象

    // 原始业务逻辑对象
    public class UserServiceImpl implements UserService{
        // 开启事务   处理业务   调用dao
        @Override
        public void save(String name) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public void delete(String id) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public void update() {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
        }
    
        @Override
        public String findAll(String name) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
            return name;
        }
    
        @Override
        public String findOne(String id) {
            try {
                System.out.println("开启事务");
                System.out.println("处理事务逻辑,调用DAO~~~");
                System.out.println("提交事务");
            } catch (Exception e) {
                System.out.println("回滚事务");
                e.printStackTrace();
            }
            return id;
        }
    }
    
  • 上面的原始逻辑对象存在着冗余问题,下面我们通过动态代理来解决一下这个问题。


    首先我们需要知道一个类,叫做Proxy,它有一个静态方法newProxyInstance是用来生成代理对象的,这个newProxyInstance有三个参数:

    • 参数一ClassLoader类型第一个参数是类加载器,我们知道代理类和原始逻辑对象要有相同的接口,对于动态代理来说,因为读接口的信息,因为底层是要操作字节码,而ClassLoader就是用来读.class文件的,读类信息的。
    • 参数二Class[]类型这个Class[]数组需要传原始逻辑对象实现的所有接口,因为代理对象要和原始逻辑对象实现相同的接口,传入这个Class[]数组是为了知道要实现的所有接口是什么。
    • 参数三InvocationHandler接口类型(在传参的时候直接new就可以),这个接口我们需要实现它的invoke()方法,invoke()方法里面用来添加额外功能并且调用原始逻辑对象的方法

    newProxyInstance方法返回类型是一个Object,如果我们知道返回的是原始逻辑的代理类,我们可以使用强制类型转换转换为原始逻辑对象实现的接口类型。


    其次,InvocationHandler接口的invoke()方法也有三个参数

    • 参数1:是一个Object类型的对象,代表当前代理对象。
    • 参数2:传入的是一个Method对象,之后在InvocationHandler接口的invoke()方法内部用Method对象的反射调用原始逻辑对象的对应方法。(Method方法也有一个invoke方法,它的invoke方法有两个参数第一个参数传入原始逻辑对象的类对象(传参的时候可以直接new),因为我们要知道我们现在所创建的动态代理对象是为哪个原始逻辑类创建的,第二个参数代表原始逻辑对象的这个方法需要传入的参数)。
    • 参数3Method需要传入的参数

    下面是我们使用动态代理创建代理的代码:

    
    public class TestDynamicProxy {
        public static void main(String[] args) {
    
            // 使用动态代理对象:指的是在程序运行过程中动态通过代码的方式为指定的类生成动态代理对象
    
    
            // Proxy 用来生成动态代理的类
    
            // 参数1:classLoader 类加载器
            // 因为要读接口的信息,因为底层是要操作字节码,ClassLoader就是用来读.class文件的,读类信息的
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // 参数2:Class[] 目标对象的接口类型的数组
            // 要基于哪个目标类的(所有)接口去生成代理对象
            // 目标类可能实现多个接口,所以这里用Class[]数组
            Class[] classes = {UserService.class};
            // 参数3:InvocationHandler接口类型 有一个invoke()方法 用来书写额外功能 附加操作
            // 返回值:创建好的动态代理对象
            UserService userServiceDynamicProxy = (UserService) Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
                @Override
                // 通过动态代理对象调用自己里面代理方法时会优先执行InvocationHandler类中的invoke方法
                // 参数 1:当前创建好的代理对象
                // 参数 2:当前代理对象执行的方法对象
                // 参数 3:当前代理对象执行方法的参数
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 当前执行的方法
                    System.out.println("当前执行的方法:" + method.getName());
                    // 当亲执行的方法的参数
                    System.out.println("当前执行的方法的参数:" + args[0]);
                    try {
                        System.out.println("开启事务");
                        // 调用目标类中业务方法 通过反射机制 调用目标类中的当前方法
                        Object invoke = method.invoke(new UserServiceImpl(), args);
                        System.out.println("提交事务");
                        return invoke;
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("回滚事务");
                    }
                    return null;
                }
            });
    
            String result = userServiceDynamicProxy.findAll("小陈");
        }
    }
    
    

    在这里插入图片描述

    之后调用动态代理对象的findAll方法的执行流程是这样的:

    执行InvocationHandler类的invoke方法内部是增加一些额外操作之后通过method对象的方法去调用原始逻辑对象的方法


    通过使用动态代理,关于事务的开启、关闭、回滚直接在动态代理中就可以操作了,使原始逻辑对象可以更加专注于处理事务的逻辑。

  • 补充

    mybatis中的获取DAO对象的本质就是采用动态代理的方法,之后调用DAO对象的方法时就是让method的namemybatis中标签的id进行对照,这也是mybatis中接口不能重载的原因,如果重载不知道调用的是哪个方法。

代理模式是一种结构型设计模式,它提供一个代理对象来代表另一个对象。在代理模式中,有一个被称为实际对象(Subject)和一个被称为代理对象(Proxy)的中介,代理对象持有实际对象的引用,并且可以控制对实际对象的访问。代理模式的主要目的是在不修改原始对象的情况下,为原始对象添加额外的逻辑处理。 代理模式分为多种型,如远程代理、虚拟代理、保护代理等,它们各自有不同的应用场景: - 远程代理:为远程对象提供一个本地代表。 - 虚拟代理:根据需要创建开销大的对象,通过虚拟代理控制访问这些对象的过程。 - 保护代理:控制对原始对象的访问权限,例如进行权限检查。 代理模式的优点包括: 1. 能够控制对真实对象的访问,并在访问前后添加额外的逻辑。 2. 可以通过代理对象实现延迟加载,即在实际需要时才创建真实对象。 3. 增强了对真实对象的封装,并且可以避免对真实对象的重复引用。 在C#中实现代理模式通常涉及以下步骤: 1. 定义一个接口或抽象,声明真实对象和代理对象需要实现的方法。 2. 实现真实对象的,按照接口或抽象的要求实现具体方法。 3. 实现代理,它同样实现接口或抽象,并在方法中持有真实对象的引用,通过调用真实对象的方法来执行所需的操作,同时可以添加额外的逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值