Spring代理模式

Spring代理模式

之前提到,Spring 的两个关键点就是 IoC(控制反转) 和 AOP(面向切面编程),IoC 已经研究过了,接下里就到 AOP 了。不过在学习 Spring AOP 前,必须要了解一下代理模式,因为代理模式是 AOP 的核心。

代理模式可以分为静态代理和动态代理,新建 Spring-08-Proxy 项目研究一下(因为在学习 Spring 的过程中,就不额外开个分类了)。

1. 静态代理

1.1 代理模式类图

代理模式( Proxy Pattern )是一个使用率非常高的模式,其定义如下:

Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问。)

代理模式的通用类图为

其中的三个角色为

  • Subject 抽象角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义
  • RealSubject 具体角色:也叫做被委托角色、被代理角色,是业务逻辑的具体执行者
  • Proxy 代理角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作
1.2 租房例子

这里用租房的例子说明一下:租房本来有房东和客户两个对象,但房东不想自己去出租房子(或客户找不到房东租房),就需要一个第三方房屋中介(出租代理)。

出租房子的类图为

用代码实现一下,首先有一个出租接口,是具体的业务

// 出租接口
// Subject:抽象角色,业务定义
public interface Rent {
    void rent();
}

要出租房子的房东就要实现这个接口

// 房东角色,要出租房子
// RealSubject:业务的具体执行者
public class Host implements Rent{
    public void rent() {
        System.out.println("房东的房子租出去了!");
    }
}

这时候客户已经可以找房东租房了

// 我就是客户!
public class Client {
    public static void main(String[] args){
        Host host = new Host();
        host.rent();
    }
}
// 执行结果
// 房东的房子租出去了!

不过现在房东不想自己去出租房子(或客户找不到房东租房),就需要一个引入出租代理

// 出租代理,租房找他
// Proxy:代理角色,负责对真实角色的应用
public class RentProxy implements Rent{
    // 要代理的对象
    private Host host;

    public RentProxy(Host host) {
        this.host = host;
    }

    public void rent() {
        // 帮房东出租房子
        host.rent();
    }
}

这时候客户要想租房,就可以去找代理了

// 我就是客户!
public class Client {
    public static void main(String[] args){
        // 普通代理要求不能 new 真实对象,所以这里不算
        Host host = new Host();
        // 房东把房子交给代理了
        RentProxy rentProxy = new RentProxy(host);
        // 我们找代理租房就好了
        rentProxy.rent();
    }
}
// 执行结果
// 房东的房子租出去了!

在这里代理的作用就是,房东把自己的房子交给代理( +Host ),客户租房直接去找代理就行了。

这样看来代理的作用好像不大,不过上面提到

Proxy 代理角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制 委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作

没错,代理可以在业务执行前进行预处理,或者业务完成后进行善后。

让代理来点作用,出租前先带客户看看房,客户如果要租房就签合同,代理也要收钱

// 出租代理,租房找他
// Proxy:代理角色,负责对真实角色的应用
public class RentProxy implements Rent{
    // 要代理的对象
    private Host host;

    public RentProxy(Host host) {
        this.host = host;
    }

    public void rent() {
        // 客户找代理租房,先带去看房
        seeHouse();
        // 客户觉得可以,签合同
        getContract();
        // 帮房东出租房子
        host.rent();
        // 租完房子要收钱的
        getCost();
    }

    // 预处理 before
    public void seeHouse(){
        System.out.println("中介带客户看房!");
    }

    // 应该算预处理
    public void getContract(){
        System.out.println("确认要租,签合同了!");
    }

    // 善后 after
    public void getCost(){
        System.out.println("出租完成,收米!");
    }
}

这里想到了一个问题:客户找代理租房,是客户跟代理说 “你带我去看房”,“你跟我签合同”,“你收我钱”;还是代理跟客户说 “要租房?先去看房”,“可以就签合同吧”,“把钱付一下” 呢?显然应该是后者。

