当前位置: 主页 > 建站知识 > APP开发

星空体育入口:招聘面试官问:动态代理是什么?

发布时间:2024-06-23 21:29   浏览次数:次   作者:zoc7RcITctunhMtq7EzA
这篇文章是从微信公众号「Java极客技术」转载的,作者是鸭血粉丝汤。请联系Java极客技术公众号获取转载许可。一、代理是什么意思呢?据史籍记载,代理一词最早出现在代理商这一行业中。所谓代理商,简而言之,就是帮助企业或老板打理生意,自身不进行任何商品生产。举个例子,当我们前往火车站购票时,如果人数较少,售票员一个人还能应付过来。但是一旦人数增多,情况就会变得拥挤,因此会出现各种代售点,我们可以从代售点购票,从而加快售票速度。随着

代售点的出现,可以说,它明显地提升了老板的用户购票体验。从软件设计的观点来看,实际效果是一样的。使用代理模式编程能明显增强原有功能并简化方法调用方式。在讲解动态代理之前,让我们先来谈一谈静态代理。

静态代理

接着,我们以两数相加为例来展示实现过程星空体育APP。

接口类

public interface Calculator {      /**      * 求两个数的和      * @param num1      * @param num2      * @return      */     Integer add(Integer num1, num2); } 

目标物件

