【Spring学习】Aop前奏之静态代理和动态代理

前言

最近在学狂神的spring,看得好慢,花了两三个星期才看玩ioc然后刚进入aop前奏的代理模式,今天突然不想看新的,想把之前学的代理模式好好总结一下

what

  • 所以代理模式,按照狂神的说法,可以看成房东请中介租房,然后客户是跟中介打交道的,这里面代理就是这个中介。中介负责各种杂七杂八的事情,而房东只负责把房子租出去即可,比如说中介还要签合同负中介费之类的。这种设计模式,可以很好地把业务逻辑解耦,提高可扩展性
    【Spring学习】Aop前奏之静态代理和动态代理_第1张图片
  • 代理模式是spring aop的底层,springAOP必问点,分为静态和动态

静态代理

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

代理模式好处

  • 可以让真实角色的操作更加纯粹,不用去关注一些公共业务
  • 公共业务交给了代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理

代码步骤

  • 1.接口
  • 2.真实角色
  • 3.代理角色
  • 4.客户端访问代理角色

一个静态代理的例子

**接口类:**简单增删改查的功能

package com.fat.client.Demo02;

/**
 * @author fatsea
 * @date 2022/9/3 - 20:43
 */
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

真实角色:实现接口

package com.fat.client.Demo02;

/**
 * @author fatsea
 * @date 2022/9/3 - 20:43
 */
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("add user");
    }

    @Override
    public void delete() {
        System.out.println("delete user");
    }

    @Override
    public void update() {
        System.out.println("update user");
    }

    @Override
    public void query() {
        System.out.println("query user");
    }

    //1.改动原有的业务代码,在公司中是大忌
}

Hovewer,这时候产品来个需求说要,对每个接口的方法打印一下日志
但是改动原有的业务代码是公司中的大忌
我们可以把真实对象抽出来,把这些打日志的杂七杂八交给代理中介做

因此,我们可以考虑定义一个proxy,它负责照料真实角色的业务,也有自己一些特有的逻辑:

代理角色:

package com.fat.client.Demo02;

/**
 * @author fatsea
 * @date 2022/9/3 - 20:46
 */

// 代理UserService
public class UserServiceProxy implements UserService{

    private UserServiceImpl userService;

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

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("add");
        userService.add();
    }

    @Override
    public void update() {
        log("add");
        userService.add();
    }

    @Override
    public void query() {
        log("add");
        userService.add();
    }

    //日志方法
    public void log(String msg) {
        System.out.println("[Debug]使用了" + msg + "方法");
    }
}

首先的话,它要定义一个真实对象(表示它代理了谁),然后再在真实对象的方法上,加上他自己的一些杂七杂八的操作,例如这里的log操作

客户类

package com.fat.client.Demo02;

/**
 * @author fatsea
 * @date 2022/9/3 - 20:44
 */
public class Client {
    // 使用代理不改变原有代码,扩展功能
    // 业务和额外逻辑分离
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();

        proxy.setUserService(userService);
        proxy.add();
    }
}

客户的话,首先定义好它想要接除的真实对象UserServiceImpl
然后生成一个proxy负责代理这个真实对象
最后调用这个代理的add方法,一方面完成了真实对象的操作,另一方面也完成proxy的额外操作

以上就是静态代理的一个例子
它的核心就是,真实对象该干啥干啥,花里胡哨的边角功能都交给代理,实现解耦

静态代理缺点

  • 一个真实角色就会产生一个代理角色,代码量会翻倍
  • 开发效率变低
    根据上面例子的思路,一个真实角色就要来一个代理角色,那岂不是很麻烦?
    我们能不能根据真实角色,自动化生成代理角色,而不用显示写死呢?
    这就是动态代理,它要用到反射机制获取真实角色的方法,然后创建动态代理

什么是aop

【Spring学习】Aop前奏之静态代理和动态代理_第2张图片我们知道代理可以实现分工,使得耦合性变低,但是静态代理的方法会使得代理翻倍
因此如果我们又想代理,又不想类增加,就要使用反射动态加载一些类

动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的
  • 动态代理分为两大类:基于接口的动态代理、基于类的动态代理
    • 基于接口的—JDK的动态代理【我们在这里使用】
    • 基于类:cglib
    • java字节码实现:javasist

需要了解两个类:Proxy代理、InvocationHandler调用处理程序
InvocationHandler:invoke方法
proxy代理谁,method代理什么方法,args什么参数
返回结果(实例类)

动态代理实例1(租房子)