这里的区别就体现在,Client 中要主动调用 seeHouse、getContract、getCost 这种预处理和善后工作吗?显然不应该。这种工作应该让代理去负责,客户找到代理租房,代理直接一条龙服务!对应的就是在代理的 rent 方法中进行预处理和善后。

这时客户去找代理租房

// 我就是客户!
public class Client {
    public static void main(String[] args){
        // 普通代理要求不能 new 真实对象,所以这里不算
        Host host = new Host();
        // 房东把房子交给代理了
        RentProxy rentProxy = new RentProxy(host);
        // 我们找代理租房,简单的租房操作其实背后有一条龙服务!
        rentProxy.rent();
    }
}
// 执行结果
/*
    中介带客户看房!
    确认要租,签合同了!
    房东的房子租出去了!
    出租完成,收米!
*/

房东只管出租他的房子,客户只需要找代理租房,烦人的事情都让代理干完了,减轻了客户和房东的负担,这就是代理模式。

1.3 用户业务例子

这里再用一个之前项目中的 UserService 例子加深对代理模式的理解。

首先有一个 UserService 接口,它是业务层的接口,对应 Dao 层的对数据库的操作

// Subject 抽象角色
public interface UserService {
    // 对应 Dao 层的增删改查!
    public void add();
    public void delete();
    public void update();
    public void query();
}

然后是接口的具体实现类,之前在实现类中进行了对 Dao 层的调用,这里简化一下(亿下)

// RealSubject 真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加用户!");
    }

    public void delete() {
        System.out.println("删除用户!");
    }

    public void update() {
        System.out.println("修改用户!");
    }

    public void query() {
        System.out.println("查询用户!");
    }
}

好了,现在的要求是,要在执行某个操作时输出日志信息,那要怎么办呢?

如果在 UserServiceImpl 类上修改,就是

// RealSubject 真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("[Debug]:进行了Add操作");
        System.out.println("增加用户!");
    }
    // 同上
    ...
}

这样好像完成了要求。但此时的 UserServiceImpl 类承担了不应该由它来做的事情,一个业务实现类,为什么要输出日志啊?这就增加了代码的耦合性。如果要添加的奇奇怪怪的功能更多,这个业务实现类中关键的业务实现部分就更加不起眼了。

所以这时候就需要代理了!增加 UserServiceProxy 类,输出日志这种事情,属于代理的预处理或善后工作

// Proxy 角色
public class UserServiceProxy implements UserService{
    // 要代理的对象!
    private UserService userService;

    // 不要直接 new 对象!添加 set 方法让 Spring 注入!连起来了!
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void add() {
        // 就当是 before 吧!
        printLog("[Debug]:进行了Add操作");
        userService.add();
    }

    public void delete() {
        printLog("[Debug]:进行了Delete操作");
        userService.delete();
    }

    public void update() {
        printLog("[Debug]:进行了Update操作");
        userService.update();
    }

    public void query() {
        printLog("[Debug]:进行了Query操作");
        userService.query();
    }

    public void printLog(String msg){
        System.out.println(msg);
    }
}

**让代理来输出日志,保证了 UserServiceImpl 类的职责清晰!**假设用到了增加用户的方法

// 客户端!
public class Client {
    public static void main(String[] args){
        // 这三步应该让 Spring 来干!不过不麻烦了
        UserService userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        // 增加用户
        proxy.add();
    }
}
// 执行结果
// [Debug]:进行了Add操作
// 增加用户!

可以看到通过调用代理来执行业务,成功输出了日志。而如果有其他的要求,也可以通过扩展代理类去实现!

这大概就是面向切面编程( AOP )的思想吧!

扩展:差点和装饰模式搞混了!在装饰模式中,装饰类的作用就是一个特殊的代理类。

1.4 静态代理小结