public class CalculatorImpl implements Calculator { @Override     public Integer add(Integer num1,整数num2) {\n Integer result = num1 + num2;\n return result;\ \n代理对象\npublic class CalculatorProxyImpl implements Calculator {\n private Calculator calculator;\n @Override\n public Integer add(Integer num1, 整数 num2) {         // 在调用方法前,可以添加其他功能...         整数 结果 = 计算器.相加(num1, num2);         // 方法调用后,可以为CalculatorProxyImpl添加其他功能。在CalculatorProxyClient测试类中,通过创建CalculatorImpl对象作为目标对象,再创建CalculatorProxyImpl对象作为代理对象,可以实现代理的功能。例如,代理对象可以调用目标对象的add方法,进行一些额外的操作,并返回所需的结果。2)System.out.println("累加结果:" + result);\n \n输出结果\n累加结果:3\n通过这种代理方式,最大的好处是可以在不改变目标对象的情况下,扩展目标对象的功能。虽然代理模式有其优点,但也存在一些缺点:代理对象和目标对象需实现相同的接口。因此,当目标对象扩展新功能时,代理对象也需要相应扩展,因而难以维护。动态代理的优点在于可以解决目标对象扩展新功能时代理对象也需要跟着扩展的问题。然而,它也有缺点,需要代理对象和目标对象实现相同的接口,因此,当目标对象扩展新的功能时,代理对象也需要一起扩展,维护起来不容易。那么它是如何被解决的呢?以JDK为例,当需要为特定目标对象添加代理处理时,JDK会动态在内存中构建代理对象,以实现对目标对象的代理功能。在下面,我们仍然以两数相加为例,详细介绍具体的方法!3.1、JDK 中生成代理对象的方式是通过创建接口。创建接口时需要使用public关键字, 并且在接口中定义方法。例如:\n```java\npublic interface JdkCalculator{\n /**\n * 计算两个数之和\n * @param num1\n * @param num2\n * @return\n */\n Integer add(Integer num1,\n```实现整数目标接口的JdkCalculatorImpl类,实现JdkCalculator接口。 覆盖公共Integer add(Integer num1)方法。 整数 num2) {         整数 result = num1 + num2;         返回 result;     } } 

动态代理对象

星空体育入口

public class JdkProxyFactory {      /**      * 维护一个目标对象      */     私有的 Object target;      公共的 JdkProxyFactory(Object target) {         this.target = target;     }      公共的 Object getProxyInstance(){         Object proxyClassObj = Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces()                 新的 InvocationHandler(){                      @Override                    public Object invoke(Object proxy, Method method, Object[] args) 抛出 Throwable 异常 {                         System.out.println("方法调用前,可以增加其他功能...");                          // 执行目标对象方法                         Object 返回值 = method.invoke(target,System.out.println("在方法调用后, args); ...");可以增加其他功能...");                          返回 returnValue;                     }                 });         返回 proxyClassObj;     } } 

测试类

公共 类
TestJdkProxy {      公共 静态 空 main(字符串public static void main(String[] args) {\n    // 目标对象\n    JdkCalculator target = new JdkCalculatorImpl();\n    System.out.println(target.getClass());\n    \n    // 代理对象\n    JdkCalculator proxyClassObj = (JdkCalculator) new JdkProxyFactory(target).getProxyInstance();\n    System.out.println(proxyClassObj.getClass());\n    \n    // 执行代理方法\n    Integer result = proxyClassObj.add(1, 2);\n    System.out.println("结果为:" + result);\

2) {{\nSystem.out.println("Sum result: " + result);\
}}\n输出如下:\nclass com.example.java.proxy.jdk1.JdkCalculatorImpl\nclass com.sun.proxy.$Proxy0\n在调用方法前,额外功能可以被添加...\n在调用方法后,可以增加其他功能...

相加结果为:3

使用JDK技术动态创建interface实例的步骤如下:1.首先定义一个InvocationHandler实例,其负责实现接口的方法调用

2.通过Proxy.newProxyInstance()创建interface实例,其需要3个参数:

(1)使用的ClassLoader,通常为接口类的ClassLoader

(2)需要实现的接口数组至少需要提供一个接口作为参数。    (3)用于处理接口方法的调用的 InvocationHandler 实例。3. 对于返回的 Object,需要将其强制转换为接口类型。动态代理实际上是 JVM 在运行时动态创建类的过程,并没有什么神秘的黑魔法技术。将上述动态代理的代码改写为一个静态实现类大致如下示例:

public class JdkCalculatorDynamicProxy implements JdkCalculator {      private InvocationHandler handler;      public JdkCalculatorDynamicProxy(InvocationHandler handler) {         this.handler = handler;     }      public void add(Integer num1,整数 num2){handler.invoke(this, JdkCalculator.class.getMethod("add", Integer.class, Integer.class), new Object[] um1, num2});本质上,JVM 自动为我们生成了一个上述类的字节码(无需源代码,直接生成)。3.2、除了JDK可以实现动态创建代理对象之外,还有一个著名的第三方框架叫做cglib。cglib可以在运行时动态生成子类对象,从而实现对目标对象功能的扩展。3.2、除了jdk能够实现动态创建代理对象外,还有一个非常有名的第三方框架:cglib,它可以在内存中动态生成一个子类对象,从而实现对目标对象功能的扩展。\ncglib的特点如下:\n1. cglib不仅可以代理接口,还可以代理类,而JDK的动态代理只能代理接口。\n2. cglib是一个强大的高性能的代码生成包,被许多AOP框架广泛使用,例如我们所熟知的Spring AOP,cglib为它们提供方法的拦截。CGLIB包的基础是利用一个小巧而高效的字节码处理框架ASM来进行字节码转换和生成新的类,速度非常快。在使用 cglib 之前,我们需要添加相应的依赖包。如果项目已经引入了 spring-core 的 jar 包,就不需要单独引入 cglib 了,因为 spring 中已经包含了 cglib。
<dependency>     <groupId>cglib</groupId>     <artifactId>cglib</artifactId>     <version>3.2.5</version> </dependency> 

 

接下来,我们将继续以两数相加为例,详细介绍具体的步骤!在这个地方呢,我们会只写中文而不是其他语言。 num2) } 

目标

public class CglibCalculatorImpl implements CglibCalculator {     @Override    public Integer add(Integer num1,public int add(int num1, int num2) {\n    int result = num1 + num2;\n    return result;\
\npublic class CglibProxyFactory implements MethodInterceptor {\n    /** 维护一个目标对象 **/\n    private Object target;\n    \n    public CglibProxyFactory(Object target) {\n        this.target = target;\n    }\n    \n    /** 为目标对象生成代理对象 **/\n    public Object getProxyInstance() {\n        //工具类\n        Enhancer en = new Enhancer();\n        //设置父类\n        en.setSuperclass(target.getClass());\n        //设置回调函数\n        en.setCallback(this);\n        //创建子类对象代理\n        return en.create();\n    }\n    \n    @Override\n    public Object intercept(Object o, Method method, Object[] objects, \n                            MethodProxy methodProxy) throws Throwable {\n        // TODO Auto-generated method stub\n        return method.invoke(target, objects);\n    }\
方法method(Object[] args, MethodProxy methodProxy) throws Throwable {\nSystem.out.println("在调用方法之前,可以添加其他功能...");\n// 执行目标对象方法\nObject returnValue = method.invoke(target,	args);\
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来代理目标对象,我们可以获取代理的实例对象。我们可以通过CglibProxyFactory类的getProxyInstance方法来创建代理实例。下面的代码展示了如何获取代理对象的类并执行代理方法。\n```java\nCglibCalculator proxyClassObj = (CglibCalculator) new CglibProxyFactory(target).getProxyInstance();\nSystem.out.println(proxyClassObj.getClass());\n// 执行代理方法\nInteger result = proxyClassObj.add(1, 2);\n```
2);\nSystem.out.println("The result of addition:" + result);\n输出结果:\nclass com.example.java.proxy.cglib1.CglibCalculatorImpl\nclass com.example.java.proxy.cglib1.CglibCalculatorImpl$$EnhancerByCGLIB$$3ceadfe4\n方法调用前,可以添加其他功能...\n方法调用后,可以增加其他的功能... 结果相加为:3 假设要把 cglib 生成的代理类改成静态实现类,大概如下:\npublic class CglibCalculatorImplByCGLIB extends CglibCalculatorImpl implements Factory {\n    private static final MethodInterceptor methodInterceptor;\n    private static final Method method;\n    public final Integer add(Integer var1, 在 变量2) { 返回 methodInterceptor.intercept(this, method, new Object[]{var1, var2}, methodProxy);     }      //... } 

在这里,拦截思路与 JDK 类似,都是通过一个接口方法进行拦截处理!在前面的内容中,我们提到了,cglib 不仅可以代理接口,还可以代理类。接下来我们来尝试代理一个类。public class CglibCalculatorClass {\n /**\n * 计算两个数的和\n * @param num1\n * @param num2\n * @return\n */\n public Integer add(Integer num1,public int add(int num1, int num2) {\n int result = num1 + num2;\n return result;\ \npublic class TestCglibProxyClass {\n public static void main(String[] args) {\n //目标对象\n CglibCalculatorClass target = new CglibCalculatorClass();\n System.out.println(target.getClass());\n //代理对象\n CglibCalculatorClass proxyClassObj = (CglibCalculatorClass) new CglibProxyFactory(target).getProxyInstance();\n System.out.println(proxyClassObj.getClass());\n //执行代理方法\n int result = proxyClassObj.add(1, 2);\n System.out.println("result: " + result);\n }\ 2);\nSystem.out.println("结果相加:" + result);\ }}\n输出结果\n类 com.example.java.proxy.cglib1.CglibCalculatorClass\n类 com.example.java.proxy.cglib1.CglibCalculatorClass$$EnhancerByCGLIB$$e68ff36c\n方法调用前,可以增加其他功能... 方法调用之后,能够附加其他功能... 两数相加的结果为3 

四、静态织入

在前述内容中,我们介绍的代理方式是通过在代码运行时动态生成class文件来实现动态代理的目的。动态代理的技术目的在于解决静态代理模式中当目标接口发生扩展时,代理类也需要相应变动的问题。这样可以避免工作变得繁琐和复杂。 基本上,动态代理的目的是回到问题的本质。遵循问题的核心,动态代理技术的主要目的实际上是为了解决静态代理模式中的一个问题,即当目标接口发生扩展时,代理类也需要相应地进行更改,以避免带来繁琐和复杂的工作负担。在Java生态系统中,还存在着一个非常知名的第三方代理框架,即AspectJ。AspectJ可以通过特定的编译器,在将目标类编译成class字节码时,同时在方法周围添加业务逻辑,从而实现静态代理的效果。

使用AspectJ进行方法插入时,一般有四种方式:

方法调用前拦截 方法调用后拦截 方法调用返回后拦截 方法抛出异常后拦截

操作起来也很简单,首先需要在项目中添加AspectJ编译器插件。<插件>\n org.codehaus.mojo\n aspectj-maven-plugin\n 1.5\n \n \n \n compile\n test-compile\n \n \n \n \n 1.6\n 1.6\n UTF-8\n 1.6\n true\n true\n \n然后,请编写一个可进行代理的方法。@RequestMapping("/hello")\npublic String hello(String name) {\n String result = "Hello, World";\n System.out.println(result);\n return result;\ \n编写代理配置类\n@Aspect\npublic class ControllerAspect {\n /**\n * 定义切入点\n */\n @Pointcut("execution(* com.example.demo.web..*.*(..))")\n public void methodAspect(){}\n /**\n * 方法调用前拦截\n */\n @Before("   @Before(&qu在“methodAspect()”方法中,我们定义了一系列的拦截器:\n1. 在目标方法执行之前,before()方法将输出"代理->调用方法执行之前...";\n2. 在目标方法执行之后,after()方法将输出"代理->调用方法执行之后...";\n3. 在目标方法正常返回后,afterReturning()方法将输出"代理->调用方法结束之后...";\n4. 如果目标方法抛出异常,afterThrowing()方法将输出"代理->调用方法异常..."。\n这样编译后, 你好,这个方法会变成这样。@RequestMapping("/hello")\npublic String hello(Integer name) throws SQLException {\n JoinPoint var2 = Factory.makeJP(ajc$tjp_0, this, this, name);在这段代码中,我们看到了一些变量和尝试块的使用。 首先,我们有一个名为var7的对象和一个尝试块。在尝试块中,我们看到了另一个对象var5和另一个尝试块。在内部尝试块中,我们看到了调用before的内容并且有一个String类型的result,输出了"Hello World"。然后将result赋值给var5。在catch块中调用了after,然后抛出了错误。在另一个catch块中,调用了抛出异常的内容并且抛出了该异常。最后调用了return并将var7转换成String返回。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编译器过于复杂,使用动态代理更加方便省事!

六、Java代理模式及其作用

1、Java中的三种代理模式包括静态代理、动态代理和cglib代理

 

2、Java动态代理的用途是什么?
代表未知数, 表示一个数, 代表正数。

招聘面试官问:动态代理是什么?

星空体育平台

星空体育官网

招聘面试官问:动态代理是什么?

星空体育APP

招聘面试官问:动态代理是什么?


星空体育入口 星空体育官方 星空体育下载