详解JAVA *

作者:奉强的个人博客 时间:2023-11-24 22:52:04 

文档更新说明

2018年09月24日 v1.0 初稿

代理在生活中很常见,比如说婚介网站,其实就是找对象的代理;还有社保代理、人事代理;还有找黄牛抢票,其实也是一种代理;而这些代理,在JAVA中也是有对应实现的。

1、为什么要 *

* 的作用其实就是在不修改原代码的前提下,对已有的方法进行增强。
关键点:

不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强

2、举个栗子

我们用一个很简单的例子来说明:Hello类,有一个introduction方法。

现在我们的需求就是不修改Hello类的introduction方法,在introduction之前先sayHello,在introduction之后再sayGoodBye

3、实现方式

JAVA中,实现 * 有两种方式,一种是JDK提供的,一种是第三方库CgLib提供的。特点如下:

JDK * :被代理的目标类需要实现接口
CgLib方式:可以对任意类实现 *

3.1、JDK *

JDK * 需要实现接口,然后通过对接口方法的增强来实现 *

所以要使用JDK * 的话,我们首先要创建一个接口,并且被代理的方法要在这个接口里面

3.1.1、创建一个接口

我们创建一个接口如下:

Personal.java


public interface Personal {
/**
  * 被代理的方法
  */
 void introduction();
}

3.1.2、实现接口

创建接口实现类,并且完成introduction方法

PersonalImpl.java


public class PersonalImpl implements Personal {
 @Override
 public void introduction() {
   System.out.println("我是程序员!");
 }
}

3.1.3、创建代理类

JDK代理的关键就是这个代理类了,需要实现InvocationHandler

在代理类中,所有方法的调用都好分发到invoke方法中。我们在invoke方法完成对方法的增强即可

JDKProxyFactory.java


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
  * 目标对象
  */
 private T target;
/**
  * 构造函数传入目标对象
  *
  * @param target 目标对象
  */
 public JDKProxyFactory(T target) {
   this.target = target;
 }
/**
  * 获取代理对象
  *
  * @return 获取代理
  */
 public T getProxy() {
   return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
 }
@Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   // 对方法增强
   System.out.println("大家好!");
   // 调用原方法
   Object result = method.invoke(target, args);
   // 方法增强
   System.out.println("再见!");
   return result;
 }
}

就这样,JDK * 的代码就完成了,接下来写一份测试代码

3.1.4、编写测试代码

为了方便测试,我们编写一个test方法

同时为了查看class文件,还添加了一个generatorClass方法,这个方法可以将 * 生成的.class输出到文件

ProxyTest.java


import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
 public void testJdkProxy() {
   // 生成目标对象
   Personal personal = new PersonalImpl();
   // 获取代理对象
   JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
   Personal proxy = proxyFactory.getProxy();
// 将proxy的class字节码输出到文件
   generatorClass(proxy);
// 调用代理对象
   proxy.introduction();
 }
/**
  * 将对象的class字节码输出到文件
  *
  * @param proxy 代理类
  */
 private void generatorClass(Object proxy) {
   FileOutputStream out = null;
   try {
     byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
     out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
     out.write(generateProxyClass);
   } catch (Exception e) {
     e.printStackTrace();
   } finally {
     if (out != null) {
       try {
         out.close();
       } catch (IOException e) {
         // TODO Auto-generated catch block
       }
     }
   }
}
}

3.1.5、查看运行结果

可以看到,运行test方法之后,控制台打印出如下:

大家好!
我是程序员!
再见!

我们在introduction方法前和后都成功增加了功能,让这个程序员的自我介绍瞬间变得更加有礼貌了。

3.1.6、探探 * 的秘密

* 的代码并不多,那么JDK底层是怎么帮我们实现的呢?

在测试的时候我们将动态生成的代理类的class字节码输出到了文件,我们可以反编译看看。

结果有点长,就不全部贴出来了,不过我们可以看到,里面有一个introduction方法如下:


/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
 Objects.requireNonNull(h);
 this.h = h;
}
public final void introduction() throws {
   try {
     super.h.invoke(this, m3, (Object[])null);
   } catch (RuntimeException | Error var2) {
     throw var2;
   } catch (Throwable var3) {
     throw new UndeclaredThrowableException(var3);
   }
 }

原来,生成的代理对象里面,引用了我们的InvocationHandler,然后在将introduction方法里面调用了InvocationHandlerintroduction,而InvocationHandler是由我们编写的代理类,在这里我们增加了sayHellosayGoodBye操作,然后还调用了原对象的introduction方法,就这样完成了 * 。

3.2、CgLib *

CgLib动态

3.2.1、创建被代理对象

由于CgLib不需要实现接口,所以我们不需要创建接口文件了(当然,你要有接口也没有问题)

直接创建目标类,实现introduction方法

PersonalImpl.java


public class PersonalImpl {
 public void introduction() {
   System.out.println("我是程序员!");
 }
}

3.2.2、创建代理类

