前些天更新的《陆敏技说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的技术手段。
![](https://img.haomeiwen.com/i7917318/30fe77028439349e.png)
未完待续……
网友评论