java面试常见模式问题---代理模式

 更新时间:2021年06月09日 14:07:15   作者:兴趣使然の草帽路飞  
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息
  • 本篇总结的是 代理设计模式,后续会经常更新~
  • 代理模式最直观的解释就是,通过代理,将被代理对象 “增强”!(即,扩展被代理对象的功能)
  • 代理模式分为静态代理,和动态代理:动态代理的代理类是动态生成的 , 静态代理的代理类是我们提前写好的逻辑。
  • Java 中实现动态代理的方式有 2 种:
  • JDK 动态代理
  • CGLIB 动态代理

1、静态代理

静态代理角色分析

  • 抽象角色 :一般使用接口或者抽象类来实现。
  • 真实角色 :被代理的角色。
  • 代理角色: 代理真实角色 , 代理真实角色后 ,一般会做一些附属的操作。
  • 调用方:使用代理角色来进行一些操作。

我们以租客租客租房子为例,涉及到的对象有:租客、中介、房东。(房东即为被代理对象,中介即为代理对象)

租客通过中介之手租住房东的房子,代理对象中介需要寻找租客租房,并从中获取中介费用。

代码实现

Rent.java 即抽象角色

// 抽象角色:租房
public interface Rent {
   public void rent();
}

Host.java 即真实角色

// 真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy.java 即代理角色

//代理角色:中介
public class Proxy implements Rent {
   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }
   // 租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   // 看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   // 收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}

Client.java 调用方,即客户

// 客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       // 房东要租房
       Host host = new Host();
       // 中介帮助房东
       Proxy proxy = new Proxy(host);
       // 你去找中介!
       proxy.rent();
  }
}

静态代理的缺点

需要手动创建代理类,如果需要代理的对象多了,那么代理类也越来越多。

为了解决,这个问题,就有了动态代理 !

2、动态代理

说到动态代理,面试的时候肯定会问动态代理的两种实现方式:

先来看公共的 UserService 接口,和 UserServiceImpl 实现类:

/**
 * @author csp
 * @date 2021-06-03
 */
public interface UserService {
    /**
     * 登录
     */
    void login();
    /**
     * 登出
     */
    void logout();
}
/**
 * @author csp
 * @date 2021-06-03
 */
public class UserServiceImpl implements UserService{
    @Override
    public void login() {
        System.out.println("用户登录...");
    }
    @Override
    public void logout() {
        System.out.println("用户推出登录...");
    }
}

JDK 动态代理

代码如下

/**
 * @author csp
 * @date 2021-06-03
 */
