专业的事情,交给专业的人去干,你只管好好上班 —— 代理模式

大家好,我是徒手敲代码。

今天来介绍一下代理模式。

在日常生活中,当需要完成一项专业性强、涉及复杂流程的任务时,比如:买房,我们会选择委托给专业人士------房地产中介。这个中间人扮演的就是代理角色。他凭借专业知识,帮我们处理各种细节,确保任务顺利完成。但是如果我们省去这些代理,直接与多方交涉,可能会面临信息不对称、流程不熟等问题。

像这种专业的事情,交给专业的人去做的原则,就相当于软件范畴中的代理模式。

假设有一个UserService接口,定义了新增用户insertUser()和更新用户updateUser()的方法。实现类UserServiceImpl,实现了这个接口。

java 复制代码
public interface UserService {
    void insertUser(User user);
    void updateUser(User user);
}

public class UserServiceImpl implements UserService {
    @Override
    public void insertUser(User user) {
        // 实现用户插入逻辑
    }

    @Override
    public void updateUser(User user) {
        // 实现用户更新逻辑
    }

如果我们需要在调用insertUser()updateUser()前打印日志,并且不使用代理模式,直接在方法里面加其他逻辑,就变成这样:

typescript 复制代码
public class UserServiceImpl implements UserService {
    @Override
    public void insertUser(User user) {
        log("Inserting user...");
        // 实现用户插入逻辑 
    }

    @Override
    public void updateUser(User user) {
        log("Updating user...");
        // 实现用户更新逻辑
    }

    private void log(String message) {
        // 打印日志的具体实现
    }
}

过了两天,如果又要在那两个方法前,加个事务,那么原来的方法又要再改一次。虽然这样可以实现功能,但是随着时间的推移,这段代码会越来越复杂。显然,是不符合对扩展开放,对修改关闭这个原则。

这个问题的解决方案是,可以加多一个代理类,负责做业务范畴以外的逻辑。比如,创建一个UserServiceProxy类,同样实现UserService接口,并持有一个UserService对象。代理类负责封装日志和事务管理逻辑,将原本直接对UserServiceImpl的操作转交给代理类

typescript 复制代码
public class UserServiceProxy implements UserService {
    private final UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void insertUser(User user) {
        beginTransaction();
        log("Inserting user...");
        target.insertUser(user);
        commitTransaction();
    }

    @Override
    public void updateUser(User user) {
        beginTransaction();
        log("Updating user...");
        target.updateUser(user);
        commitTransaction();
    }

    private void log(String message) {...}
    private void beginTransaction() {...}
    private void commitTransaction() {...}
    private void rollbackTransaction() {...}
}

// 使用代理类
UserService userService = new UserServiceProxy(new UserServiceImpl());
userService.insertUser(someUser);

像这种预先定义好代理类结构,并实现相关逻辑的模式,称为静态代理

显然,这种方式有个缺点,就是当需要为很多个类添加相同额外操作的时候,就要对每个类都写一个代理类,会出现大量重复的代码。

此时此刻,动态代理就诞生了。

Java提供了两种实现动态代理的方式:基于**InvocationHandler接口的JDK原生动态代理和基于CGLIB**库的字节码生成技术。

基于接口的动态代理:

typescript 复制代码
public class LoggingAndTransactionalProxy implements InvocationHandler {
    private final Object target;

    public static <T> T createProxy(T target) {
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new LoggingAndTransactionalProxy(target)
        );
    }

    private LoggingAndTransactionalProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beginTransaction();
        try {
            log(method.getName() + " invoked...");
            Object result = method.invoke(target, args);
            commitTransaction();
            return result;
        } catch (Exception e) {
            rollbackTransaction();
            log(method.getName() + " failed.");
            throw e;
        }
    }

    private void log(String message) {...}
    private void beginTransaction() {...}
    private void commitTransaction() {...}
    private void rollbackTransaction() {...}
}

// 使用动态代理
UserService userService = LoggingAndTransactionalProxy.createProxy(new UserServiceImpl());
userService.insertUser(someUser);

注意:这种方式,必须要求构造参数的入参对象类型,实现了一个或多个接口。

CGLIB动态代理

typescript 复制代码
public class LoggingAndTransactionalInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        beginTransaction();
        try {
            log(method.getName() + " invoked...");
            Object result = proxy.invokeSuper(obj, args);
            commitTransaction();
            return result;
        } catch (Exception e) {
            rollbackTransaction();
            log(method.getName() + " failed.");
        }
    }

    private void log(String message) {...}
    private void beginTransaction() {...}
    private void commitTransaction() {...}
    private void rollbackTransaction() {...}

    public static <T> T createProxy(T target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new LoggingAndTransactionalInterceptor());
        return (T) enhancer.create();
    }

    // 使用CGLIB动态代理
    UserService userService = LoggingAndTransactionalInterceptor.createProxy(new UserServiceImpl());
    userService.insertUser(someUser);
}

这两种方式,都是在调用的时候,才传入被代理的对象,所以会灵活很多。

最后,来说一下代理模式和装饰者模式的区别,简单来理解,代理模式,就是多了个外人来增强功能,所以这些功能跟被代理类本身,是没说明关系的。就像房地产中介处理房子买卖、过户很厉害,关你买家什么事,都是中介自己的能力。

而装饰者模式,增强的功能是属于自己的功能,想要更加深入的了解装饰着模式,请期待下一篇的文章。

今天的分享到这里结束了。

关注公众号"徒手敲代码",免费领取腾讯大佬推荐的Java电子书!

相关推荐
武子康7 分钟前
大数据-127 - Flink StateBackend详解:Memory、Fs、RocksDB 与 OperatorState 管理机制与重分配原理
大数据·后端·flink
赶飞机偏偏下雨10 分钟前
【Java笔记】消息队列
java·开发语言·笔记
豐儀麟阁贵30 分钟前
2.6 代码注释与编码规
java·开发语言
程序员三明治32 分钟前
【Mybatis从入门到入土】ResultMap映射、多表查询与缓存机制全解析
java·sql·缓存·mybatis·resultmap·缓存机制·多表查询
间彧37 分钟前
Java transient关键字详解与项目实战
后端
华仔啊37 分钟前
Java 重试机制没写对,线上很容易出问题!这份生产级方案请收好
java·后端
你不是我我39 分钟前
【Java 开发日记】什么是线程池?它的工作原理?
java·开发语言
Seven9741 分钟前
剑指offer-35、数组中的逆序对
java·leetcode
梵得儿SHI1 小时前
Java 反射机制深度解析:从运行时 “解剖” 类的底层逻辑
java·开发语言·反射·反射机制·private·类成员·反射的三大核心功能
CodeSheep1 小时前
大家有没有发现一个奇特现象:你能在一个公司工作 12 年以上,无论你多忠诚多卖力,一旦公司赚的少了,那你就成了“眼中钉肉中刺”
前端·后端·程序员