代理模式

为什么要学习代理模式?因为这就是Spring AOP的底层机制!【Spring AOP 和 Spring MVC】

1、什么是代理模式

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

通俗的来讲代理模式就是我们生活中常见的中介

代理模式的分类

  • 静态代理
  • 动态代理

在学习AOP之前 , 我们要先了解代理模式

image-20210121135942509

真实案例:租房

image-20210121140823935

2、静态代理

静态代理角色分析

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实的角色,代理真实的角色后,我们一般会做一些附属操作
  • 客户:访问代理角色的人

静态代理具体实现步骤

  1. 抽象的接口

    // 租房的接口
    public interface Rent {
        // 房屋出租的方法
        void rent();
    }
    
  2. 真实对象

    // 房东是真实的对象
    public class Landlord implements Rent {
    
        @Override
        public void rent() {
            // 房东需要出租房子
            System.out.println("房东要出租房子");
        }
    }
    
  3. 代理对象

    // 房屋中介就是代理对象
    public class Proxy implements Rent {
        // 中介可以找到房东
        private Landlord landlord;
    
        public Proxy() {
        }
    
        public Proxy(Landlord landlord) {
            this.landlord = landlord;
        }
    
        // 代理对象提供出租房屋的方法
        @Override
        public void rent() {
            // 在出租房屋时,添加一些自己的方法
            this.seeHouse();
            // 代理调用房东的出租房屋方法
            landlord.rent();
            this.fare();
        }
    
        //看房
        private void seeHouse(){
            System.out.println("带房客看房");
        }
    
        //收中介费
        private void fare(){
            System.out.println("收中介费");
        }
    }
    
  4. 客户端

    public class Client {
        public static void main(String[] args) {
            // 房东要出租房屋
            Landlord landlord = new Landlord();
            // 房屋中介帮房东出租房屋
            Proxy proxy = new Proxy(landlord);
            // 客户找到房屋中介租房
            proxy.rent();
            // 整个过程,客户不用接触房东,而是房屋中介找到房东
        }
    }
    

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式。程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。

静态代理模式的优点

  • 可以使得我们的真实角色更加纯粹!不再去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时变得更加集中和方便!

缺点

  • 一个真实角色,就需要一个代理角色;代码量会翻倍
  • 类多了,多了代理类,工作量变大了,开发效率降低!

3、静态代理再理解

