JDK动态代理

145 阅读6分钟

引入

概念

JDK动态代理可以理解成jdk帮我们创建一个代理对象,来帮我们动态管理真实对象中的构造函数,方法等信息.真实的对象只需要负责主要业务逻辑,其余的交给代理类.

生活中的例子

通常,由于身在异乡等各种因素,我们在租房子的时候,往往会通过中介(被委托方)寻找各种房源.一般不会直接接触到房东.

实战

我们先看下静态代理的例子.模拟一下代理的意思.然后再引入动态代理.

静态代理

1.定义一个接口

public interface EmployeeService {

    /**
     * 保存员工注册信息
     * @param username 用户名
     * @param password 密码
     */
    void save(String username,String password);
}

2.实现该接口,创建一个真实类,只做业务代码.

/**
 * 房东  -- 真实对象
 */
public class EmployeeServiceImpl implements EmployeeService{
    public void save(String username, String password) {
        System.out.println("保存用户信息,用户名:"+username+"密码"+password);
    }
}

3.创建一个代理类.

/**
 * 代理类  -- 中介
 */
public class EmployeeServiceProxy implements EmployeeService{

    //引入房东
    private EmployeeService target;

    public void setTarget(EmployeeService target) {
        this.target = target;
    }

    private MyTransactionManager tx;

    public void setTx(MyTransactionManager tx) {
        this.tx = tx;
    }

    public void save(String username, String password) {
        try {
            //System.out.println("开启事务");
            tx.begin();
            target.save(username, password);
            //System.out.println("提交事务");
            tx.commit();
        }catch (Exception e){
            //System.out.println("回滚事务");
            tx.rollback();
        }
    }
}

4.创建一个事务类

/**
 * 事务类
 */
public class MyTransactionManager{

    public void begin() {
        System.out.println("开启事务");
    }
    public void commit() {
        System.out.println("提交事务");
    }
    public void rollback() {
        System.out.println("回滚事务");
    }
}

5.配置文件

<bean id="tx" class="com.mycode.util.staticproxy.MyTransactionManager"/>


<!--代理对象引用真实对象-->
<bean id="employeeService" class="com.mycode.util.staticproxy.EmployeeServiceProxy">
    <property name="target">
        <bean class="com.mycode.util.staticproxy.EmployeeServiceImpl"/>
    </property>
    <property name="tx" ref="tx"/>
</bean>

6.最后测试,执行下述代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class staticProxyTest {
    //按照类型找 此时类型为:com.mycode.util.staticproxy.EmployeeService
    @Autowired
    private EmployeeService employeeService;
    @Test
    public void test(){
        employeeService.save("1","2");
    }
}

我们来理一下上面代码.

首先创建了一个接口并定义save()方法;之后用真实对象"EmployeeServiceImpl"实现该接口,并在该真实对象中只实现业务处理,其余不做处理;接着定义了一个代理类,这个代理类不仅去调用真实对象的业务代码(类似与中介帮你推荐房子,是主要工作),而且还简单模拟了一下事务操作(这里可以想象成中介帮你推荐房子前后的准备工作与结束工作,以及你拒绝他房源之后的工作).

之后在配置文件里配置代理类,并注入事务管理和真实对象,交给Spring容器管理.

在测试类中,注入EmployeeService接口,我们知道@Autowired注解会按照类型去寻找,如果存在多个,再按照注入的名字去查找,这里将EmployeeServiceImpl真实对象藏在了代理对象EmployeeServiceProxy中,所以无论怎么找,只会找到代理类.执行测试代码:

开启事务
保存用户信息,用户名:1密码2
提交事务

小结:通过静态代理的例子,我们大致了解了何为代理,真实对象和代理对象都实现了同一个接口,真实对象只处理业务代码,而代理对象在处理真实对象业务逻辑之外,也支持事务等处理.但是静态代理劣势也很明显,就是后面如果我们要想拥有班级的代理,房贷的代理,车贷的代理的话,我们要创建无数个代理对象,很是不妥.

JDK动态代理

首先我们看一下JDK动态代理中比较重要的方法.第一个参数为类加载器,第二个参数为真实对象实现的接口,第三个为需要代理对象做的事情.这个方法就是帮我们创建代理对象并返回代理对象.

Proxy.newProxyInstance(
          ClassLoader loader,
          Class<?>[] interfaces,
          InvocationHandler h
);

看一下第三个参数的源码.第一个参数为代理类对象,第二个参数为真实对象的方法,第三个为第二个参数传入的参数数组.

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

1.定义一个(班级)接口

public interface ClassRoom {

    /**
     * 班级中,可以吩咐学生去做某件事情
     * @param studentName 学生名
     * @param doSomething 具体某件事情
     */
    void doWork(String studentName,String doSomething);
}

2.写一个真实对象(班主任)并实现上述接口.

/**
 * 班主任 -- 真实对象
 * 只管主业务
 */
public class ClassRoomOwner implements ClassRoom{