代理模式的优点

  • 职责清晰:真实角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

    在租房例子中体现在房东只管如何出租他房子,不用管其他乱七八糟的事情!

  • 高扩展性:具体角色是随时都会发生变化的,只要它实现了接口,无论它如何变化,代理类完全就可以在不做任何修改的情况下使用。

    租房例子中的房东只要实现了出租房子接口,无论他想出租公寓还是别墅,代理都能帮他出租!

  • 智能化:还没有体现出来,这就要看动态代理了。

代理模式的缺点

  • 这种代理其实是静态代理,问题就是每有一个真实角色,就需要一个对应的代理。当真实角色很多的时候,代码量就非常庞大了。这个问题就需要动态代理解决。

代理模式又可以扩展为普通代理强制代理,这里先不仔细研究了,到时候继续设计模式之禅。

2. 动态代理

什么是动态代理?

动态代理就是,在程序运行期间创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

动态代理的原理比较复杂,涉及到反射和 JVM 的知识(我都不会!),所以只能先照葫芦画瓢先用起来。

动态代理实现的方式有两种:JDK 和 CGLib,这里使用 JDK 实现。

2.1 代理类模板

要使用动态代理,就要用到 JDK 提供的 Proxy 类和 InvocationHandler 接口

  • Proxy 类:用于生成被代理接口的代理类对象!
  • InvocationHandler 接口:只有一个方法 invoke 要实现,就是这个方法去调用被代理接口的方法!

首先创建 ProxyHandler 类,这个类就是动态代理类,用它来动态地创建某个接口(实现该接口的对象)的代理类,所以要有一个 target 属性接收要被代理的接口

// 用它来动态生成代理类!
public class ProxyHandler {
    // 被代理的接口
    private Object target;

	// 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }
}

然后要实现调用处理器 InvocationHandler 接口,让这个类具有调用被代理接口的方法的能力(通过反射)

// 用它来动态生成代理类!
public class ProxyHandler implements InvocationHandler {
    // 被代理的接口
    private Object target;

    // 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }

    // 处理代理实例,调用被代理对象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在这里调用了被代理对象的方法!
        // 就是这里进行 面向切面编程!
        Object result = method.invoke(target, args);
        return result;
    }
}

最后要通过 Proxy 类,获取被代理接口的代理类对象!

// 用它来动态生成代理类!
public class ProxyHandler implements InvocationHandler {
    // 被代理的接口
    private Object target;

    // 用注入的方式!
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成代理类
    public Object getProxy(){
        // 用代理类,创建代理实例
        // 参数为 类加载器ClassLoader loader
        //      被代理的接口 Class<?>[] interfaces
        //      调用处理器对象 InvocationHandler h
        // 这个类就实现了调用处理器接口,所以传自己!
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }

    // 处理代理实例,调用被代理对象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在这里调用了被代理对象的方法!
        // 就是这里进行 面向切面编程!
        Object result = method.invoke(target, args);
        return result;
    }
}

这就是一个动态代理类的模板了,下面来使用一下。

2.2 租房动态代理

把租房例子用动态代理改写一下!

首先,租房接口没有变,房东也还是那个房东

public interface Rent {
    void rent();
}
public class Host implements Rent{
    public void rent() {
        System.out.println("房东的房子租出去了!");
    }
}

现在,没有手动写的代理类 RentProxy 了,要通过动态代理去获取

public class Client {
    @Test
    public void rentTest(){
        // 真实角色
        Rent host = new Host();
        // 代理角色,不存在!怎么办?找动态代理类要!
        ProxyHandler ph = new ProxyHandler();
        // 传入要被代理的对象
        ph.setTarget(host);
        // 动态生成对应的代理类!
        Rent proxy = (Rent) ph.getProxy();
        // 使用被代理对象的方法
        proxy.rent();
    }
}
// 执行结果
// 房东的房子租出去了!

我们并没有去写租房的代理类 RentProxy,也成功实现了代理!

不过这里没有扩展的功能,要想进行预处理和善后工作,就要找到调用了被代理对象方法的方法——invoke方法,在调用前后增加新操作

