ThoughtWorks欧亚创新工作室JavaEE 学习专题程序员

静态代理与动态代理

2017-11-17  本文已影响44人  小小蒜头

在学习Spring框架的时候,有一个重要的思想就是AOP(面向切面编程),AOP底层就是通过动态代理来实现的。

为什么需要代理?

代理

代理就是代替原来的角色去做中间那些繁琐的工作。打一比方,我现在想要学习,那么就要打开书包,准备好纸笔,再开始学习,等学完了我还得收拾书,把书塞回书包里并整理好,这是一个完整的学习的过程,但是我现在只想学习,那我就需要让妈妈帮我把书包打开,把纸笔准备好,我只管学习,等学完以后,妈妈再帮我把东西整理好。在这里,妈妈就代表了一个代理对象,要学习的人是我,我只管学习,这样效率才更高,至于其他的交给代理对象(妈妈)做就好了。

代理模式的结构如下:

结构

再联想我们最开始接触JDBC操作数据库的时候,业务层每一个方法,都需要 1.打开数据库连接,2.执行我们想要的操作,3.关闭数据库连接。 这样就使得业务层代码不够纯粹,我的功能只是查询用户数据,打开和关闭数据库连接关我毛事?我干嘛要去干这件事?这就是传统开发中存在的一个问题,现在通过代码演示一下:

用户服务层接口,只有一个save方法

interface UserService{
    public void saveUser();
}

用户服务层实现了save方法

class UserServiceImpl implements UserService{

    @Override
    public void saveUser() {
        System.out.println("1:打开数据库连接");
        System.out.println("2:保存用户信息");
        System.out.println("3:关闭数据库连接");
    }   
}

我们可以看到核心业务与辅助业务写在了一个方法中,不但业务冗余了不说,像开关数据库连接这样的公共操作也大量的重复,这时候就出现了代理模式的思想,我们可以使用代理模式的思想改写一下上面的代码:

代理类

class UserServiceProxy implements UserService{

    private UserService userService;
    
    public UserServiceProxy(UserService userService) {
        super();
        this.userService = userService;
    }

    public void open(){
        System.out.println("1:打开数据库连接");
    }
    public void close(){
        System.out.println("3:关闭数据库连接");
    }

    @Override
    public void saveUser() {
        this.open();
        userService.saveUser();
        this.close();
    }
}

测试代码打印结果,和上面没有使用代理的代码是一样的,但是通过修改可以清晰地看到,业务层代码变得只剩下最核心的保存用户信息的代码。通过代理模式,我们可以抽取出核心业务与辅助业务,但是问题随之而来了,我这里编写的UserServiceProxy是挺不错,可是它只能服务于UserService这个接口的对象,如果我有一千个业务,那岂不是要编写一千个代理类,其实这种代理模式就是静态代理,它的缺点很明显,静态代理只能服务于一种类型的对象,不利于业务的扩展,那能不能设计一个代理类可以服务于所有的业务对象呢?于是,动态代理就闪亮登场了。

如果要实现动态代理,那么你要编写的那个代理类就需要实现一个InvocationHandler接口,这个接口所在位置是java.lang.reflect.InvocationHandler。看到reflect就知道,动态代理肯是通过反射来实现的,这个接口中有一个方法:

//在代理实例上处理方法调用并返回结果。
Object  invoke(Object proxy, Method method, Object[] args)
    proxy - 表示需要代理的对象
    method - 表示要操作的方法
    args - method方法所需要传入的参数(可能没有,可能null,也可能多个)

如果要想让代理设计真正可用,还必须有一个代理类对象产生,java.lang.reflect.Proxy,Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。

在这个类下面,需要用到这样一个方法:

public static Object newProxyInstance(ClassLoader loader,
                                       Class<?>[] interfaces,
                                       InvocationHandler h)
                                  throws IllegalArgumentException)

该方法返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。方法中有三个参数:
   参数:
     loader - 定义代理类的类加载器
     interfaces - 代理类要实现的接口列表
     h - 指派方法调用的调用处理程序
   返回:
    一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口 

动态代理类

class ServiceProxy implements InvocationHandler {

    private Object target=null;//保存真实业务对象
    /**
     * 返回动态代理类的对象,这样用户才可以利用代理类对象去操作真实对象
     * @param obj  包含有真实业务实现的对象
     * @return   返回代理对象
     */
    public Object getProxy(Object obj) {
        this.target=obj;//保存真实业务对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object result=method.invoke(target, args);//通过反射调用真实业务对象的业务方法,并且返回
        return result;
    }
}

测试

public class TestProxy {

    public static void main(String[] args) {
            UserService service=(UserService) new ServiceProxy().getProxy(new UserServiceImpl());
            service.saveUser();
    }
}

// 打印结果:
// 1:打开数据库连接
// 2:保存用户信息
// 3:关闭数据库连接

其实结果中的打开数据库连接,和关闭数据库连接可以在ServiceProxy中加入两个方法,open()和close(),一个放到method.invoke()上面,一个放到下面。注意,我们现在编写的就是一个万能的动态代理类,没有和任何的业务层接口关联。

总结

动态代理和静态代理相比较,最大的好处就是接口中声明的所有的方法都被转移到一个集中的方法中去处理,就是invoke()方法。这样在接口中声明的方法比较多的情况下我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

动态代理只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法的返回值是被代理接口的一个实现类。

那么有没有可能我们可以不依赖接口呢?这时候就需要cglib实现动态代理了,感兴趣的可以自己去查阅。

上一篇 下一篇

猜你喜欢

热点阅读