    public void doWork(String studentName, String doSomething) {
        //只处理主业务
        System.out.println("让"+studentName+"去"+doSomething);
    }
}

3.事务代码我们继续沿用静态代理中的MyTransactionManager类.

我们一步一步来看.在测试类中调用java.lang.reflect.Proxy.newProxyInstance()方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationDynamic.xml")
public class dynamicProxyTest {
    @Autowired
    private ClassRoomInvocationHandler classRoomInvocationHandler;
    @Test
    public void test(){
        ClassRoom classRoom =(ClassRoom) Proxy.newProxyInstance(
                loader,
          interfaces,
          h
        );
        classRoom.doWork("xx","扫地");
    }
}

我们先填一下第三个参数,InvocationHandler为接口,这里我们写一个实现该接口的处理类.

public class ClassRoomInvocationHandler implements InvocationHandler{

    //事务
    private MyTransactionManager tx;

    public void setTx(MyTransactionManager tx) {
        this.tx = tx;
    }

    //真实对象
    private Object object;

    public void setObject(Object object) {
        this.object = object;
    }

    public Object getObject() {
        return object;
    }

    /**
     * 返回一个真实对象
     * @param proxy 真实对象的代理
     * @param method 创建真实对象的method
     * @param args method传参需要的参数
     * @return 真实对象
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //事务处理
        Object returnObj=null;
        try {
            tx.begin();
            returnObj = method.invoke(object, args);
            tx.commit();
        }catch (Exception e){
            e.printStackTrace();
            e.getMessage();
            tx.rollback();
        }
        //返回真实对象
        return returnObj;
    }
}

我们再填一下第二个参数,参数为真实对象实现的接口,第一个为类加载器,加载我们业务代码的是AppClassLoader.这两个参数可以直接从真实对象的Class文件中取.所以最终部分测试代码如下:

@Autowired
private ClassRoomInvocationHandler classRoomInvocationHandler;
@Test
public void test(){
    ClassRoom classRoom =(ClassRoom) Proxy.newProxyInstance(
            classRoomInvocationHandler.getObject().getClass().getClassLoader(),
            classRoomInvocationHandler.getObject().getClass().getInterfaces(),
            classRoomInvocationHandler
    );
    classRoom.doWork("xx","扫地");
}

我们最终创建了一个和真实类实现同一个接口的代理类,这里之所以能强转,就是因为代理类和真实类实现的是同一个接口,用接口类强转也体现了java的多态.执行之后我们发现,代理类真的帮我们在做了事情.

分配任务
让xx去扫地
任务进行

这里可能就有人问了,代理类是怎么创建的啊,之后调doWork()方法,代理类又是怎么去调用的,别急,这是咱们下面要说的.

实现原理

我们先看下Proxy.newProxyInstance()的源码


public class Proxy implements java.io.Serializable {

private static final Class<?>[] constructorParams =
    { InvocationHandler.class };
    
protected InvocationHandler h;

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

    /*
     * 查找并生成代理类Class对象
     */
    Class<?> cl = getProxyClass0(loader, intfs);

         /*
         * 创建代理类Class的有参构造器,参数为InvocationHandler类型
         */
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        /*
         * 通过有参构造器创建代理类对象并返回
         */
        return cons.newInstance(new Object[]{h});
        //省略
  
}

为了方便理解,我这边把具体生成的代理类给贴出来,先执行下述代码:

public static void main(String[] args) {
    try {
        generateClassFile(ClassRoom.class,"ClassRoomProxy");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * 将代理类对象以class形式输出到本地磁盘
 * @param targetClass 代理类实现的接口
 * @param proxyName 代理对象文件前缀
 * @throws Exception
 */
 private static void generateClassFile(Class<?> targetClass,String proxyName) throws Exception{
    byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, new Class[]{targetClass});
    String path = targetClass.getResource(".").getPath();
    System.out.println(path);
    FileOutputStream fileOutputStream = new FileOutputStream(path + proxyName + ".class");
    fileOutputStream.write(classFile);
    fileOutputStream.close();
}

得到的代理类class文件如下:

public final class ClassRoomProxy extends Proxy implements ClassRoom {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public ClassRoomProxy(InvocationHandler var1) {
        super(var1);
    }
static {
        m3 = Class.forName("com.mycode.util.dynamicproxy.jdkapi.ClassRoom")
            .getMethod("doWork", 
            Class.forName("java.lang.String"),
            Class.forName("java.lang.String"));

    }
    //省略

    public final void doWork(String var1, String var2) {
        try {
            //super.h是拿取父类的h变量,这里其实是ClassRoomInvocationHandler对象
            //然后调用其invoke方法
            //this是该代理对象,m3就是ClassRoom接口定义的dowork方法,最后一个传参是dowork方法的传参
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

//省略
    
}

代理类型继承了Proxy,并也实现了ClassRoom接口.

这个文件要跟着测试类中的classRoom.doWork("xx","扫地");来聊.

虽然我们强转了类型,但其还是个代理类,其实是代理类调用doWork("xx","扫地"),重要注释都已经写在了代理类上.