Java两种方式实现 *

作者:知优码 时间:2022-10-15 22:12:23 

一、JDK *

Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只需传入目标接口、目标接口的类加载器以及 InvocationHandler 便可为目标接口生成代理类及代理对象。我们称这个Java技术为: *


@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                  Class<?>[] interfaces,
                  InvocationHandler h)
 throws IllegalArgumentException
{
//...
}

在 Java 中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口,因此 interfaces 必须是一个接口。

在 * 技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的 InvocationHandler 的 invoke 方法(这相当于 invoke 方法拦截到了代理对象的方法调用)。

因此 JDK * 的整体流程为:

  1. 实现 InvocationHandler,用来处理对象拦截后的逻辑。(该对象必须是接口,或者父类是接口)

  2. 使用 Proxy.newProxyInstance 产生代理对象。

以下是一个用 JDK 动态代码实现 AOP 的具体例子:

1.目标(Target)类


public interface UserService {
 void eat();
}

public class UserServiceImpl implements UserService {
 @Override
 public void eat() {
   System.out.println("吃东西");
 }
}

2.切面(Aspect)类


public class MyAspect {
 /**
  * 前置通知
  */
 public void before() {
   System.out.print("先洗手再");
 }
}

3. 织入(Weaving)过程


/**
* 产生代理对象的工厂类
*/
public class MyFactoryBean {

private MyFactoryBean() {
 }

public static UserService getInstance() {
   // target : 目标类
   final UserService userService = new UserServiceImpl();
   // Aspect : 切面类
   final MyAspect myAspect = new MyAspect();
   // Weaving : 织入,也就是产生代理的过程
   UserService proxyInstance = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),
       new Class[]{UserService.class}, (Object proxy, Method method, Object[] args) -> {
         // 模拟切点 - pointcut
         if ("eat".equals(method.getName())) {
           myAspect.before();
         }
         return method.invoke(userService, args);
       });
   return proxyInstance;
 }
}

 public static void main(String[] args) {
   UserService userService = MyFactoryBean.getInstance();
   // 先洗手再吃东西
   userService.eat();
 }

想想看,这其实跟我们平常使用的 AOP 已经很相似了,Spring 里面定义了前置通知(@Before)、异常通知(@AfterThrowing)等等,Spring 只是换成了甄别这些注解来选择什么时候调用通知方法,另外,Spring 还通过切点表达式来选择目标类和切入点。

二、CGLIB *

CGLIB * 需要引入第三方的库,它通过修改代理对象生成子类的方式来实现调用拦截,代理对象不需要实现接口,但是代理类不能是 final,代理的方法也不能是 final。


/**
* 产生代理对象的工厂类
*/
public class MyFactoryBean {

private MyFactoryBean() {
 }

public static UserService getInstance() {
   // target : 目标类
   final UserService userService = new UserServiceImpl();
   // Aspect : 切面类
   final MyAspect myAspect = new MyAspect();
   // Weaving : 织入,也就是产生代理的过程
   Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(userService.getClass());
   enhancer.setUseCache(false);
   enhancer.setCallback(new MethodInterceptor() {
     @Override
     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
       // 模拟 pointcut-切点
       if ("eat".equals(method.getName())) {
         myAspect.before();
       }
       return methodProxy.invokeSuper(o, objects);
     }
   });
   return (UserService) enhancer.create();
 }

public static void main(String[] args) {
   UserService proxyInstance = MyFactoryBean.getInstance();
   // 先洗手再吃东西
   proxyInstance.eat();
 }
}

三、总结

在 JDK 中实现 * 时,要求代理类必须是接口或继承接口的类,因为 JDK 最后生成的 proxy class 其实就是实现了代理类所代理的接口并且继承了 java 中的 Proxy 类(继承 Proxy 类是为了判断该类是否为代理类),通过反射找到接口的方法,调用 InvocationHandler的invoke 方法实现拦截。

CGLIB 字节码增强是JDK * 的一个很好的补充, CGLIB 中最后生成的 proxy class 是一个继承代理类所代理的 class,通过重写被代理类中的非 final 的方法实现代理。

总结为:

  • JDK * :代理类必须是接口或继承接口的类。

  • CGLIB 字节码增强: 代理类不能是 final,代理的方法也不能是 final(继承限制) 。

关于在 Spring 的 AOP 中采用何种代理手段,我们不强加限制的话,会根据类是否有接口来区别对待:

  1. 当一个类有接口的时候,就会选用 JDK 的 * 。

  2. 当一个类没有实现接口的时候,就会选用 CGLIB 代理的方式。

来源:https://www.javaidea.cn/topic/1126.html

标签:Java,动态,代理
0
投稿

猜你喜欢

  • WPF+DiffPlex实现文本比对工具

    2022-04-20 21:32:07
  • 一篇文章带你搞定JAVA反射

    2023-09-18 02:27:51
  • Android通过Movie展示Gif格式图片

    2023-06-16 19:29:38
  • C#获取汉字字符串拼音首字母的方法

    2022-09-06 14:01:12
  • 浅试仿 mapstruct实现微服务编排框架详解

    2022-07-12 13:20:44
  • android实现密码框右侧显示小眼睛

    2023-01-26 19:54:59
  • 解析Java继承中方法的覆盖和重载

    2021-09-02 02:02:32
  • Android Wear计时器开发

    2023-04-10 01:22:21
  • C#图像处理之图像平移的方法

    2021-12-16 08:38:37
  • Java 守护线程_动力节点Java学院整理

    2023-11-28 07:51:14
  • java多线程之CyclicBarrier的使用方法

    2023-11-04 21:52:54
  • Java实现验证码的产生和验证

    2022-10-30 22:41:39
  • Java 实现将List平均分成若干个集合

    2023-10-16 21:36:59
  • winform实现限制及解除鼠标移动范围的方法

    2023-07-17 22:11:51
  • 教你如何使用Java实现WebSocket

    2021-08-22 20:39:02
  • 判断List和Map是否相等并合并List中相同的Map

    2022-12-29 02:11:10
  • C#中Razor模板引擎简单使用

    2022-01-21 10:04:13
  • spring cloud gateway请求跨域问题解决方案

    2021-11-05 11:19:25
  • C#之IP地址和整数互转的小例子

    2023-11-21 05:49:19
  • IDEA编译乱码Build Output提示信息乱码

    2023-08-07 12:14:35
  • asp之家 软件编程 m.aspxhome.com