面试官提问:什么是动态代理?(下)

简介: 代理这个词最早出现在代理商这个行业,所谓代理商,简而言之,其实就是帮助企业或者老板打理生意,自己本身不做生产任何商品。

3.2、cglib 生成代理对象的玩法

除了 jdk 能实现动态的创建代理对象以外,还有一个非常有名的第三方框架:cglib,它也可以做到运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib 特点如下:

  • cglib 不仅可以代理接口还可以代理类,而 JDK 的动态代理只能代理接口
  • cglib 是一个强大的高性能的代码生成包,它广泛的被许多 AOP 的框架使用,例如我们所熟知的 Spring AOP,cglib 为他们提供方法的 interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,速度非常快。

在使用 cglib 之前,我们需要添加依赖包,如果你已经有spring-corejar包,则无需引入,因为spring中包含了cglib

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

下面,我们还是以两数相加为例,介绍具体的玩法!

  • 创建接口
public interface CglibCalculator {
    /**
     * 计算两个数之和
     * @param num1
     * @param num2
     * @return
     */
    Integer add(Integer num1, Integer num2);
}
  • 目标对象
public class CglibCalculatorImpl implements CglibCalculator {
    @Override
    public Integer add(Integer num1, Integer num2) {
        Integer result = num1 + num2;
        return result;
    }
}
  • 动态代理对象
public class CglibProxyFactory implements MethodInterceptor {
    /**
     * 维护一个目标对象
     */
    private Object target;
    public CglibProxyFactory(Object target) {
        this.target = target;
    }
    /**
     * 为目标对象生成代理对象
     * @return
     */
    public Object getProxyInstance() {
        //工具类
        Enhancer en = new Enhancer();
        //设置父类
        en.setSuperclass(target.getClass());
        //设置回调函数
        en.setCallback(this);
        //创建子类对象代理
        return en.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法调用前,可以添加其他功能....");
        // 执行目标对象方法
        Object returnValue = method.invoke(target, args);
        System.out.println("方法调用后,可以添加其他功能....");
        return returnValue;
    }
}
  • 测试类
public class TestCglibProxy {
    public static void main(String[] args) {
        //目标对象
        CglibCalculator target = new CglibCalculatorImpl();
        System.out.println(target.getClass());
        //代理对象
        CglibCalculator proxyClassObj = (CglibCalculator) new CglibProxyFactory(target).getProxyInstance();
        System.out.println(proxyClassObj.getClass());
        //执行代理方法
        Integer result = proxyClassObj.add(1,2);
        System.out.println("相加结果:" + result);
    }
}
  • 输出结果
class com.example.java.proxy.cglib1.CglibCalculatorImpl
class com.example.java.proxy.cglib1.CglibCalculatorImpl$$EnhancerByCGLIB$$3ceadfe4
方法调用前,可以添加其他功能....
方法调用后,可以添加其他功能....
相加结果:3

将 cglib 生成的代理类改写为静态实现类大概长这样:

public class CglibCalculatorImplByCGLIB extends CglibCalculatorImpl implements Factory {
    private static final MethodInterceptor methodInterceptor;
    private static final Method method;
    public final Integer add(Integer var1, Integer var2) {
        return methodInterceptor.intercept(this, method, new Object[]{var1, var2}, methodProxy);
    }
    //....
}

其中,拦截思路与 JDK 类似,都是通过一个接口方法进行拦截处理!

在上文中咱们还介绍到了,cglib 不仅可以代理接口还可以代理类,下面我们试试代理类。

  • 创建新的目标对象
public class CglibCalculatorClass {
    /**
     * 计算两个数之和
     * @param num1
     * @param num2
     * @return
     */
    public Integer add(Integer num1, Integer num2) {
        Integer result = num1 + num2;
        return result;
    }
}
  • 测试类
public class TestCglibProxyClass {
    public static void main(String[] args) {
        //目标对象
        CglibCalculatorClass target = new CglibCalculatorClass();
        System.out.println(target.getClass());
        //代理对象
        CglibCalculatorClass proxyClassObj = (CglibCalculatorClass) new CglibProxyFactory(target).getProxyInstance();
        System.out.println(proxyClassObj.getClass());
        //执行代理方法
        Integer result = proxyClassObj.add(1,2);
        System.out.println("相加结果:" + result);
    }
}
  • 输出结果
class com.example.java.proxy.cglib1.CglibCalculatorClass
class com.example.java.proxy.cglib1.CglibCalculatorClass$$EnhancerByCGLIB$$e68ff36c
方法调用前,可以添加其他功能....
方法调用后,可以添加其他功能....
相加结果:3

四、静态织入

在上文中,我们介绍的代理方案都是在代码运行时动态的生成class文件达到动态代理的目的。

回到问题的本质,其实动态代理的技术目的,主要为了解决静态代理模式中当目标接口发生了扩展,代理类也要跟着一遍变动的问题,避免造成了工作伤的繁琐和复杂。

在 Java 生态里面,还有一个非常有名的第三方代理框架,那就是AspectJAspectJ通过特定的编译器可以将目标类编译成class字节码的时候,在方法周围加上业务逻辑,从而达到静态代理的效果。

采用AspectJ进行方法植入,主要有四种:

  • 方法调用前拦截
  • 方法调用后拦截
  • 调用方法结束拦截
  • 抛出异常拦截

