在Spring常见问题解决 - AOP调用被拦截类的属性报NPE这篇文章里面讲到了:Spring
进行AOP
实现的时候,如果采取Cglib
来实现,那么底层创建实例的时候,会优先使用ReflectionFactory
去创建实例。而文章我也提到过,由ReflectionFactory
创建的实例对象,其成员属性是不会被初始化的。不过这句话从严格意义来说并不正确,那么本文我们继续来探究下。
总的来说,ReflectionFactory
的作用在于:可以不通过调用构造函数来创建一个对象的实例。 案例如下:
我们自定义一个User
类(无参构造必须要有)
public class User {
private String name;
public User(String name) {
this.name = name;
System.out.println("User with name");
}
public User() {
System.out.println("User without param");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
2.通过ReflectionFactory
来创建User
实例:
public static void main(String[] args) throws Exception {
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor constructor = reflectionFactory.newConstructorForSerialization(User.class, Object.class.getDeclaredConstructor());
constructor.setAccessible(true);
User t = (User) constructor.newInstance();
System.out.println(t.toString());
}
3.结果如下:可见并没有调用对应的构造函数。
ReflectionFactory
创建实例,底层是通过字节码来直接操作的。但是这一块的原理并不是本文想要讨论的重点。我们知道,AOP
调用里面,如果调用了被代理类的成员变量,那么其可能为null
。这一块才是我们要讨论的。
案例如下:
public class Test {
public final User user = new User("LJJ");
public User user2 = new User("LJJ");
public String name = "Hello";
public final String str = "ssss";
public Integer a = 12222;
public final Integer b = 12222;
public int aa = 1;
public final int bb = 2;
public static void main(String[] args) throws Exception {
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor constructor = reflectionFactory.newConstructorForSerialization(Test.class, Object.class.getDeclaredConstructor());
constructor.setAccessible(true);
Test t = (Test) constructor.newInstance();
System.out.println("final user: " + t.user);
System.out.println("user: " + t.user2);
System.out.println("final String: " + t.str);
System.out.println("String: " + t.name);
System.out.println("final Integer: " + t.b);
System.out.println("Integer: " + t.a);
System.out.println("final int: " + t.bb);
System.out.println("int: " + t.aa);
}
}
结果如下:
那么我们来讨论下,为何会这样?首先我们可以根据结果给出三类:
String
类型(单独拿出来是因为比较特殊)。Integer/User
这种Object
。int
这种基础数据类型。接下来进行分析,首先,我们知道用ReflectionFactory
去创建实例是不会调用构造函数的。那么我们先来说下Java
中一个类加载的流程,一共分为五个步骤:
< client>
方法的过程(包括静态语句块static{}
)详细的可以康康我的这篇文章深入理解Java虚拟机系列(三)–虚拟机类加载机制
初始化阶段对于一个类的创建而言是至关重要的,Java
类在初始化阶段,会根据我们自己制定的Java
代码去初始化类变量和其他资源 。既然ReflectionFactory
去创建实例是不会调用构造函数的,那么也就是不会经过初始化这个阶段,故对于本案例中的Test
类,其成员变量都是没有被初始化赋值的。故都是null
。
当然,有的读者可能注意到了,final String
和 final int
的组合都是能够拿到正确的值的(int
类型的默认值是0,它不存在null
这一说法)。
final
。对于本文而言重点就是:
final
修饰的变量,一定要有一个初始化的过程。可以在定义的时候直接赋值。也可以在类构造的时候传参赋值。final
变量是基本数据类型以及String
类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。那么请注意,Java
里面对于常量相关的概念我们要知道:
String
类型,在编译期就指定了起值,那么对应的字符串是保存在常量池中的。(也是栈的一部分)也因这两类数据的特殊性,不像User
类这样,初始化还需要在堆内存中去开辟一个空间。因此对于Test
类而言,User
类型的user
成员变量,无论是否被final
修饰,其都是需要通过堆内存给他们分配空间才能够使用的,而这一阶段就发生在Test
类的初始化阶段(但是本文案例中,这一阶段都被跳过了呀)。
这里也就可以理解为何案例中输出的结果是这样的。后面再举个例子来简单理解下final
的用法:
@org.junit.Test
public void test() {
String a = "helloworld";
final String b = "hello";
String d = "hello";
String c = b + "world";
String e = d + "world";
System.out.println((a == c));
System.out.println((a == e));
}
结果如下:
解释:
String a = "helloworld";
这段代码,变量a
就是常量,对应的字符串保存在常量池中。final String b = "hello";
中final
的修饰,因此对于变量b
,它是常量。String c = b + "world";
这段代码,程序看来就是两个常量之间的拼接运算,因此变量c
也是常量,这段逻辑则是在编译期就完成的。其值为”helloworld"
。同时发现常量池中已经有了”helloworld“
了,因此变量c
和变量a
本质上是一个东西。因此比较返回true
。String d = "hello";
这段代码,程序认为它是一个变量。非常量。因此后续的String e = d + "world";
这段代码,是在程序运行期间的链接阶段进行的。 本质上就不是一个东西。因为字符串拼接,运行时生成,说明用到了StringBuilder
的拼接,对象引用都不是同一个了。 因此比较返回false
。