一个菜鸡JAVA后端的博客~

设计模式-代理模式

定义

  • Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问)
  • 代理设计模式通常有三个角色
    • Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
    • RealSubject具体角色:也叫做被委托角色、被代理角色。是业务逻辑具体的执行者。
    • Proxy代理主题角色:也叫做委托类、代理类。他负责对真实角色的应用,把所有抽象主题类的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
  • 代理模式也叫做委托模式,它是一种基本设计技巧,其他许多模式,如状态模式、策略模式、访问者模式本质上是更特殊场合采用了委托模式

优点

  • 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后的代理完成一件事务,附带的结果就是编程简洁清晰。
  • 高扩展性:具体主题角色是随时都会发生变化的,只要它实现了接口,不管如何变化。代理类完全可以在不做任何修改的情况下使用。
  • 智能化:如Mybatis自动将表单元素映射到对象上。

使用场景

  • 场景:
    • 如Spring中的AOP等

代码

通用类图

静态代理通用类图

代理模式通用类图

动态代理通用类图

动态代理通用类图

静态代理

静态代理是相对来说比较简单的代理,优点是逻辑简单

类图

静态代理类图

代码

代理接口

package com.pattern.proxy.staticproxy;

public interface Subject {
    void request();
}

具体被代理的对象(实现代理接口

package com.pattern.proxy.staticproxy;

public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("执行了被代理对象的方法");
    }
    
}

