1、描述

代理模式属于结构型模式中的一种,通过对代理对象的调用来达到对原对象的增强、减弱作用。通过代理类的生成时机,我们将编译期就生成代理类的情况称之为静态代理模式,而在 Java 运行期动态生成代理类的场景称为动态代理模式。动态代理又基于接口继承两种实现方式分别分为 JDK 动态代理和 CGLib 动态代理两种。

2、适用性

  • 当访问对象不方便直接引用时(如原对象授权太过宽泛、需要对不同用户级别提供不同权限)。
  • 原对象功能需要增加,可以通过代理模式在不影响原始类的基础上实现目的。

3、实现逻辑

  1. 抽取真实主题类需要代理的接口获取抽象主题类。
  2. 代理类和主题类共同实现抽象主题类。
  3. 代理类控制真实主题类生命周期。代理在完成⼀些任务后 应将⼯作委派给服真实主题类的对象。
  • 真实主题类:原始类、需要被代理的类。

  • 抽象主题类:定义真实主题类中需要被代理类代理的接口。保证代理对象和原始对象的可交互性。

  • 代理类:实现抽象主题类,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

4、实战代码

4.1 静态代理

存在一个 Member 操作的业务层代码。现仅提供新增和查询功能的代理类,并需记录查询日志。

/**
 * 用户类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:00:46
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private Long id;
    private String name;
}

/**
 * 原始主题类接口(模拟实际开发,代理模式中这个类没有实际用途)
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:17:12
 */
public interface MemberService {

    Member getMember(Long id);

    List<Member> listMember();

    void saveMember(Long id, String name);

    void updateMember(Long id, String name);

    void deleteMember(Long id);
}

/**
 * 抽象主题类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:00:14
 */
public class MemberServiceImpl implements MemberService, ProxyService {
    /**
     * 模拟 DB 初始化点数据
     */
    public static final Map<Long, Member> db = new HashMap<>();

    static {
        db.put(1L, new Member(1L, "张三"));
        db.put(2L, new Member(2L, "李四"));
    }

    @Override
    public Member getMember(Long id) {
        Member member = db.get(id);
        System.out.println(member);
        return member;
    }

    @Override
    public List<Member> listMember() {
        ArrayList<Member> members = new ArrayList<>(db.values());
        System.out.println(members);
        return members;
    }

    @Override
    public void saveMember(Long id, String name) {
        db.put(id, new Member(id, name));
    }

    @Override
    public void updateMember(Long id, String name) {
        db.put(id, new Member(id, name));
    }

    @Override
    public void deleteMember(Long id) {
        db.remove(id);
    }
}

/**
 * 抽象主题类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:17:12
 */
public interface ProxyService {

    Member getMember(Long id);

    List<Member> listMember();

    void saveMember(Long id, String name);

}

/**
 * 代理类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:16:26
 */
public class ProxyServerImpl implements ProxyService {

    private MemberService memberService = new MemberServiceImpl();

    @Override
    public Member getMember(Long id) {
        System.out.println("代理类查询 member 详情信息");
        return memberService.getMember(id);
    }

    @Override
    public List<Member> listMember() {
        System.out.println("代理类查询 member 列表信息");
        return memberService.listMember();
    }