使用代理,实现我们平时写的增删改查。

  1. 编写的一个抽象角色UserService

    // 抽象角色,增删改查
    public interface UserService {
        void insert();
        void delete();
        void update();
        void query();
    }
    
  2. 编写一个真实的对象UserServiceImpl实现UserService

    // 真实的对象,实现了增删改查方法
    public class UserServiceImpl implements UserService {
        @Override
        public void insert() {
            System.out.println("新增一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("更新一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询一个用户");
        }
    }
    
  3. 添加一个需求,为每个方法添加一个日志功能,如何实现?

    • 思路1:为每个方法手动添加一个日志方法【太麻烦,也不符合面向对象的设计原则】
    • 思路2:添加一个代理类,在原方法不改变的情况下,帮助我们添加日志!
  4. 编写一个UserServiceProxy代理类

    // 代理对象,在这里面增加日志的实现
    public class UserServiceProxy implements UserService {
        private UserService userService;
    
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public void insert() {
            userService.insert();
            this.log("新增");
        }
    
        @Override
        public void delete() {
            userService.delete();
            this.log("删除");
        }
    
        @Override
        public void update() {
            userService.update();
            this.log("更新");
        }
    
        @Override
        public void query() {
            userService.query();
            this.log("查询");
        }
    
        // 日志功能的额外方法
        private void log(String msg) {
            System.out.println("[Debug] 这是" + msg + "的日志");
        }
    }
    
  5. 测试结果

    public class Client {
        public static void main(String[] args) {
            // 真实的对象
            UserService service = new UserServiceImpl();
            // 代理对象
            UserServiceProxy proxy = new UserServiceProxy();
            proxy.setUserService(service);
    
            // 通过代理对象实现了日志功能
            proxy.insert();
            proxy.delete();
            proxy.update();
            proxy.query();
        }
    }
    

通过上面的例子,我们已经基本了解了静态代理模式,就是:

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

AOP:纵向开发,横向开发

image-20210121151619785

4、动态代理

了解完静态代理后,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

动态代理的概念

  • 动态代理的角色和静态代理的一样
  • 动态代理的代理类是动态生成的,而静态代理的代理类是我们提前写好的。
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
    • 基于接口的动态代理----JDK动态代理
    • 基于的动态代理--cglib
    • 现在用的比较多的是java字节码javasist来生成动态代理,可以百度一下javasist
    • 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!

JDK的动态代理需要了解两个类

核心:InvocationHandlerProxy, 可以打开JDK帮助文档看看

【InvocationHandler:调用处理程序】

image-20210121155114930

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
// proxy - 调用该方法的代理实例
// method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
// args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

【Proxy : 代理】

image-20210121155403929

image-20210121155436050

image-20210121155539944

// 生成代理类
public Object getProxy() {
    return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}

具体代码实现:房屋出租

  1. 抽象的接口

    public interface Rent {
        void rent();
    }
    
  2. 真实的角色

    public class Host implements Rent {
        @Override
        public void rent() {
            System.out.println("出租房屋");
        }
    }
    
  3. 代理的角色

    public class ProxyInvocationHandler implements InvocationHandler {
        // 被代理的接口
        private Object target;
    
        public void setRent(Object target) {
            this.target = target;
        }
    
        // proxy: 代理类。
        // method: 代理类的调用处理程序的方法对象。
        // 处理代理实例上的方法调用并返回结果
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 核心:本质利用反射实现!
            Object invoke = method.invoke(target, args);
            return invoke;
        }
        // 生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    }
    
  4. 客户

    public class Client {
        public static void main(String[] args) {
            // 真实的角色
            Rent rent = new Host();
            // 代理实例的调用处理程序
            ProxyInvocationHandler handler = new ProxyInvocationHandler();
            // 将真实角色放置进去!
            handler.setRent(rent);
            // 动态生成对应的代理类!
            Rent proxy = (Rent) handler.getProxy();
            proxy.rent();
        }
    }
    

5、动态代理再理解

使用动态代理的方式实现增删改查

因为我们自己编写的ProxyInvocationHandler.java是一个通用的代理类

我们使用它实现代理之前的UserService就非常简单!

  1. 抽象的对象

    // 抽象角色,增删改查
    public interface UserService {
        void insert();
        void delete();
        void update();
        void query();
    }
    
  2. 真实的对象

    public class UserServiceImpl implements UserService {
        @Override
        public void insert() {
            System.out.println("新增一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("更新一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查询一个用户");
        }
    }
    
  3. 代理的对象

    public class UserServiceProxy implements InvocationHandler {
        private Object target;
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object invoke = method.invoke(target, args);
            // 执行后设置 log
            this.log(method.getName());
            return invoke;
        }
        
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
        // 添加一个log方法
        private void log(String msg) {
            System.out.println("[Debug] 这是" + msg + "的日志");
        }
    }
    
  4. 客户

    public class Client {
        public static void main(String[] args) {
            // 真实的对象
            UserService service = new UserServiceImpl();
            // 代理对象
            UserServiceProxy userServiceProxy = new UserServiceProxy();
            userServiceProxy.setTarget(service);
            UserService proxy = (UserService) userServiceProxy.getProxy();
            // 通过代理对象实现了日志功能
            proxy.insert();
            proxy.delete();
            proxy.update();
            proxy.query();
        }
    }
    

我们可以发现,代理的对象类基本不用改变,只是添加了我们自己的log方法,就可以代理房屋出租,也可以代理UserService。这样我们只需要一个代理对象,就可以代理多个对象

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有!

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时变得更加集中和方便。
  • 一个动态代理,一般代理某一类业务。
  • 一个动态代理可以代理多个类,代理的是接口!
posted @ 2021-01-22 12:15  天下御免  阅读(98)  评论(0编辑  收藏  举报