黑马程序员技术交流社区

标题: 【广州校区】+【原创】静态代理、动态代理的简单分析 [打印本页]

作者: Mylo    时间: 2018-10-16 10:44
标题: 【广州校区】+【原创】静态代理、动态代理的简单分析
本帖最后由 Mylo 于 2018-10-18 11:34 编辑

在讲动态代理之前,我们先看一个小需求:
有如下的一段代码(简单模拟添加数据,删除数据):
public class UserDao {    public void add(){

        System.out.println("添加用户");
    }

    public void delete(){
        System.out.println("删除用户");
    }
}

  我们都知道,操作数据库的话,一般都是要开启事务的,所以,我们现在的小需求是:在不改变这个类的代码的情况下,给他添加开启事务,提交事务/回滚事务的操作,
那么这个时候我们应该怎么办呢?
分析:既然是不改变原来的代码,我们只能添加一个中间类,通过中间类的方法去调用这个UserDao的方法,然后 ,在中间类的方法里面开启事务、提交事务/回滚事务即可。到时候我们直接调用的是中间类就OK了
所以,我们就创建一个中间类
public class UserDaoUtils {    private UserDao ud = new UserDao();
    public void add(){
        System.out.println("开启事务");
        ud.add();
        System.out.println("提交事务");
    }
}


这样就可以解决我们想要解决的问题了。这个时候,我们把中间类称之为代理类,这种方式我们称之为静态代理。不过,这个时候,我们就能发现,代理类里面的方法跟我们想代理的UserDao的方法还不是完全一致的,我们就想要他们里面的方法都是一样的,这样的话,代理类就可以完全代理UserDao里面的内容了。这个时候,我们可以让UserDao和代理类(UserDaoUtils)同时实现一样的接口
public interface Dao {    void add();
    void delete();
}
public class UserDao implements  Dao{
    @Override
    public void add(){
        System.out.println("添加用户");
    }

    @Override
    public void delete() {
        System.out.println("删除用户");
    }
}
public class UserDaoUtils implements Dao {
    UserDao ud = new UserDao();
    @Override
    public void add() {
        System.out.println("开启事务");


        ud.add();


        System.out.println("提交事务");
    }

    @Override
    public void delete() {
        System.out.println("开启事务");

        ud.delete();

        System.out.println("提交事务");

    }
}

这样的话,我们就可以实现了两个类里面有同样的方法了,可是,这个时候,又有一个新的问题,如果我被代理的类比较多了,那么,按照静态代理这种一个被代理的类对应一个代理类的方法来创建代理类的话,就需要创建大量的代理类了,这种不是我们想要的。这个时候我们先想一下,静态代理帮我们做了些什么东西,他做的这些东西有没有别的类或者对象也能够帮我们实现这些功能呢?
总结一下静态代理做的东西:
1.首先实现了跟被代理对象同样的接口,这样做的好处是让代理类于被代理类的方法一致
2.代理类在对应的方法里面调用了被代理对象的方法,实际上,代理代理,顾名思义只是帮我们做了一些其他的事情而已,实际我们要做的事情还是由真实对象去做,就好比我们进行数据的添加,肯定是有UserDao的对象去添加的,而不是让UserDaoUtils的对象去帮我们添加,这个类只是帮我们开启了事务、提交回滚事务而已

好像静态代理就是帮我们做了这两件事情而已,那么,如果我们能够找到一个类或者对象帮我们做这件事情,我们不就是可以省略了创建很多中间类的步骤了吗?这个时候,我们就要了解一下动态代理了,动态代理动态代理,那么意思也就是说他可以变化的去给某一些类创建代理对象了,关于动态代理,我们来看下下面的这个类和方法
Proxy
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
          返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
这个类可以帮我们实现我们上面提到的需求,这个类也就是代理类,创建动态代理的对象。
解析一下参数:
第一个参数:ClassLoader loader ,这个是类加载器,这个类加载器只要是属于我们自定义的类去获取的都可以,因为自定义类都是属于Application ClassLoader
第二个参数:Class<?>[] interfaces, 这个参数是被代理对象实现的接口,我们这个案例的的接口只有Dao,实际上一个类是可以实现多个接口的,所以,这里使用的是一个Class的数组,我们在静态代理的时候就说过了,提供一个接口的好处是我们可以让被代理类和代理类有同样的方法,所以,这里面也是需要有接口的。实际上,动态代理是必须提供接口的,我们在这里面也能大概知道这个道理,就是为了让代理类与被代理类有同样的方法
第三个参数:InvocationHandler h,这个是用来拦截当前代理对象执行的方法,这个是动态代理的核心,我们之前在讲静态代理的时候,就讲过,我们写的中间类(静态代理类)主要是为了写一个方法,然后去调用被代理类的方法,从而实现增强被代理类的功能,那么,在动态代理里面,我们也是要做这件事情,所以,我们在代理对象执行一个相应的方法的时候,先去拦截这个方法,然后再在这个方法内部调用被代理对象的方法,并且对他进行增强!


public class ProxyUtils {    static  UserDao ud = new UserDao();

    /**
     * 动态代理面向接口
     * 因为只有接口 才能规范 代理对象和被代理对象 的方法是一样的
     *
     * 动态代理 也是为产生一个中间类的对象
     *
     * */
public static Object getProsxyObject(){

        /**
         * 第一个参数主要是为了获取被代理对象
         *
         * 第二个参数 主要是为了让中间类对象(代理对象) 有着 跟被代理对象一样方法
         *
         * 第三个参数 就是为了增强被代理的方法 (首先要拦截代理对象执行的方法)
         * */
Dao d = (Dao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), new Class[]{Dao.class}, new InvocationHandler() {
            /**
             * Object o 代理对象
             *Method method 当前  代理对象执行的方法
             *  Object[] objects 当前执行方法 上面的参数
             * */
@Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                // Object o = method.invoke(obj,args);
            /*    System.out.println(method);
                System.out.println(Arrays.toString(objects));
                System.out.println("---");*/
                System.out.println("开启事务");
            
              Object obj =  method.invoke(ud,objects);


                System.out.println("提交事务");
                return obj;
            }
        });
        //代理对象被调用的时候 才会执行

        return d;
    }

    public static void main(String[] args) {
        //代理对象
        Dao d = (Dao)getProsxyObject();
      //  d.add();

        String test = d.update("test");
        System.out.println(test);
    }

}


当代理对象执行一个方法的时候,他会进入到InvocationHandler里面的public Object invoke(Object o, Method method, Object[] objects) throws Throwable{
}的方法里面,我们对这个方法的参数做一个说明:
第一个参数:Object o ,这个是代理对象,所以,在invoke的方法里面,我们除了o.getClass()的方法外,其他的都不能使用,因为我们说了,代理对象执行一个方法的时候,就会进入到invoke的这个方法里面,如果我们在invoke的方法里面使用了代理对象,那么就会递归调用了invoke的方法,会内存溢出,这个对象不要使用!
第二个参数:Method method ,这个是当前代理对象执行的方法的对象,我们可以根据方法的名字给某一些特定的方法进行一个增强
第三个参数: Object[] objects,这个是当前方法所执行携带的参数
执行上面的代码,我们可以解决我们创建多个代理类的缺陷!







欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2