    @Override
    public void saveMember(Long id, String name) {
        System.out.println("代理类新增 member 信息");
        memberService.saveMember(id, name);
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:28:01
 */
public class App {
    public static void main(String[] args) {
        ProxyService proxyServer = new ProxyServerImpl();
        proxyServer.saveMember(3L, "王五");
        System.out.println("--------------");
        proxyServer.getMember(1L);
        System.out.println("--------------");
        proxyServer.listMember();
    }
}

执行结果:
软件设计模式白话文系列(六)代理模式-LMLPHP
通过代理模式,我实现了对原始对象的访问控制,客户端只能调用新增和查询接口,同时也实现对原始对象接口的增强作用,新增了日志打印功能。

4.2 JDK 动态代理

Java 中提供了一个动态代理类 Proxy,Proxy 不是上述中的代理类,而是通过其提供的静态方法(newProxyInstance 方法)来动态创建代理对象。

/**
 * 动态代理工厂
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 13:47:40
 */
public class ProxyFactory {
    private static ProxyService proxyService = new MemberServiceImpl();

    public static ProxyService getProxyService() {
        return (ProxyService) Proxy.newProxyInstance(
                proxyService.getClass().getClassLoader(),
                proxyService.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("代理类操作 member 功能:" + method.getName());
                    return method.invoke(proxyService, args);
                });
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:28:01
 */
public class App {
    public static void main(String[] args) {
        ProxyService proxyServer = ProxyFactory.getProxyService();
        proxyServer.saveMember(3L, "王五");
        System.out.println("--------------");
        proxyServer.getMember(1L);
        System.out.println("--------------");
        proxyServer.listMember();
    }
}

执行结果:
软件设计模式白话文系列(六)代理模式-LMLPHP
动态代理相对于静态代理,可以把多个方法集中控制,在需代理的方法数量多或者会出现改动的时候,动态代理效率远高于静态代理。

4.3 CGLib 动态代理

JDK 动态代理和静态代理的必要前提是代理类和原始类都需要创建⼀个接⼝来实现代理和服务对象的可交换性。 如果没有现成的服务接⼝,从服务类中抽取接⼝并⾮总是可⾏的, 因为我们需要对服务的所有客户端进⾏修改, 让它们使⽤接⼝。所以我们的备选方案将代理作为服务类的⼦类, 这样代理就能继承服务的所有接⼝了。

这里我们需要导入 CGLib 依赖,CGLib 可以为没有提供接口的类实现代理。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
/**
 * 抽象主题类 不用实现接口
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:00:14
 */
public class MemberServiceImpl {
    /**
     * 模拟 DB 初始化点数据
     */
    public static final Map<Long, Member> db = new HashMap<>();

    static {
        db.put(1L, new Member(1L, "张三"));
        db.put(2L, new Member(2L, "李四"));
    }

    public Member getMember(Long id) {
        Member member = db.get(id);
        System.out.println(member);
        return member;
    }

    public List<Member> listMember() {
        ArrayList<Member> members = new ArrayList<>(db.values());
        System.out.println(members);
        return members;
    }

    public void saveMember(Long id, String name) {
        db.put(id, new Member(id, name));
    }

    public void updateMember(Long id, String name) {
        db.put(id, new Member(id, name));
    }

    public void deleteMember(Long id) {
        db.remove(id);
    }
}

/**
 * 动态代理工厂
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 13:47:40
 */
public class ProxyFactory implements MethodInterceptor {

    public MemberServiceImpl getProxyObject() {
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(MemberServiceImpl.class);
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        return (MemberServiceImpl) enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理类操作 member 功能:" + method.getName());
        return methodProxy.invokeSuper(o, objects);
    }
}

/**
 * 测试类
 *
 * @author Eajur.Wen
 * @version 1.0
 * @date 2022-11-11 06:28:01
 */
public class App {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        MemberServiceImpl proxyObject = proxyFactory.getProxyObject();
        proxyObject.saveMember(3L, "王五");
        System.out.println("--------------");
        proxyObject.getMember(1L);
        System.out.println("--------------");
        proxyObject.listMember();
        System.out.println("--------------");
        proxyObject.deleteMember(1L);
    }
}

执行结果:
软件设计模式白话文系列(六)代理模式-LMLPHP

这里需要注意,CGLib 生成代理类的本质是生成原始类的子类,因此无法代理被 final 修饰的类,或者被 final 修饰的方法,且会将原始类可被继承的方法完全暴露给客户端。

这里简单提一下,在 JDK 1.8 过后,JDK 动态代理效率高于 CGLib 动态代理,Spring AOP 中也是根据需代理类的方法有无接口来优先判断是否使用 JDK 动态代理。

11-11 17:32