public class JDKProxyFactory implements InvocationHandler {
    // 目标对象(被代理对象)
    private Object target;
    public JDKProxyFactory(Object target) {
        super();
        this.target = target;
    }
    /**
     * 创建代理对象
     *
     * @return
     */
    public Object createProxy() {
        // 1.得到目标对象的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 2.得到目标对象的实现接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 3.第三个参数需要一个实现invocationHandler接口的对象
        Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
        return newProxyInstance;
    }
    /**
     * 真正执行代理增强的方法
     *
     * @param proxy  代理对象.一般不使用
     * @param method 需要增强的方法
     * @param args   方法中的参数
     * @return
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK 动态代理:登录/登出前逻辑校验......");
        Object invoke = method.invoke(target, args);
        System.out.println("JDK 动态代理:登录/登出后日志打印......");
        return invoke;
    }
    public static void main(String[] args) {
        // 1.创建对象
        UserServiceImpl userService = new UserServiceImpl();
        // 2.创建代理对象
        JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(userService);
        // 3.调用代理对象的增强方法,得到增强后的对象
        UserService userServiceProxy = (UserService) jdkProxyFactory.createProxy();
        userServiceProxy.login();
        System.out.println("==================================");
        userServiceProxy.logout();
    }
}

输出结果如下

JDK 动态代理:登录/登出前逻辑校验......
用户登录...
JDK 动态代理:登录/登出后日志打印......
==================================
JDK 动态代理:登录/登出前逻辑校验......
用户推出登录...
JDK 动态代理:登录/登出后日志打印......

CGLIB 动态代理

代码如下:

/**
 * @author csp
 * @date 2021-06-03
 */
public class CglibProxyFactory implements MethodInterceptor {
    // 目标对象(被代理对象)
    private Object target;
    // 使用构造方法传递目标对象
    public CglibProxyFactory(Object target) {
        super();
        this.target = target;
    }
    /**
     * 创建代理对象
     *
     * @return
     */
    public Object createProxy() {
        // 1.创建Enhancer
        Enhancer enhancer = new Enhancer();
        // 2.传递目标对象的class
        enhancer.setSuperclass(target.getClass());
        // 3.设置回调操作
        enhancer.setCallback(this);
        return enhancer.create();
    }
    /**
     * 真正执行代理增强的方法
     * @param o 代理对象
     * @param method 要增强的方法
     * @param objects 要增强方法的参数
     * @param methodProxy 要增强的方法的代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("cglib 动态代理:登录/登出前逻辑校验......");
        Object invoke = method.invoke(target, objects);
        System.out.println("cglib 动态代理:登录/登出后日志打印......");
        return invoke;
    }
    public static void main(String[] args) {
        // 1.创建对象
        UserServiceImpl userService = new UserServiceImpl();
        // 2.创建代理对象
        CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(userService);
        // 3.调用代理对象的增强方法,得到增强后的对象
        UserService userServiceProxy = (UserService) cglibProxyFactory.createProxy();
        userServiceProxy.login();
        System.out.println("==================================");
        userServiceProxy.logout();
    }
}

测试结果如下

cglib 动态代理:登录/登出前逻辑校验......
用户登录...
cglib 动态代理:登录/登出后日志打印......
==================================
cglib 动态代理:登录/登出前逻辑校验......
用户推出登录...
cglib 动态代理:登录/登出后日志打印......

面试题一:JDK动态代理和CGLIB动态代理区别?

① JDK 动态代理本质上是实现了被代理对象的接口,而 CGLib 本质上是继承了被代理对象,覆盖其中的方法。

② JDK 动态代理只能对实现了接口的类生成代理,CGLib 则没有这个限制。但是 CGLib 因为使用继承实现,所以 CGLib 所以无法对 final private 方法static方法进行代理。

③ JDK 动态代理是 JDK 里自带的,CGLib 动态代理需要引入第三方的 jar 包。

④ 在调用代理方法上,JDK动态代理是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用。(看过一篇文章,介绍说 FastClass 简单的理解,就是使用一个 index 下标作为入参,可以直接定位到要调用的方法直接,并进行调用)

在性能上,JDK1.7 之前,由于使用了 FastClass 机制,CGLib 在执行效率上比 JDK 快,但是随着 JDK 动态代理的不断优化,从 JDK 1.7 开始,JDK 动态代理已经明显比 CGLib 更快了。

面试题二:JDK 动态代理为什么只能对实现了接口的类生成代理?

根本原因是通过 JDK 动态代理生成的类已经继承了 Proxy 类,所以无法再使用继承的方式去对类实现代理。

总结

文章会不定时更新,有时候一天多更新几篇,如果帮助您复习巩固了知识点,还请三连支持一下,后续会一点点的更新!希望大家多多关注脚本之家的其他内容!

相关文章

  • 源码解析JDK 1.8 中的 Map.merge()

    源码解析JDK 1.8 中的 Map.merge()

    这篇文章主要介绍了JDK 1.8 之 Map.merge()的相关知识,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-10-10
  • Java实现按行分割大文件

    Java实现按行分割大文件

    这篇文章主要为大家详细介绍了Java实现按行分割大文件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • mybatis的ParamNameResolver参数名称解析

    mybatis的ParamNameResolver参数名称解析

    这篇文章主要为大家介绍了mybatis的ParamNameResolver参数名称解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 详解Spring Boot使用Maven自定义打包方式

    详解Spring Boot使用Maven自定义打包方式

    这篇文章主要介绍了Spring Boot使用Maven自定义打包方式,本文通过多种方式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • SpringBoot工程Docker多环境中使用同一个Jar包解决方案

    SpringBoot工程Docker多环境中使用同一个Jar包解决方案

    在Docker多环境部署中,SpringBoot工程可以通过环境变量来动态改变配置,无需重新打包,利用volume挂载或docker cp命令,可以将配置文件直接传入容器,提高部署效率,并保证安全性
    2024-09-09
  • Spring Boot中slf4j日志依赖关系示例详解

    Spring Boot中slf4j日志依赖关系示例详解

    在项目开发中,记录日志是必做的一件事情。而当我们使用Springboot框架时,记录日志就变得极其简单了。下面这篇文章主要给大家介绍了关于Spring Boot中slf4j日志依赖关系的相关资料,需要的朋友可以参考下
    2018-11-11
  • 解析Spring中@Controller@Service等线程安全问题

    解析Spring中@Controller@Service等线程安全问题

    这篇文章主要为大家介绍解析了Spring中@Controller@Service等线程的安全问题,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-03-03
  • mybatis-plus 如何使用雪花算法ID生成策略

    mybatis-plus 如何使用雪花算法ID生成策略

    这篇文章主要介绍了mybatis-plus如何使用雪花算法ID生成策略,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 一文探究ArrayBlockQueue函数及应用场景

    一文探究ArrayBlockQueue函数及应用场景

    这篇文章主要为大家介绍了一文探究ArrayBlockQueue函数及应用场景,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Spring Boot 优雅整合多数据源

    Spring Boot 优雅整合多数据源

    这篇文章主要介绍了Spring Boot 优雅整合多数据源,多数据源就是在一个单一应用中涉及到了两个及以上的数据库,更多相关内容需要的小伙伴可以参考下面文章介绍
    2022-05-05

最新评论