A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大蓝鲸Java 于 2017-12-1 12:41 编辑

【南京校区】动态代理技术分享

动态代理技术基本上是在框架中使用,例如:Struts1、Struts2、Spring和Hibernate中都使用了动态代理技术。如果你不想自己写个框架,那么你基本上是用不上动态代理技术的。我们学习动态代理技术的目的是为了更好的理解框架内部的原理,也就是说是为了将来我们学习框架打基础!动态代理技术有点小难度!而且明白了动态代理技术可能一时也想不到他适合在什么情况下使用它。这些东西都会在学习框架时渐渐明白。
      接下来我们就正式的开始介绍Proxy实现动态代理技术.我们先来看一下,jdk给我的解释.动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,该类具有下面描述的行为:
代理接口:是代理类实现的一个接口。
代理实例:是代理类的一个实例。
每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler.
在这里我们一定要注意:Proxy动态代理:只针对接口.
我们可以从jdk中找到获得代理对象的方法: static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h),该方法是一个静态的,我们使用的时候,可以直接通过类名就能调用,获取代理实例.其实动态代理也没有那么难学,在这里只要掌握了:
1.    谁是代理者
a)      由newProxyInstance所产生的对象就是代理者
2.    谁是被代理者
a)      那些接口就是被代理者
3.    newProxyInstance方法中的三个参数是做什么
a)      ClassLoader: 类加载器!因为这个方法会生成一个新类需要知道谁来加载它.
1.     先来了解ClassLoader是什么东西?
ClassLoader是类加载器,它的作用就是负责把.class文件加载到内存中(一般情况下,一个class文件只会被加载一次到内存).
2.     动态代理,为什么会需要这个类加载器呢?
其实,每一个.class都需要类加载器.因为你要运行这块代码,就必须先把这块代码加载到内存中,而类加载器就相等于一个交通工具,把你存储在本地磁盘中的.class文件运输到jvm的内存中取.而有动态代理产生的对象,并没有在本地产生一个.class文件,所以我们需要手动给一个类加载器过去.
3.     如何获取类的加载器呢?
可以通过反射的方式来获取类的加载器,在Class类中有一个方法getClassLoader(),就是用来获取类加载器的.如果想要获取这个方法,那么我们必须先获取一个字节码文件对象.
4.     应该使用哪个类,获取哪个字节码对象呢?
一般情况下,我们应该获取本类的字节码,因为我们的动态代理是写在本类中的.那么当我们需要执行动态代理的时候,这个类一定以及被加载了,所以,保证了我们可以通过本类获取类加载器.
5.     如何获取?
第一步:获取本类的字节码对象
第二步:通过获取的字节码对象,调用getClassLoader()方法,来获取.
b)      Class[]:生成的新类要实现的接口们
c)       InvocationHandler:它是一个接口,需要实现它,只有一个方法invoke().这个方法会拦截被代理对象的所有方法.即调用被代理对象的方法,就是调用InvocationHandler的invoke()方法
当我们实现该接口的时候,会重写一个invoke方法,该方法中也有三个参数:
1.     Objectproxy:代表当前代理对象.它就是代理对象本身.
2.     Methodmethod:当前方法的反射对象,是通过反射的方式,获取接口中的方式.
3.     Obejct[]args : 当前方法调用的参数.接口中方法的参数,该参数是可变参数.可以有一个,可以有多个,也可以没有.
那么,你对动态代理就有了一个基本的认识,在后面的学习中会让你更加的清晰.
最后一个问题: 这个技术有什么用?
           对类进行增强.与装饰者模式相同.但是比装饰者模式更加的灵活.
以下有一个案例可供大家参考:
1.定义一个接口Login
public interface Login {
    String login(String username,String password);
}
2.定义一个类实现Login接口
public class LoginImp implementsLogin {
    @Override
    public String login(String username, String password) {
        if(!"admin".equals(username) || !"admin".equals(password)){
            return "fail";
        }
        return "success";
    }
}


3.定义一个Demo,实现动态代理
public class Demo {
    /*
     * 动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,
     * 该类具有下面描述的行为。 代理接口 是代理类实现的一个接口。
     *  代理实例是代理类的一个实例。
     *  每个代理实例都有一个关联的调用处理程序对象,它可以实现接口 InvocationHandler
     *  
     *  Proxy动态代理:只针对接口.

        static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
     */
    public static void main(String[] args) {
        /*
         *代理类中获取实例的三个参数:
         *  第一个参数:ClassLoader loader
         *      了解ClassLoader是什么东西?
         *          类加载器,作用:负责加载类的对象
         *          我们编写一个源码文件(.java),会编译成字节码文件(.class)
         *          其实就是将.class文件对象加载到内存中.
         *  
         *      动态代理,为什么会需要这个类加载器?
         *          ClassLoader:可以当作一个交通工具
         *
         *      使用哪个类的加载器?
         *          保证,动态代理执行的时候,该类已经被加载了.
         *          一般情况下,使用的类都是本类,通过本类获取一个类的加载器.
         *
         *      如何获取?
         *          第一步:获取该类的字节码文件对象.
         *          第二步:通过该类的字节码文件对象,获取类加载器:getClassLoader();
         *
         *  Class<?>[]
         *  InvocationHandler :是代理实例的调用处理程序 实现的接口。
         */
        //获取代理的实例对象
        //获取类加载器
        ClassLoader loader = Demo.class.getClassLoader();
        //获取Class[],该数组中存储的是字节码文件对象,而且必须是接口的字节码
        Class[] interfaces = {Login.class};
        //InvocationHandler

        final Login l = new LoginImp();

        InvocationHandler h = new InvocationHandler() {
            /*
             * Object proxy:当前的代理对象;
             *
             * Method method:当前方法的反射对象
             *
             * Object[] args:当前方法调用的参数
             *
             */
            @Override
            public Object invoke(Object proxy, Method method,Object[] args)
                    throws Throwable {
                // 是代理实例调用处理的代码
                Object obj = method.invoke(l, args);
                //无论登录成功,还是失败都需要添加到日志文件中
                String log = "用户:" + args[0] + "," + "登录平台" + obj;
                LogUtils.logWrite(log);
                return obj;
            }
        };

        Object obj = Proxy.newProxyInstance(loader, interfaces, h);//代理对象
        //System.out.println(obj);//打印值就是:在InvocationHandler实现类中的invoke的返回值.
        //强转
        Login login = (Login)obj;
        //创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        //获取用户名
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        //获取密码
        System.out.println("请输入密码:");
        String password = sc.nextLine();
        //调用登录的方法
        String isSuccess = login.login(username,password);
        if("success".equals(isSuccess)){
            System.out.println("登录成功...");
        }else{
            System.out.println("登录失败...");
        }
    }
}

4.工具类写的实现:
public class LogUtils {
    private LogUtils(){}

    public static void logWrite(String log){
        try {
            //获取当前时间,记录日志的时间
            Date date = new Date();
            //创建格式化日期对象
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
            String sDate = sdf.format(date);
            BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt",true));
            bw.write(sDate + ":");
            bw.newLine();
            bw.write("\t"+log);
            bw.newLine();
            bw.write("--------------------------------------------------------");
            bw.newLine();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6 个回复

倒序浏览
郭俊峰老师 来自手机 高级黑马 2017-12-4 12:29:59
沙发
动态代理讲的蛮详细
回复 使用道具 举报
厉害了我的哥~~
回复 使用道具 举报
666,学习了。
回复 使用道具 举报
666
回复 使用道具 举报
回复 使用道具 举报
6666666666666666
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马