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

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

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

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

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

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

相关推荐
qmx_0734 分钟前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战43 分钟前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
罗政5 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
架构文摘JGWZ6 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师7 小时前
spring获取当前request
java·后端·spring
aPurpleBerry7 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏7 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko7 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring
2301_789985947 小时前
Java语言程序设计基础篇_编程练习题*18.29(某个目录下的文件数目)
java·开发语言·学习