同样,我们也需要创建代理类,并且在这里实现增强的逻辑,这次我们不是实现InvocationHandler接口了,而是实现CgLib提供的接口MethodInterceptor,都是类似的,MethodInterceptor中,全部方法调用都会交给intercept处理,我们在intercept添加处理逻辑即可。

CgLibProxyFactory.java


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CgLibProxyFactory<T> implements MethodInterceptor {
/**
  * 获取代理对象
  *
  * @param tClass 被代理的目标对象
  * @return 代理对象
  */
 public T getProxyByCgLib(Class<T> tClass) {
   // 创建增强器
   Enhancer enhancer = new Enhancer();
// 设置需要增强的类的类对象
   enhancer.setSuperclass(tClass);
// 设置回调函数
   enhancer.setCallback(this);
// 获取增强之后的代理对象
   return (T) enhancer.create();
 }
/**
  * 代理类方法调用回调
  *
  * @param obj 这是代理对象,也就是[目标对象]的子类
  * @param method [目标对象]的方法
  * @param args 参数
  * @param proxy 代理对象的方法
  * @return 返回结果,返回给调用者
  * @throws Throwable
  */
 @Override
 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("大家好!");
Object result = proxy.invokeSuper(obj, args);
System.out.println("再见!");
return result;
 }
}

3.2.3、编写测试代码

在刚才的测试方法中,我们添加一个cglib的测试方法:


@Test
public void testCgLibProxy() {
 // 生成被代理的目标对象
 PersonalImpl personal = new PersonalImpl();
// 获取代理类
 CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
 PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// 将proxy的class字节码输出到文件
 generatorClass(proxy);
// 调用代理对象
 proxy.introduction();
}

3.2.4、查看运行结果

运行测试用例,可以看到跟JDK的实现一样的效果

大家好!
我是程序员!
再见!

3.2.5、探探 * 的秘密

JDK的测试一样,我们也来看看生成的class文件


public final void introduction() throws {
 try {
   super.h.invoke(this, m7, (Object[])null);
 } catch (RuntimeException | Error var2) {
   throw var2;
 } catch (Throwable var3) {
   throw new UndeclaredThrowableException(var3);
 }
}

可以发现,与JDK的 * 并没有区别。

4、如何选择

既然有两种实现方式,那么到底应该怎么选择呢?

就两个原则:

目标类有接口实现的,JDKCgLib都可以选择,你开心就好
目标类没有实现任何接口,那只能用CgLib

5、后记

其实在第一次看到 * 的时候,我就想不明白,我们都把目标类new出来了,为什么还要将目标类丢给代理类呢?为什么不直接调用目标类对应的方法呢?

后来才发现,原来我没搞清楚 * 的使用场景,场景很清晰,就是:

不修改原来已有的代码(满足设计模式的要求)
对已有方法进行增强
关键是增强,代理类里面我们是可以添加很多处理逻辑的,从而实现增强效果。就像黄牛抢票比我们厉害些一样。

以上所述是小编给大家介绍的JAVA * 详解整合,希望对大家有所帮助。

来源:https://www.fengqiangboy.com/15377761043880.html

标签:JAVA, ,
0
投稿

猜你喜欢

  • Spring bean的实例化和IOC依赖注入详解

    2023-11-23 23:57:15
  • Java下载文件时文件名乱码问题解决办法

    2023-08-23 17:37:03
  • ContentProvider启动流程示例解析

    2023-07-31 03:57:34
  • Java经典面试题最全汇总208道(四)

    2023-11-08 23:59:26
  • Android 程序执行Linux命令的解决方法及注意事项

    2023-07-13 00:14:58
  • Android基于TextView实现跑马灯效果

    2023-07-25 06:32:45
  • OpenCV实现直线检测并消除

    2023-07-12 20:44:36
  • java异常与错误处理基本知识

    2023-11-25 10:44:59
  • 解决grails服务端口冲突的办法(grails修改端口号)

    2023-09-12 01:00:03
  • springboot对接支付宝支付接口(详细开发步骤总结)

    2023-11-10 23:07:35
  • Java经典面试题汇总--多线程

    2023-07-13 01:17:48
  • spring boot整合log4j2及MQ消费处理系统日志示例

    2023-06-17 17:47:54
  • springboot常用注释的讲解

    2023-11-03 02:53:15
  • Java多线程编程中使用Condition类操作锁的方法详解

    2023-10-19 13:30:55
  • C# winfrom 模拟ftp文件管理实现代码

    2023-07-15 16:29:48
  • SpringBoot如何进行对象复制的实践

    2023-11-23 03:40:19
  • springboot post接口接受json时,转换为对象时,属性都为null的解决

    2023-06-17 15:24:23
  • C++ Boost MPI接口详细讲解

    2023-11-02 13:35:36
  • 如何通过SpringBoot实现商城秒杀系统

    2023-11-24 23:02:33
  • SparkSQl简介及运行原理

    2023-09-17 05:18:19
  • asp之家 软件编程 m.aspxhome.com