掘金 后端 ( ) • 2024-04-18 09:56

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

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

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

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

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

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()前打印日志,并且不使用代理模式,直接在方法里面加其他逻辑,就变成这样:

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的操作转交给代理类

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**库的字节码生成技术。

基于接口的动态代理:

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动态代理

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电子书!