美文网首页
Java 动态代理

Java 动态代理

作者: 索伦x | 来源:发表于2019-06-17 00:05 被阅读0次

Java 动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法都可以)。
如果你在没有掌握动态代理的情况去学习 Spring,那么你基本上没办法真正理解 Spring。
Java 的动态代理有 2 种实现方式,JDK 动态代理及Cglib。

JDK 动态代理

1、我们要有一个接口,还要有一个接口的实现类,而这个实现类呢就是我们要代理的对象

接口:

public interface UserDao { 
      public void addUser(User user); 
}

接口的实现类(即要代理的对象):

package org.dynamicproxy.test;

public class UserDaoMysqlImpl implements UserDao {

    public void addUser(User user) {
        // TODO Auto-generated method stub
        System.out.println("connect to MySQL DataBase...");
        System.out.println("id为"+user.getId()+"的用户信息成功添加到数据库表中...");
    }
}

顺便说下,所谓代理呢也就是在调用实现类的方法时,可以在方法执行前后做额外的工作,这个就是代理。

2、我们要自己写一个在要代理类的方法执行时,能够做额外工作的类,而这个类必须继承InvocationHandler接口

处理类(即能够做额外工作的类):

package org.dynamicproxy.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LogHandler implements InvocationHandler {
    // 持有被代理对象的引用(此引用可以有外部灵活制定的)
    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("开始记录日志,添加用户方法开始执行...");
        method.invoke(target, args);
        System.out.println("开始记录日志,添加用户方法执行结束...");
        return null;
    }
}

顺便说下为什么要继承InvocationHandler接口呢?

因为代理类的实例在调用实现类的方法的时候,不会调真正的实现类的这个方法, 而是转而调用这个类的invoke方法(继承时必须实现的方法),在这个方法中你可以调用真正的实现类的这个方法。

3、用代理类的实例去调用实现类的方法。

package org.dynamicproxy.test;

import java.lang.reflect.Proxy;

import org.junit.Test;

public class TestProxy{
 
    @Test
    public void testProxy(){
    UserDao userDAO = new UserDaoMysqlImpl();
    LogHandler li = new LogHandler(userDAO);  //创建一个Handerler对象
  
    UserDao userDAOProxy = (UserDao)Proxy.newProxyInstance(userDAO.getClass().getClassLoader(), userDAO.getClass().getInterfaces(), li);
    userDAOProxy.addUser(new User("001"));
   
}
}

newProxyInstance参数含义

  • 第一个参数:代理的类加载器,必须和被代理的对象是一个类加载器
  • 第二个参数含义:代理对象要实现的那些接口
  • 第三个参数:指派方法调用的调用处理程序

代理调用过程

  • 得到UserDaoMysqlImpl这个类的一个代理类userDAOProxy,同时为代理类绑定了一个处理类LogHandler li
  • 每次调用UserDaoMysqlImpl这个子类的addUser方法时,
  • 不是userDAO这个UserDaoMysqlImpl类的实例去调用,
  • 而是这个UserDaoMysqlImpl的代理类userDAOProxy去调用它自己的invoke方法,
  • 这个invoke方法里呢可以调用userDAO这个实例的addUser方法

所以说要实现java的动态代理,InvocationHandler接口和Proxy是必须要用到的。

Cglib 动态代理

CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。

来看示例,假设我们有一个没有实现任何接口的类

public class HelloConcrete {
    public String sayHello(String str) {
        return "HelloConcrete: " + str;
    }
}

因为没有实现接口该类无法使用JDK代理,通过CGLIB代理实现如下:

  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
  ...
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        logger.info("You said: " + Arrays.toString(args));
        return proxy.invokeSuper(obj, args);
    }
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());
 
HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

运行上述代码输出结果:

日志信息: You said: [I love you!]
HelloConcrete: I love you!

上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()equals()toString()等,但是getClass()wait()等方法不会,因为它是final方法,CGLIB无法代理。

如果对CGLIB代理之后的对象类型进行深挖,可以看到如下信息:

# HelloConcrete代理对象的类型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces: 
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

我们看到使用CGLIB代理之后的对象类型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,这是CGLIB动态生成的类型;父类是HelloConcrete,印证了CGLIB是通过继承实现代理;同时实现了net.sf.cglib.proxy.Factory接口,这个接口是CGLIB自己加入的,包含一些工具方法。

注意,既然是继承就不得不考虑final的问题。我们知道final类型不能有子类,所以CGLIB不能代理final类型,遇到这种情况会抛出类似如下异常:

java.lang.IllegalArgumentException: Cannot subclass  final class cglib.HelloConcrete

同样的,final方法是不能重载的,所以也不能通过CGLIB代理,遇到这种情况不会抛异常,而是会跳过final方法只代理其他方法。

如果你还对代理类cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52具体实现感兴趣,它大致长这个样子:

// CGLIB代理类具体实现
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
  extends HelloConcrete
  implements Factory
{
  ...
  private MethodInterceptor CGLIB$CALLBACK_0; // ~~
  ...
 
  public final String sayHello(String paramString)
  {
    ...
    MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
    if (tmp17_14 != null) {
      // 将请求转发给MethodInterceptor.intercept()方法。
      return (String)tmp17_14.intercept(this, 
              CGLIB$sayHello$0$Method, 
              new Object[] { paramString }, 
              CGLIB$sayHello$0$Proxy);
    }
    return super.sayHello(paramString);
  }
  ...
}

上述代码我们看到,当调用代理对象的sayHello()方法时,首先会尝试转发给MethodInterceptor.intercept()方法,如果没有MethodInterceptor就执行父类的sayHello()。这些逻辑没什么复杂之处,但是他们是在运行时动态产生的,无需我们手动编写。

相关文章

  • Java 动态代理

    java的动态代理机制详解 JDK动态代理详解 Java核心技术点之动态代理

  • JDK动态代理详解

    JDK动态代理详解 java动态代理类 Java动态代理类位于java.lang.reflect包下,一般主要涉及...

  • Java动态代理从入门到原理再到实战

    目录 前言 什么是动态代理,和静态代理有什么区别 Java动态代理的简单使用 Java动态代理的原理解读 动态代理...

  • java反射和动态代理

    java动态代理Proxy.newProxyInstance 详解java代理机制(静态代理、动态代理)以及使用场景

  • 保存java 动态代理生成的字节码文件

    保存java 动态代理生成的字节码文件 在Java中,常用的动态代理技术有JDK的动态代理和cglib动态代理,不...

  • java动态代理(JDK和cglib)(转载自http://ww

    java动态代理(JDK和cglib) JAVA的动态代理 代理模式 代理模式是常用的java设计模式,他的特征是...

  • java基础巩固笔记(4)-代理

    标签: java Contents java基础巩固笔记(4)-代理概念动态代理创建动态类动态代理的工作原理面向切...

  • java 动态代理

    动态代理动态代理可以让我们在运行时动态生成代理类,解耦程度更高。Java 动态代理的实现主要借助于 java.la...

  • java随笔(十一)

    java动态代理源码分析,总结。java动态代理实现步骤: 通过阅读源码发现,动态生成代理对象$Proxy0,该对...

  • Java基础:反射

    反射注解动态代理相关阅读 Java基础:类加载器 Java基础:反射 Java基础:注解 Java基础:动态代理 ...

网友评论

      本文标题:Java 动态代理

      本文链接:https://www.haomeiwen.com/subject/ntonfctx.html