还是分之前的四步走,但这里的显式proxy将会被一个proxy生成器代替
租房接口:

package com.fat.client.Demo03;

/**
 * @author fatsea
 * @date 2022/8/31 - 21:39
 */

// 租房
public interface Rent {

    public void rent();
}

真实对象:房东

package com.fat.client.Demo03;


/**
 * @author fatsea
 * @date 2022/8/31 - 21:40
 */
// 房东
public class Host implements Rent {

    public void rent() {
        System.out.println("房东要出租房子");
    }

}

那么,关键来了,我们看看下面的
ProxyInvocationHandler:可以理解为召唤Proxy的类

package com.fat.client.Demo03;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author fatsea
 * @date 2022/9/3 - 21:10
 */
// 我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    // Proxy提供了创建动态代理类和实例的静态方法
    // 生成得到代理类
    // 1.ClassLoader
    // 2.代理类的接口
    // 3.InvocationHandler(本身就是)
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    // 处理代理实例,并返回结果
    // 执行代理真正要干的事情
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


        seeHouse(); // 中介特有的

        // 通过反射执行方法
        // 动态代理的本质就是使用反射机制实现
        Object result = method.invoke(rent, args);

        fare(); // 中介特有的

        return result;
    }

    public void seeHouse() {
        System.out.println("中介带看房子");
    }

    public void fare() {
        System.out.println("中介收中介费");
    }
}

在这个里面,还是要把需要被代理的真实对象定义好,然后需要注意的是它继承了InvocationHandler说明是一个调用处理器

getProxy:获取proxy,通过Proxy类的创建动态代理类和实例的静态方法,需要三个参数ClassLoader(就是本身,通过反射获取),代理类的接口(这里是rent的接口,通过反射获取),InvocationHandler(本身就是);很好,我们就有无中生有,生出一个proxy对象了,但是它的方法我们还没写好

invoke:负责方法的调用,里面可以插入一些中介特有的方法,例如看房子和收费;但是怎么调用原来的真实对象的方法呢,就要通过method.invoke(里面放入方法名字和参数即可),这里也是使用反射实现

客户类

package com.fat.client.Demo03;

/**
 * @author fatsea
 * @date 2022/9/3 - 21:17
 */
public class Client {
    // 没有代理类
    public static void main(String[] args) {
        // 真实角色
        Host host = new Host();

        // 代理角色:现在没有
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        // 通过调用程序处理角色,来处理我们要调用的接口对象!

        // 设置真实要代理的角色
        pih.setRent(host);

        // 通过方法动态生成代理类
        // 这里的proxy就是动态生成的代理, 我们并没有写
        Rent proxy = (Rent) pih.getProxy();

        proxy.rent();
    }
}

客户的调用链是:生成真实对象,获取一个proxy生成器,把真实对象丢进生成器里面,然后再获取一个继承真是对象接口的proxy;最后就可以调用代理的方法了(也就是加工了的真实对象的方法)

动态代理例子再抽象

把真实对象写成Object,可以写成代理生成的工具类

package com.fat.client.Demo04;

import com.fat.client.Demo03.Rent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author fatsea
 * @date 2022/9/3 - 21:10
 */
// 我们会用这个类自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口(这里不写死,变成工具类)
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    // Proxy提供了创建动态代理类和实例的静态方法
    // 生成得到代理类
    // 1.ClassLoader
    // 2.代理类的接口
    // 3.InvocationHandler(本身就是)
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // InvocationHandler处理代理实例,并返回结果
    // 执行代理真正要干的事情
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        log(method.getName());
        // 通过反射执行方法
        // 动态代理的本质就是使用反射机制实现
        Object result = method.invoke(target, args);


        return result;
    }

    public void log(String msg) {
        System.out.println("执行了" + msg + "方法");
    }

}

套路都是一样,只不过把之前实现的rent改成万金油object
这里注意打日志的话,又来了一个反射,获取方法的名字即可
框架处处体现着反射

动态代理的好处

  • 可以让真实角色的操作更加纯粹,不用去关注一些公共业务
  • 公共业务交给了代理角色,实现了业务的分工
  • 公共业务发生扩展的时候,方便集中管理
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可(Impl2也一样) 代理的是一类业务

总结

代理这种思想就是解耦
什么类该干什么分工明确,加强可扩展性,可维护性
不改变原有的代码
静态代理需要重写代理类,而动态代理可以使得同一个接口(同一类业务)共享一个代理

你可能感兴趣的:(spring,spring,学习,代理模式)