// 用它来动态生成代理类!
public class ProxyHandler implements InvocationHandler {
	...

    // 处理代理实例,调用被代理对象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在这里调用了被代理对象的方法!
        seeHouse();
        getContract();
        Object result = method.invoke(target, args);
        getCost();
        return result;
    }

    // 预处理 before
    public void seeHouse(){
        System.out.println("中介带客户看房!");
    }

    // 应该算预处理
    public void getContract(){
        System.out.println("确认要租,签合同了!");
    }

    // 善后 after
    public void getCost(){
        System.out.println("出租完成,收米!");
    }
}

这时候客户再去找代理租房,结果就和之前一样了

// Client 同上
// 执行结果
/*
    中介带客户看房!
    确认要租,签合同了!
    房东的房子租出去了!
    出租完成,收米!
*/
2.3 用户业务动态代理

再把用户业务的例子用动态代理改写一下,要求还是加个日志!

UserService 接口和 UserServiceImpl 实现类不变,这里省略。

在动态代理类中增加扩展的日志方法,并且在 invoke 方法中使用

// 用它来动态生成代理类!
public class ProxyHandler implements InvocationHandler {
	...
        
    // 处理代理实例,调用被代理对象的方法!
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在这里调用了被代理对象的方法!
        // 通过反射获取调用的方法的名字!
        printLog(method.getName());
        Object result = method.invoke(target, args);
        return result;
    }

    // 扩展功能,输出日志
    public void printLog(String msg){
        System.out.println("[Debug]:进行了"+msg+"操作");
    }
}

在客户端中通过动态代理调用 add 方法

public class Client {
    @Test
    public void userServiceTest(){
        // 真实角色
        UserService userService = new UserServiceImpl();
        // 找动态代理类
        ProxyHandler ph = new ProxyHandler();
        // 传入要被代理的对象
        ph.setTarget(userService);
        // 动态生成对应的代理类!
        UserService proxy = (UserService) ph.getProxy();
        // 使用被代理对象的方法
        proxy.add();
        proxy.delete();
    }
}
// 执行结果
/*
    [Debug]:进行了add操作
    增加用户!
    [Debug]:进行了delete操作
    删除用户!
*/

没有写 UserServiceProxy 类,也进行了代理操作!在 invoke 方法中,还通过 method 的 getName 方法获取了外部调用方法的名字( 原理还是反射😥),更加简洁了!

2.4 动态代理小结

动态代理的入口是 Proxy 类,通过其中的 newProxyInstance 方法获取被代理接口的代理类,参数为

  • ClassLoader loader:用哪个类加载器去加载代理对象(),通过 Class 对象中的 getClassLoader 方法获取
  • Class<?>[] interfaces:被代理的接口,通过 Class 对象的 getInterfaces 方法获取,可以获取到 Class 对象实现的所有接口
  • InvocationHandler h:调用处理器对象,调用被代理接口中的方法时,会通过其中的 invoke 方法去执行;修改 invoke 方法就相当于修改了代理类

动态代理的桥梁是 InvocationHandler 接口,通过其中的 invoke 方法调用被代理接口中的方法,参数为

  • Object proxy:具体的代理类对象,其中有被代理接口的方法!
  • Method method:被调用的方法
  • Object[] args:被调用方法的参数

动态代理通过 Proxy 类创建具体的代理类,代理类又通过 InvocationHandler 接口中的 invoke 方法完成对被代理接口中的方法的调用。

3. 总结

代理模式:由最简单的静态代理,可以扩展为普通代理和强制代理(这里没深入),然后是最强大的动态代理!

静态代理好是好,但要写代理类太麻烦!所以出现了动态代理,动态代理的原理有点深奥,类加载器 Java 虚拟机啥啥的,后面再研究吧!

不过现在还不知道动态代理除了添加日志外还有什么应用场景···就像上面的租房和用户业务,不还得对应不同的动态代理类的操作嘛?

麻了,后面再说吧😥!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值