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

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

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

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

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

假设有一个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电子书!

相关推荐
Ciderw13 分钟前
MySQL日志undo log、redo log和binlog详解
数据库·c++·redis·后端·mysql·面试·golang
m0_7482359523 分钟前
SpringBoot:解决前后端请求跨域问题(详细教程)
java·spring boot·后端
LUCIAZZZ38 分钟前
简单说一下什么是RPC
java·网络·网络协议·计算机网络·spring cloud·rpc
嘵奇41 分钟前
最新版IDEA下载安装教程
java·intellij-idea
s_fox_1 小时前
Nginx Embedded Variables 嵌入式变量解析(4)
java·网络·nginx
Jelena157795857921 小时前
使用Java爬虫获取1688 item_get_company 接口的公司档案信息
java·开发语言·爬虫
数据小小爬虫1 小时前
Jsoup解析商品详情时,如何确保数据准确性?
java·爬虫
V+zmm101341 小时前
自驾游拼团小程序的设计与实现(ssm论文源码调试讲解)
java·数据库·微信小程序·小程序·毕业设计
坚定信念,勇往无前2 小时前
springboot单机支持1w并发,需要做哪些优化
java·spring boot·后端
丁总学Java2 小时前
`AdminAdminDTO` 和 `userSession` 对象中的字段对应起来的表格
java