我爱编程Java 杂谈

陆敏技说Spring

2018-03-30  本文已影响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的技术手段。

未完待续……

上一篇下一篇

猜你喜欢

热点阅读