设计模式系列篇(五)——代理模式

What

代理模式(Proxy Design Pattern),指的是在不改变原有类的情况下,通过引入代理类,为目标类提供额外的功能操作,实现对原有类的扩展的目的。一般来说,代理模式是对原有类的非功能性的扩展,如监控、统计、鉴权、限流、事务、幂等、日志等。

Why

代理模式主要有以下2个优点:

  1. 无须改变原有类,就能实现对原有类的扩展;
  2. 可以很好的将功能代码和框架代码解耦。可以将监控、统计、鉴权、限流、事务、幂等、日志这些附加功能与业务功能解耦,放到代理类中统一处理,让程序员只需要关注业务方面的开发。

How

实现代理模式主要有静态代理、JDK动态代理和cglib代理三种方式。今天,我们通过设计一个接口的耗时统计框架来向大家介绍代理模式的几种实现方式。假设我们正在开发一个电商平台,现需要对所有接口的耗时进行统计。为方便讲解,我们仅针对用户的登录和注册模块进行功能实现。

首先,我们先实现User实体类。

public class User {
    private Long id;
    private String username;
    private String account;
    private String password;
    private String address;
    // ... Omitting other properties

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return String.format("id: %d, username: %s, account: %s, address: %s",
                id, username, account, address);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return Objects.equals(getId(), user.getId()) &&
                Objects.equals(getPassword(), user.getPassword());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getId(), getUsername(), getAccount(), getPassword(), getAddress());
    }
}

然后,就是UserController 接口和UserControllerImpl实现类:

public interface UserController {
    boolean login(User user);
    boolean register(User user);
}

public class UserControllerImpl implements UserController {

    public boolean login(User user) {
        // Omitting login logic
        // ...
        return true;
    }

    public boolean register(User user) {
        try {
            // Omitting login logic
            // ...
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}

为了简单,这里省略Dao层和Service层。

如果我们要统计接口的耗时,那么我们可以针对UserController类进行扩展,增加接口耗时的逻辑,这里就可以使用代理模式来实现。下面,分别对三种实现方式进行介绍。

静态代理

静态代理实现起来比较简单,就是新建一个 UserControllerProxy类实现UserController类,然后对其接口方法进行扩展即可。实现细节如下:

public class UserControllerImplProxy implements UserController {
    private UserController userController;

    public UserControllerImplProxy(UserController userController) {
        this.userController = userController;
    }

    @Override
    public boolean login(User user) {
        Long startTime = System.currentTimeMillis();
        boolean res = userController.login(user);
        Long endTime = System.currentTimeMillis();
        System.out.println(String.format("login elapsed %d ms", endTime - startTime));
        return res;
    }

    @Override
    public boolean register(User user) {
        Long startTime = System.currentTimeMillis();
        boolean res = userController.register(user);
        Long endTime = System.currentTimeMillis();
        System.out.println(String.format("register elapsed %d ms", endTime - startTime));
        return res;
    }
}

搞个测试类,测试下效果:

public class TestMain {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1L);
        user.setUsername("test");
        user.setAccount("test");
        user.setPassword("*****");
        
        UserService userService = new UserServiceImpl();

        // static proxy
        UserController userController = new UserControllerImpl(userService);
        UserControllerImplProxy userControllerProxy1 = new UserControllerImplProxy(userController);
        Assert.check(userControllerProxy1.register(user), String.format("user [%s] registered error", user.toString()));
        Assert.check(userControllerProxy1.login(user), String.format("user [%s] login error", user.toString()));

    }
}

结果如下:

register elapsed 0 ms
login elapsed 0 ms

静态代理方式实现起来确实十分简单,但是也不难发现静态的弊端,那就是静态代理需要对所有方法都要实现类似的逻辑,会存在大量的代码冗余;另外,如果需要多个代理类,会导致类的数量增多,不利于代码维护。因此,便需要动态代理来解决这个问题。

JDK动态代理

所谓动态代理(Dynamic Proxy),就是我们不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

静态代理与动态代理的区别主要在:

  1. 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件;
  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。

Java借助反射语法实现动态代理非常简单。具体实现如下:

public class JdkDynamicProxyFactory {

    public Object createProxy(Object proxiedObject) {
        Class[] interfaces = proxiedObject.getClass().getInterfaces();
        DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject);
        return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), interfaces, handler);
    }

    private static class DynamicProxyHandler implements InvocationHandler {
        private Object proxiedObject;

        public DynamicProxyHandler(Object proxiedObject) {
            this.proxiedObject = proxiedObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = method.invoke(proxiedObject, args);
            long endTime = System.currentTimeMillis();
            System.out.println(String.format("%s elapsed %d ms", method.getName(), endTime - startTime));
            return result;
        }
    }
}

// 测试方法:
public class TestMain {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1L);
        user.setUsername("test");
        user.setAccount("test");
        user.setPassword("*****");

        UserController userController = new UserControllerImpl();

        // jdk dynamic proxy
        JdkDynamicProxyFactory jdkDynamicProxyFactory = new JdkDynamicProxyFactory();
        UserController userControllerProxy2 = (UserController) jdkDynamicProxyFactory.createProxy(userController);
        userControllerProxy2.register(user);
        userControllerProxy2.login(user);
    }
}

动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

cglib代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib与动态代理最大的区别就是:

  1. 使用动态代理的对象必须实现一个或多个接口
  2. 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
添加cglib的Maven依赖:


    cglib
    cglib
    3.2.5

具体实现如下:

public class CglibDynamicProxyFactory implements MethodInterceptor {
    private Object target;

    public CglibDynamicProxyFactory(Object target) {
        this.target = target;
    }

    // generate proxy object for target object.
    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return en.create();
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        long endTime = System.currentTimeMillis();
        System.out.println(String.format("%s elapsed %d ms", method.getName(), endTime - startTime));
        return result;
    }
}

// 测试类
public class TestMain {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1L);
        user.setUsername("test");
        user.setAccount("test");
        user.setPassword("*****");

        UserController userController = new UserControllerImpl();

        // cglib proxy
        CglibDynamicProxyFactory cglibDynamicProxyFactory = new CglibDynamicProxyFactory(userController);
        UserController userControllerProxy3 = (UserController) cglibDynamicProxyFactory.getProxyInstance();
        userControllerProxy3.register(user);
        userControllerProxy3.login(user);
    }
}

代码地址

i-learning

写在最后

如果你觉得我写的文章帮到了你,欢迎点赞、评论、分享、赞赏哦,你们的鼓励是我不断创作的动力~

你可能感兴趣的:(设计模式系列篇(五)——代理模式)