用于代理的对象(实现代理接口

package com.pattern.proxy.staticproxy;

public class Proxy implements Subject {
    // 被代理的对象
    Subject subject = null;
    // 设置默认的代理
    public Proxy(){
        this.subject = new Proxy();
    }
    public Proxy(Subject subject){
        this.subject = subject;
    }
    // 前置方法
    private void before(){
        System.out.println("执行了前置方法");
    }
    // 后置方法
    private void after(){
        System.out.println("执行了后置方法");
    }
    // 使用代理在原有的方法上增强操作
    @Override
    public void request() {
        this.before();
        this.subject.request();
        this.after();
    }
    
}

测试代码

package com.pattern;

import com.pattern.proxy.normal.Proxy;
import com.pattern.proxy.normal.RealSubject;
import com.pattern.proxy.normal.Subject;

import org.junit.Test;

public class ProxyTest {
    @Test
    public void StaticProxyTest(){
        Subject subject = new RealSubject();
        Subject proxy = new Proxy(subject);
        proxy.request();
    }
}

结果

静态代理执行结果

普通代理

普通代理要求客户端只能访问代理角色,而不能访问真实角色。他和静态代理基本相同,只是在静态代理的基础上屏蔽了对高层真实角色。

为方便理解,这里使用游戏代练的场景举例。

类图

普通代理类图

代码

代理接口

package com.pattern.proxy.normal;

public interface IGamePlayer {
    void playGame();
    void login();
    void logout();
}

具体被代理的对象(实现代理接口

package com.pattern.proxy.normal;

public class RealGamePlayer implements IGamePlayer {
    String username;
    String account;
    String password;
    // 构造方法,传入了一个ProxyGamePlayer类型的参数,用于禁止直接通过new来获取一个对象。这只是一个约束,尽量通过团队编程规范约束,而非代码约束
    public RealGamePlayer(ProxyGamePlayer proxyGamePlayer,String username, String account, String password) throws Exception{
        if(proxyGamePlayer == null){
            throw new Exception("无法创建对象");
        } else {
            this.username = username;
            this.account = account;
            this.password = password;
        }
    }
    @Override
    public void playGame() {
        System.out.println("用户:" + username + "正在打游戏");
    }

    @Override
    public void login() {
        System.out.println("帐号为:" + account + ",密码为:" + password + ",登录成功");
    }

    @Override
    public void logout() {
        System.out.println("用户:" + username + "登出");
    }
}

用于代理的对象(实现代理接口

package com.pattern.proxy.normal;

public class ProxyGamePlayer implements IGamePlayer {
    private RealGamePlayer realGamePlayer;
    public ProxyGamePlayer(String name, String account, String password){
        try {
            // 创建一个被代理的对象
            realGamePlayer = new RealGamePlayer(this, name, account, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void playGame() {
        this.realGamePlayer.playGame();
    }

    @Override
    public void login() {
        this.realGamePlayer.login();
    }

    @Override
    public void logout() {
        this.realGamePlayer.logout();

    }
}

测试代码

package com.pattern;

import com.pattern.proxy.normal.ProxyGamePlayer;
import com.pattern.proxy.normal.RealGamePlayer;
import com.pattern.proxy.staticproxy.Proxy;
import com.pattern.proxy.staticproxy.RealSubject;
import com.pattern.proxy.staticproxy.Subject;

import org.junit.Test;

public class ProxyTest {
    @Test
    public void NormalProxyTest(){
        System.out.println("--------------试图直接创建一个对象--------------");
        try {
            RealGamePlayer realGamePlayer = new RealGamePlayer(null, "张三","admin","admin");
        } catch (Exception e) {
            // 无法直接创建成功,必须使用代理对象
            System.out.println(e.getMessage());
        }
        System.out.println("--------------使用代理创建一个对象--------------");
        ProxyGamePlayer proxy = new ProxyGamePlayer("张三","admin","admin");
        proxy.login();
        proxy.playGame();
        proxy.logout();

    }
}

运行结果

普通代理运行结果

强制代理

强制代理在代理模式的大家族里比较另类,因为代理模式正常的思维是通过代理找到真实的角色,而强制代理则是必须要"强制"。必须通过真实角色查找到代理角色,否则你不能访问。简单来说就是高层模块new了一个真实角色的对象,返回的却是代理角色。

为了方便理解,使用用户售后的场景。用户买的XX牌电脑出故障了,找XX牌公司进行售后,但是实际售后的并不是商家本身,而是产品的代工厂。

类图

强制代理类图

代码

代理接口(接口

package com.pattern.proxy.force;

/**
 * 电脑售后接口,相当于代理接口
 */
public interface IComputerAfterSale {
    // 对电脑进行售后
    void afterSale();
    IComputerAfterSale getProxy();
}

被代理对象(实现代理接口

package com.pattern.proxy.force;

/**
 * XXX品牌电脑公司,相当于被代理的对象
 */
public class ComputerCompany implements IComputerAfterSale {
    // 代工厂,电脑公司的代理
    private IComputerAfterSale oem;
    // 找到自己的代理
    @Override
    public  IComputerAfterSale getProxy(){
        this.oem = new ComputerOEM(this);
        return oem;
    }
    // 检测是否是代理访问
    private boolean isProxy(){
        return (oem == null);
    }

    @Override
    public void afterSale() {
        if(this.isProxy()){
            System.out.println("售后由代工厂提供");
        } else {
            // 虽然是代工厂给你进行的售后,但是你只认牌子不认代工厂
            System.out.println("厂家完成了电脑的售后");
        }
    }
}

用于代理的对象(实现代理接口

package com.pattern.proxy.force;

/**
 * 电脑代工厂,相当于强制代理中的代理角色
 */
public class ComputerOEM implements IComputerAfterSale{
    private  IComputerAfterSale computerCompany;
    // 初始化的时候传入厂商,这样代工厂才知道你要售后的电脑是那个厂家的
    public ComputerOEM(IComputerAfterSale computerCompany){
        this.computerCompany = computerCompany;
    }
    @Override
    public void afterSale() {
        this.computerCompany.afterSale();
    }
    // 代工厂没有代理
    @Override
    public IComputerAfterSale getProxy() {
        return this;
    }
}

测试代码

package com.pattern;

import com.pattern.proxy.force.ComputerCompany;
import com.pattern.proxy.force.ComputerOEM;
import com.pattern.proxy.force.IComputerAfterSale;
import com.pattern.proxy.normal.ProxyGamePlayer;
import com.pattern.proxy.normal.RealGamePlayer;
import com.pattern.proxy.staticproxy.Proxy;
import com.pattern.proxy.staticproxy.RealSubject;
import com.pattern.proxy.staticproxy.Subject;

import org.junit.Test;

public class ProxyTest {
    @Test
    public void ForceProxyTest(){
        System.out.println("-------------直接找厂家售后(直接访问真实角色)-------------");
        // 不能访问,厂家只负责贴牌并不负责维修之类的
        ComputerCompany computerCompany = new ComputerCompany();
        computerCompany.afterSale();
        System.out.println("-------------直接找代工厂售后(直接访问代理角色)-------------");
        // 不能访问,代理对象是你自己new出来的,相当于你直接找代工厂售后,代工厂也不认你
        ComputerOEM oem = new ComputerOEM(computerCompany);
        oem.afterSale();
        System.out.println("-------------强制代理-------------");
        // 创建一个电脑公司
        ComputerCompany intel = new ComputerCompany();
        // 电脑公司联系代工厂处理你的电脑
        IComputerAfterSale proxy = intel.getProxy();
        // 代工厂进行维修
        proxy.afterSale();
    }
}

运行结果

强制代理运行结果哦

动态代理

动态代理在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象。动态代理根据被代理的接口生成所有的方法。给定一个接口,动态代理会宣称"已经实现了接口下所有的方法"。所有被代理的方法都由InvocationHandler接管实际的处理操作。

类图

动态代理类图

代码

代理接口(接口

package com.pattern.proxy.dynamic;

public interface IProxySubject {
    void doSomething();
}

具体被代理的类(实现代理接口

package com.pattern.proxy.dynamic;

public class ProxySubjectImpl implements IProxySubject{
    @Override
    public void doSomething() {
        System.out.println("执行了逻辑事务");
    }
}

Handler(实现InvocationHandler接口):具体的方法交由此类处理

package com.pattern.proxy.dynamic;

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

/**
 * 动态代理的方法交给此类接管,通过Invoke执行真正的方法
 */
public class SubjectInvocationHandler implements InvocationHandler {
    private IProxySubject proxySubject;
    public SubjectInvocationHandler(IProxySubject proxySubject){
        this.proxySubject = proxySubject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object value = null;
        this.before();
        // 执行代理对象的业务方法
        value = method.invoke(proxySubject,args);
        this.after();
        return value;
    }
    private void before(){
        System.out.println("对业务进行权限验证");
    }
    private void after(){
        System.out.println("记录日志");
    }
}

创建代理对象的工具类

package com.pattern.proxy.dynamic;

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

public class DynamicUtils {
    // 用于创建代理对象的工具类
    public static Object newProxyInstance(Object target, InvocationHandler invocationHandler) {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

测试代码

package com.pattern;

import com.pattern.proxy.dynamic.DynamicUtils;
import com.pattern.proxy.dynamic.IProxySubject;
import com.pattern.proxy.dynamic.ProxySubjectImpl;
import com.pattern.proxy.dynamic.SubjectInvocationHandler;
import com.pattern.proxy.force.ComputerCompany;
import com.pattern.proxy.force.ComputerOEM;
import com.pattern.proxy.force.IComputerAfterSale;
import com.pattern.proxy.normal.ProxyGamePlayer;
import com.pattern.proxy.normal.RealGamePlayer;
import com.pattern.proxy.staticproxy.Proxy;
import com.pattern.proxy.staticproxy.RealSubject;
import com.pattern.proxy.staticproxy.Subject;

import org.junit.Test;

import java.lang.reflect.InvocationHandler;

public class ProxyTest {
    @Test
    public void DynamicProxyTest(){
        // 创建一个动态代理对象
        IProxySubject subject = new ProxySubjectImpl();
        InvocationHandler invocationHandler = new SubjectInvocationHandler(subject);
        // 创建一个代理对象
        IProxySubject proxy = (IProxySubject) DynamicUtils.newProxyInstance(subject, invocationHandler);
        proxy.doSomething();


    }
}

运行结果

动态代理运行结果

注意

  • 普通代理模式的约束问题,尽量通过团队内编程规范类约束,
  • 代理是有个性的,代理类不仅仅可以实现主题接口也可以实现其他的接口完成不同的任务,代理的目的是在目标对象方法的基础上做增强。这种增强的本质就是对目标对象的方法进行拦截和过滤
  • 代理类不仅仅可以有自己的方法,通常情况下代理的职责并不单一,可以组合其他的真实角色,也可以实现自己的职责
  • 动态代理和静态代理的区别:动态代理主要的意图是解决"审计"问题,也就是切面编程,在不改变已有的代码结构的情况下增强或控制对象的行为。
  • 动态代理的首要条件是:被代理类必须实现一个接口,但现在有例如CGLIB可以不需要接口也能实现动态代理。

NOTE: 文章若无特别说明均为原创文章,如果要转载请保留出处!