Java动态代理之JDK代理

为什么要使用动态代理呢?因为静态代理需要为每一个被代理类都建立一个代理类,这样就会产生很多代理类,并且每当被代理类增减接口时,我们需要同步维护代理类。这样既冗余又增加了维护成本。所以为了避免产生很多代理类,就出现了动态代理。

JDK动态代理要求,被代理类必须要实现接口。JDK动态代理包括两部分内容,一部分是基础内容,另一部分是代理工厂类。基础内容是接口和实现类。

基础内容代码:

package com.zaoren.proxy;
/**
 * 接口
 * @author thinkpad
 *
 */
public interface IUserDao {
    void save();
}

 

package com.zaoren.proxy;

public class UserDao implements IUserDao {

    public void save() {
        System.out.println("------------保存user信息");
    }

}

 

代理工厂代码为:

package com.zaoren.proxy;

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

/**
 * 动态代理工厂,即JDK代理工厂
 * @author thinkpad
 *
 */
public class ProxyFactory {
    private IUserDao userDao;
    
    public ProxyFactory(IUserDao userDao) {
        this.userDao = userDao;
    }
    
    public Object getInstance() {
        return Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), 
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("--------->>>JDK代理调用目标前操作");
                        method.invoke(userDao, args);
                        System.out.println("--------->>>JDK代理调用目标后操作");
                        return null;
                    }
            
                }
        );
    }
}

 其中,我们使用Proxy类创建代理实例时传入了三个参数,分别为被代理类的classLoader、被代理类所实现的所有接口、invoke方法的具体实现。

 

测试代码为:

package com.zaoren.proxy;

public class MainClass {

    public static void main(String[] args) {
        
        IUserDao userDao = new UserDao();
        IUserDao jdkProxy = (IUserDao)new ProxyFactory(userDao).getInstance();
        jdkProxy.save();
    }

}

 

控制台输出结果为:

  由控制台输出可见,结果符合我们的预期。

 

  但是我重新审视代码时,发现我的代码并不能避免产生很多代理类,因为代理工厂类中的代理实例是一个具体的类型,那么如果我要代理其他类,这个代理工厂就不能用了,需要建立新的代理工厂才行。

  要知道,JDK动态代理是使用反射的机制来实现的,而反射机制的特点之一就是传入的参数可以不是具体的类型。所以我把代理工厂类中的被代理类设置为Object类型,属性名改为target,其他地方不变,就可以了,控制台的输出结果和之前的一样,但是现在我们可以用这个代理工厂代理其他的类了,而不需要建立新的代理工厂。

  修改后的代理工厂类代码为:

package com.zaoren.proxy;

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

/**
 * 动态代理工厂,即JDK代理工厂
 * @author thinkpad
 *
 */
public class ProxyFactory {
    private Object target;
    
    public ProxyFactory(Object userDao) {
        this.target = userDao;
    }
    
    public Object getInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("--------->>>JDK代理调用目标前操作");
                        method.invoke(target, args);
                        System.out.println("--------->>>JDK代理调用目标后操作");
                        return null;
                    }
            
                }
        );
    }
}

 

  然后,我又思考,在程序运行过程中,整个JDK的动态代理机制具体是如何实现的呢?分析了一遍后,我发现整个机制总的来说就是反射,机制的的具体实现其实就是临时创建一个代理类,这个代理类实现了基础实现类的接口,创建代理类需要的材料是我们通过代码提供的。我们提供的最主要的材料是扩展后的方法,即在用Proxy类来创建代理实例时,我们提供了invoke方法的具体实现。至于代理类具体是怎样一步一步的创建的,和代理类是怎样被调用的,我就不知道了。

 --------------------------------------

  通过上面的代理工厂类,我们可以代理不同的类。但是上面的代码是有局限的,因为代理不同的类时,甚至是同一个被代理类的不同的方法时,代理类所扩展的内容都是相同的。如果我们想对不同的被代理类、不同的被代理类的方法扩展不同的内容,又该怎样实现呢?

  问题的关键在于,我们要能够在invoke方法中区分不同的被代理类和被代理类的不同方法。那么应该怎样区分呢?我们可以通过反射的知识来获知当前的被代理的类是哪一个、被代理的方法是哪一个。

  调用Method对象的toString方法,可以得到当前被代理的方法的详细信息,包括方法的访问修辞符、返回值、所属类、方法名、参数等。有了这些信息,就能够区分不同的被代理类和不同的被代理方法了。

  为了实现,我有新建了一个接口和实现类,代码如下:

package com.zaoren.proxy;
/**
 * 订单接口
 * @author thinkpad
 *
 */
public interface IOrderDao {
    void save();
}

 

package com.zaoren.proxy;

public class OrderDao implements IOrderDao {

    public void save() {
        System.out.println("---------保存订单信息");
    }

}

 

  修改后的代理工厂类如下:

package com.zaoren.proxy;

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

/**
 * 动态代理工厂,即JDK代理工厂
 * @author thinkpad
 *
 */
public class ProxyFactory {
    private Object target;
    
    public ProxyFactory(Object userDao) {
        this.target = userDao;
    }
    
    public Object getInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("method = "+method.toString());
                        if(method.toString().contains("com.zaoren.proxy.IUserDao.save")){
                            System.out.println("--------->>>JDK代理调用目标前操作:记录日志");
                            method.invoke(target, args);
                            System.out.println("--------->>>JDK代理调用目标后操作:记录日志2");
                            
                        }else if(method.toString().contains("com.zaoren.proxy.IOrderDao.save")) {
                            System.out.println("--------->>>JDK代理调用目标前操作:其他操作");
                            method.invoke(target, args);
                            System.out.println("--------->>>JDK代理调用目标后操作:其他操作2");
                        }
                        return null;
                    }
            
                }
        );
    }
}

 

测试代码为:

package com.zaoren.proxy;

public class MainClass {

    public static void main(String[] args) {
        
        IUserDao userDao = new UserDao();
        IUserDao userDaoProxy = (IUserDao)new ProxyFactory(userDao).getInstance();
        userDaoProxy.save();
        
        IOrderDao orderDao = new OrderDao();
        IOrderDao orderDaoProxy = (IOrderDao)new ProxyFactory(orderDao).getInstance();
        orderDaoProxy.save();
    }

}

 

  控制台输出结果为:

可见,我们己经实现了区分的目的。

 

备注:不知道大家有没有发现,控制台输出的method的值是接口的方法名,而不是实现类的方法名。这个应该是java的多态机制,实际上执行的是实现类的方法,但是概念上是接口的方法。

posted on 2019-11-22 10:58  星辰划过指尖  阅读(190)  评论(0编辑  收藏  举报