陆敏技说Spring

作者: 码农星球 | 来源:发表于2018-03-30 17:34 被阅读0次

    前些天更新的《陆敏技说Spring》一文,小伙伴们的反映非常好,有很多找我们的老师索要配套视频的,时隔好几天,小编今天接着上次的未完待续,给大家送干货了哦~~ 结尾处有我们老师的联系方式,可以索要配套视频哦。

    此文为原创,禁止非法转载。

    正文:

    1.3.反射的原理

    我们讲完了IOC和DI了,但是其实对反射还是接触到了一点皮毛。现在,让我们深入的了解下反射。不过,要了解反射的原理,还得首先了解一些关于类型加载的基础知识。

    我们知道运行java代码的是虚拟机jvm。那么java代码本身是怎么进入到jvm并且被jvm所识别的呢。 代码本身本身编译之后变成class文件,class文件进入jvm会被一种叫做类加载器的组件先处理一遍。被类加载器处理过后的类型,会变成一些被jvm认识的元数据,它们包括:构造函数、属性和方法等。 负责反射的那些类型也认识这些元数据,并可以动态修改或者操作这些元数据。即,对于java中的任意一个类,反射类都能知道和调用这个类的所有属性和方法,包括私有属性和方法(这就厉害了,说好的封装呢,说好的private不能被访问,在反射类这里统统无效!)

    除此之外,反射甚至允许我们动态生成类型,也即我们压根在原来的代码中没有一个叫做User的类型,但是利用反射基础,却能动态生成一个User类型,再通过类加载器加载到jvm中。

    1.3.1. 类加载器

    既然说到类加载器,那我们就先来看看类加载器。类装载器具体来说就是解析类的节码文件并构造出类在JVM内部表现形式(元数据)的组件。类装载器加载一个类型,经历了如下步骤:

    1:装载:查找和导入Class文件;

    2:链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的:

    校验:检查载入Class文件数据的正确性;

    准备:给类的静态变量分配存储空间;

    解析:将符号引用转成直接引用;

    3:初始化:对类的静态变量、静态代码块执行初始化工作。

    类加载器默认有三个,它们分别负责不同类型的java类的加载:

    (1)Bootstrap ClassLoader 根类加载器

    也被称为引导类加载器,负责Java核心类的加载比如System,String等在JDK中JRE的lib目录下rt.jar文件中的那些类型。

    (2)Extension ClassLoader 扩展类加载器

    负责JRE的扩展目录中jar包的加载。这些类型在JDK中JRE的lib目录下ext目录下。

    (3)System ClassLoader 系统类加载器

    负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定jar包和类路径下那些类型。

    这三个类装载器之间存在父子层级关系,即根装载器是ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。

    我们可以通过代码得到类加载器的原型,如下:

    public class ClassLoaderTest { 

    public static void main(String[] args) { 

        ClassLoader loader = ClassLoaderTest.class.getClassLoader(); 

        System.out.println("current loader:"+loader); 

        System.out.println("parent loader:"+loader.getParent()); 

        System.out.println("top loader:"+loader.getParent(). getParent());      } 

    }

    结果:

    current loader:sun.misc.Launcher$AppClassLoader@4e0e2f2a

    parent loader:sun.misc.Launcher$ExtClassLoader@2a139a55

    top loader:null

    注意,根加载器在java中无法访问,所以是null。

    1.3.2. 反射能做什么

    利用反射,我们主要可以做这些事情,

    在运行时构造任意一个类的对象;

    在运行时获得任意一个类的信息,包括所具有的成员变量和方法;

    在运行时调用任意一个对象的方法;

    生成动态代理。

    构造任意一个对象:

        Class class1 = null;

        Class class2 = null;

        Class class3 = null;

        // 获取类型的三种方式,有了类型后就可以newInstance()了

        class1 = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");

        class2 = new UserDaoImpl().getClass();

        class3 = UserDaoImpl.class;

        System.out.println("类名称  " + class1.getName() + "类对象:" + class1.newInstance());

        System.out.println("类名称  " + class2.getName() + "类对象:" + class2.newInstance());

        System.out.println("类名称  " + class3.getName() + "类对象:" + class3.newInstance());

    注意,如果类有构造方法,则可以这样调用(这里,我们使用User类):

        Class class1 = Class.forName("com.zuikc.bean.User");

        // 第一种方法,不调用有参构造器,所以直接newInstance

        User user = (User) class1.newInstance();

        user.setAge(20);

        user.setName("baobao");

        System.out.println(user);

        // 第二种方法,先获取全部的构造器,看看它们有什么参数

        Constructor cons[] = class1.getConstructors();

        // 查看每个构造方法需要的参数

        for (int i = 0; i < cons.length; i++) {

            Class clazzs[] = cons[i].getParameterTypes();

            System.out.print("cons[" + i + "] (");

            for (int j = 0; j < clazzs.length; j++) {

                if (j == clazzs.length - 1)

                    System.out.print(clazzs[j].getName());

                else

                    System.out.print(clazzs[j].getName() + ",");

            }

            System.out.println(")");

        }

        // 调用有参构造器来创造对象

        user = (User) cons[0].newInstance(20, "Rollen");

        System.out.println(user);

        user = (User) cons[1].newInstance("Rollen");

        System.out.println(user);

    为保证代码的完整性,同时给出Bean,

    package com.zuikc.bean;

    public class User {

    public User() {

        super();

    }

    public User(String name) {

        super();

        this.name = name;

    }

    public User(int age, String name) {

        super();

        this.age = age;

        this.name = name;

    }

    private String name;

    private int age;

    public String getName() {

        return name;

    }

    public void setName(String name) {

        this.name = name;

    }

    public int getAge() {

        return age;

    }

    public void setAge(int age) {

        this.age = age;

    }

    @Override

    public String toString() {

        return "User [name=" + name + ", age=" + age + "]";

    }

    }

    获取类的信息,包括所具有的成员变量和方法:

        Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");

        System.out.println("===============本类的field===============");

        Field[] field = clazz.getDeclaredFields();

        for (int i = 0; i < field.length; i++) {

            int mo = field[i].getModifiers();

            String priv = Modifier.toString(mo);

            Class type = field[i].getType();

            System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";");

        }

        System.out.println("==========实现的接口或者父类的public field==========");

        Field[] filed1 = clazz.getFields();

        for (int j = 0; j < filed1.length; j++) {

            int mo = filed1[j].getModifiers();

            String priv = Modifier.toString(mo);

            Class type = filed1[j].getType();

            System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";");

        }

        System.out.println("==========实现的接口或者父类的方法==========");

        Method method[] = clazz.getMethods();

        for (int i = 0; i < method.length; ++i) {

            Class returnType = method[i].getReturnType();

            Class para[] = method[i].getParameterTypes();

            int temp = method[i].getModifiers();

            System.out.print(Modifier.toString(temp) + " ");

            System.out.print(returnType.getName() + "  ");

            System.out.print(method[i].getName() + " ");

            System.out.print("(");

            for (int j = 0; j < para.length; ++j) {

                System.out.print(para[j].getName() + " " + "arg" + j);

                if (j < para.length - 1) {

                    System.out.print(",");

                }

            }

            Class exce[] = method[i].getExceptionTypes();

            if (exce.length > 0) {

                System.out.print(") throws ");

                for (int k = 0; k < exce.length; ++k) {

                    System.out.print(exce[k].getName() + " ");

                    if (k < exce.length - 1) {

                        System.out.print(",");

                    }

                }

            } else {

                System.out.print(")");

            }

            System.out.println();

        }

    }

    调用任意方法,

        Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");

        // 调用方法,第二个参数指参数的类型

        Method method = method = clazz.getMethod("getUserByName", String.class);

        Object  o = method.invoke(clazz.newInstance(), "baobao");

        User user = (User)o;

        System.out.println(user.getName());

    调用任意属性:

        Class clazz = Class.forName("com.zuikc.dao.mysql.UserDaoImpl");

        Object obj = clazz.newInstance();

        // 可以直接对 private 的属性赋值

        Field field = clazz.getDeclaredField("propretyname");

        field.setAccessible(true);

        field.set(obj, "some value");

        System.out.println(field.get(obj));

    实现动态代理。注意,关于动态代理,是下一小节我们将要重点介绍的内容。在此处先略过。

    1.4.动态代理的现实意义

    我们说反射可以被用于动态代理,现在我们先不管动态代理是什么,按照我们最课程(zuikc.com)授课模式,我们先来看该技术产生的现实意义。

    1.4.1. 为什么需要动态代理

    首先,我们的程序已经撰写完毕了。现在,我们的产品经理跑过来说,最近发生了一些安全上的事故,所以我们要加入一个功能,记录某些关键的操作是谁在什么时候操作的。

    简单来说,比如,任何人访问getUserByName,我们都需要记录是谁,在什么时候访问,甚至如果是在一个web程序的话,我们还要记录访问者的远程ip地址。

    一种做法是,我们进入getUserByName中去修改代码,把记录日志这个事情完成一下。但是技术总监说,不行,既有的服务层以下的代码都已经经过严格测试,不能再侵入代码,我们必须在既有代码的外层来加入这些新功能。

    于是,思路来了:我们还是按照原有流程执行代码,但是执行到UserDao的getUserByName时候,我们通过技术手段替换掉UserDao这个对象,转而调用UserDaoProxy对象,在这个proxy中,也有一个getUserByName方法,在这个方法里面我们添加新代码,同时还调用老方法,这样就完美解决了即不修改老方法,也增加了新功能。

    当然,这里面的关键点,就是“通过技术手段替换掉UserDao这个对象,转而调用UserDaoProxy对象”。这个技术手段,就叫做:动态代理。

    1.4.2. 代理的最简单实现

    那怎么来生成代理对象,它的形式可以如:

        UserDao proxy= 根据UserDaoImpl来生成代理对象;

        proxy.getUserByName("baobao");

    好,现在的关键就是让我们来解决“根据UserDaoImpl来生成代理对象”。

    其实在JDK中的反射包中,正好有一个类java.lang.reflect.Proxy有一个newProxyInstance方法是用来专门生成代理对象的:

    static object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

    第一个参数,是要被代理的那个类的ClassLoader,那怎么得到这个ClassLoader?好办,直接调用 被代理的类型.getClass().getClassLoader();

    第二个参数,接受的是一个类型的参数,即我们的UserDaolIml这个类型一共实现了几个接口。那怎么得到这个接口数组呢,也好办,Class类型有一个方法getInterfaces()就是用来得到类型所实现的接口数组的。

    第三个参数,是要传入一个InvocationHandler的类型。我们发现InvocationHandler是一个接口,所以我们得首先实现一个InvocationHandler的实现类。这个实现类实现InvocationHandler中声明好的一个方法,叫做:

    Object invoke(Object proxy, Method method, Object[] args)

    看好了,可以说这就很关键了,Proxy被设计为,我们看上去是在执行proxy的getUserByName方法,其实是执行了proxy的invoke方法,那这是怎么做到的呢:

    1:首先,proxy能够执行另外一个类(InvocationHandler实现类)的方法,那proxy首先得持有这个类不是吗,所以我们才发现Porxy.newProxyInstance的第三个参数就是InvocationHandler类型。即创建代理类的时候,就会把InvocationHandler实现类作为方法参数传递给代理类;

    2:其次,执行任何代理类方法,jvm都会首先引导到执行invoke方法,比如执行getUserByName,其实已经被强制为执行invoke方法了,我们可以在invoke方法,通过反射对原方法为所欲为! 让我们看看最简单的invoke方法吧:

    package com.zuikc.proxy;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

    public class InvocationHandlerImpl implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("被代理的方法要开始执行了……");

        Object temp = method.invoke(被代理的类的实例, args);

        System.out.println("被代理的方法执行完毕了……");

        return temp;

    }

    } invoke自带了参数,被代理的类的原方法的元数据信息作为Method参数被传递起来的。我们可以对它为所欲为意味着:我们想怎么执行它就怎么执行它,甚至还可以选择不执行它。

    注意,被代理的方法是获取到了,但是为了运行它,可不还得有被代理的类的对象本身吗(上文中,红色的部分)?既然需要它,我们就在InvocationHandlerImpl的构造器中传给它就行了,于是,上面的代码变成了:

    package com.zuikc.proxy;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    public class InvocationHandlerImpl implements InvocationHandler {

    private Object obj = null;

    public  InvocationHandlerImpl(Object obj) {

        this.obj = obj;

    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("方法开始执行了~~");

        System.out.print("  被截获的方法为:" + method.getName() + "\t参数为:");

        for (Object arg : args) {

            System.out.print(arg + "\t");

        }

        System.out.println();

        Object temp = method.invoke(this.obj, args);

        System.out.println("方法执行完毕,返回~~");

        return temp;

    }

    }

    经过本次改造,客户端调用原来的代码,由:

        UserDaoImpl userDaoImpl = new UserDaoImpl();

        userDaoImpl.getUserByName("baobao");

    变成了被动态代理的:

        UserDaoImpl userDao = new UserDaoImpl();

        InvocationHandlerImpl handler = new InvocationHandlerImpl(userDao);

        UserDao proxy= (UserDao)Proxy.newProxyInstance(

                userDao.getClass().getClassLoader(),

                userDao.getClass().getInterfaces(),

                handler);

        proxy.getUserByName("baobao");

    其输出为:

    方法开始执行了~~

    被截获的方法为:getUserByName  参数为:baobao 

    方法执行完毕,返回~~

    1.4.3. 面向切面编程

    “代理的最简单实现”这一小节中所表现出现的开发思想就被定义为:面向切面编程(AOP)。 代理是实现AOP的技术手段。

    未完待续……

    相关文章

      网友评论

        本文标题:陆敏技说Spring

        本文链接:https://www.haomeiwen.com/subject/wtbicftx.html