使用起来也非常简单,首先是在项目中添加AspectJ编译器插件。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <encoding>UTF-8</encoding>
        <complianceLevel>1.6</complianceLevel>
        <verbose>true</verbose>
        <showWeaveInfo>true</showWeaveInfo>
    </configuration>
</plugin>

然后,编写一个方法,准备进行代理。

@RequestMapping({"/hello"})
public String hello(String name) {
    String result = "Hello World";
    System.out.println(result);
    return result;
}

编写代理配置类

@Aspect
public class ControllerAspect {
    /***
     * 定义切入点
     */
    @Pointcut("execution(* com.example.demo.web..*.*(..))")
    public void methodAspect(){}
    /**
     * 方法调用前拦截
     */
    @Before("methodAspect()")
    public void before(){
        System.out.println("代理 -> 调用方法执行之前......");
    }
    /**
     * 方法调用后拦截
     */
    @After("methodAspect()")
    public void after(){
        System.out.println("代理 -> 调用方法执行之后......");
    }
    /**
     * 调用方法结束拦截
     */
    @AfterReturning("methodAspect()")
    public void afterReturning(){
        System.out.println("代理 -> 调用方法结束之后......");
    }
    /**
     * 抛出异常拦截
     */
    @AfterThrowing("methodAspect()")
    public void afterThrowing() {
        System.out.println("代理 -> 调用方法异常......");
    }
}

编译后,hello方法会变成这样。

@RequestMapping({"/hello"})
public String hello(Integer name) throws SQLException {
    JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, name);
    Object var7;
    try {
        Object var5;
        try {
            //调用before
            Aspectj.aspectOf().doBeforeTask2(var2);
            String result = "Hello World";
            System.out.println(result);
            var5 = result;
        } catch (Throwable var8) {
            Aspectj.aspectOf().after(var2);
            throw var8;
        }
        //调用after
        Aspectj.aspectOf().after(var2);
        var7 = var5;
    } catch (Throwable var9) {
        //调用抛出异常
        Aspectj.aspectOf().afterthrowing(var2);
        throw var9;
    }
    //调用return
    Aspectj.aspectOf().afterRutuen(var2);
    return (String)var7;
}

很显然,代码被AspectJ编译器修改了,AspectJ并不是动态的在运行时生成代理类,而是在编译的时候就植入代码到class文件。

由于是静态织入的,所以性能相对来说比较好!

五、小结

看到上面的介绍静态织入方案,跟我们现在使用Spring AOP的方法极其相似,可能有的同学会发出疑问,我们现在使用的Spring AOP动态代理,到底是动态生成的还是静态织入的呢

实际上,Spring AOP代理是对JDK代理和CGLIB代理做了一层封装,同时引入了AspectJ中的一些注解@pointCut@after@before等等,本质是使用的动态代理技术

总结起来就三点:

  • 如果目标是接口的话,默认使用 JDK 的动态代理技术;
  • 如果目标是类的话,使用 cglib 的动态代理技术;
  • 引入了AspectJ中的一些注解@pointCut@after@before,主要是为了简化使用,跟AspectJ的关系并不大;

那为什么Spring AOP不使用AspectJ这种静态织入方案呢?

虽然AspectJ编译器非常强,性能非常高,但是只要目标类发生了修改就需要重新编译,主要原因可能还是AspectJ的编译器太过于复杂,还不如动态代理来的省心!

相关文章
|
1月前
|
设计模式 Java 索引
动态代理总结,面试你要知道的都在这里,无废话!
动态代理总结,面试你要知道的都在这里,无废话!
|
5月前
|
监控 网络协议 安全
TCP和UDP面试题提问
TCP是一种面向连接、可靠的协议,提供确认和重传机制,确保数据完整性和可靠性,适合网页浏览、邮件收发等。UDP则是无连接、轻量级协议,不保证数据可靠性,但适合实时应用如语音视频通话和在线游戏,追求低延迟。
SpringJDK动态代理实现,2024Java面试真题精选干货整理
SpringJDK动态代理实现,2024Java面试真题精选干货整理
|
6月前
|
Java 数据安全/隐私保护
【面试问题】JDK 动态代理与 CGLIB 区别?
【1月更文挑战第27天】【面试问题】JDK 动态代理与 CGLIB 区别?
|
6月前
|
Java
【面试问题】动态代理是什么?
【1月更文挑战第27天】【面试问题】动态代理是什么?
|
监控 Java Spring
【面试题精讲】JDK动态代理
【面试题精讲】JDK动态代理
|
Cloud Native Go
面试中的问题提问:如何通过提问展示你的主动性
面试中的问题提问:如何通过提问展示你的主动性
83 0
|
网络协议 前端开发 数据处理
终于有大佬把TCP/IP协议讲清楚了!面试再也不怂面试官提问了
不难看出,TCP/IP 与 OSI 在分层模块上稍有区别。OSI 参考模型注重“通信协议必要的功能是什么”,而 TCP/IP 则更强调“在计算机上实现协议应该开发哪种程序”。
|
负载均衡 Java
面试官,谈谈动态代理与RPC?我...
面试官,谈谈动态代理与RPC?我...
148 0
|
Java
Java 最常见的面试题:怎么实现动态代理?
Java 最常见的面试题:怎么实现动态代理?
143 0
下一篇
无影云桌面