J官方描述:
Provides classes and interfaces for obtaining reflective information about classes and objects. Reflection allows programmatic access to information about the fields, methods and constructors of loaded classes, and the use of reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
Classes in this package, along with java.lang.Class accommodate applications such as debuggers, interpreters, object inspectors, class browsers, and services such as Object Serialization and JavaBeans that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class.
大致的意思:反射被视为动态语言的关键。借助于Reflection API,基于运行时获取有关类和对象的反射信息的类和接口,允许以编程方式访问有关属性、方法和构造函数的信息,并允许在安全限制内使用反射字段、方法和构造函数对其底层对应对象进行操作,通常反射与Class一起使用。
**动态语言:**在类的运行时,可以动态改变类的结构的语言。如:C#,Javascript,PHP,Python,Erlang
**静态语言:**与动态语言对应,运行时,结构不可以变化的语言。如:Java,C,C++
Java不是动态语言,但Java可以称为“准动态语言”。即,Java有一定的动态性,我们可以利用反射机制,字节码操作获得类似动态语言的特性,使得编程更加灵活。
1.2 运行原理[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9lbO3l8-1647784307612)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220315234555567.png)]
**Source源代码阶段:**会将.java文件通过javac,编译成.class文件字节码,字节码内部,包含了该类的结构信息。
**Class类对象阶段:**通过类加载器,将类加载进JVM中,获取到Class对象,从而可以获取类结构的全部信息。
**Runtime运行时阶段:**创建好了所需要的对象,正常使用。
由此可见,反射可以理解成:得到Class对象以后,就能获取类的结构数据。
1.3 反射机制提供的功能
在运行时,判断任意一个对象所属的类
在运行时,构造任意一个类的对象
在运行时,判断任意一个类所具有的成员变量和方法
在运行时,获取泛型信息
在运行时,调用任意一个对象的成员变量和方法
在运行时,获取注解并处理
动态代理
常见的类:
java.lang.Class:可以用来表示所有字节码对象的类
java.lang.reflect.Method:用于表示类方法的对象
java.lang.reflect.Field:用于表示类属性的对象
java.lang.reflect.Constructor:用于表示类构造器的对象
2 Class类 2.1 概述Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.
Class has no public constructor. Instead Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and by calls to the defineClass method in the class loader.
大致的意思:
Java中运行的类,接口(枚举是一种特殊的类,注解是一种特殊的接口),基本类型,void,数组都可以表示一个Class对象,是在JVM中的一份份字节码,由具有相同元素类型和维数的所有数据共享。Class类是描述类的类,类对象是由Java虚拟机自动构造的,类被加载是通过调用类加载器中的defineClass方法。
Class本身也是一个类
一个加载的类在JVM中只会有一个Class实例,由相同元素类型和维数的所有数据共享与数据无关
int[] a = new int[10];
int[] b = {1, 2, 3, 4, 5, 6};
Class aClass = a.getClass();
Class bClass = b.getClass();
// true
System.out.println(aClass == bClass);
一个Class对象对应的是一个加载到JVM中的一个.class文件
通过Class可以完整地得到一个类中的所有被加载的结构
Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象
2.2 Class类中常用的方法| 方法名 | 功能说明 |
|---|---|
| static Class forName(String name) | 返回指定类名name的Class对象,并加载进内存 |
| Object newInstance() | 调用构造器,获取Class对象的一个实例 |
| String getName() | 获取Class对象表示的实体(类,接口,数组,基本类型或void)名称 |
| Class getSuperClass() | 获取当前Class对象的父类的Class对象 |
| Class[] getInterfaces() | 获取当前Class对象的接口 |
| ClassLoader getClassLoader() | 获取该类的类加载器 |
| Constructor[] getConstructors() | 获取当前Class对象的构造器 |
| Field[] getDeclaredFields() | 获取当前Class对象的属性 |
| Method[] getDeclaredMethods() | 获取当前Class对象的方法 |
| Method getDeclaredMethod(String name,Class … paramTypes) | 获取当前Class对象中的指定方法 |
现有Person.java,Baby.java,MyAnnotation自定义注解,其中Baby继承了Person类
MyAnnotation:
package com.wujinhua.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "hello";
}
Person:
package com.wujinhua.demo;
public class Person {
public int age;
String name;
private String phoneNumber;
private Person(int age, String name, String phoneNumber) {
this.age = age;
this.name = name;
this.phoneNumber = phoneNumber;
}
public Person() {
System.out.println("Person init.");
}
@MyAnnotation("eat")
public String eat(String foodName, int money) throws ClassNotFoundException, IllegalArgumentException {
System.out.println("i eat:" + foodName + " money: " + money);
return "eat";
}
private void say() {
System.out.println("i can say hello world");
}
public static void play() {
System.out.println("i can play game");
}
}
package com.wujinhua.demo;
import java.io.Serializable;
public class Baby extends Person implements Cloneable, Serializable {
private String sex;
String nickName;
public double weight;
public Baby(String nickName) {
this.nickName = nickName;
}
private Baby() {
}
private void crawl() {
System.out.println("I can crawl");
}
public static void work() {
System.out.println("I do not need to work");
}
}
2.3 获取Class
获取Person的Class类的实例(四种方法)
已知具体的类,通过类的class属性获取。
Class personClass1 = Person.class; //class com.wujinhua.demo.Person System.out.println(personClass1);
已知一个类的全限定名
Class personClass2 = Class.forName("com.wujinhua.demo.Person");
// class com.wujinhua.demo.Person
System.out.println(personClass2);
已知类的实例
Person person = new Person(); Class extends Person> personClass3 = person.getClass(); // class com.wujinhua.demo.Person System.out.println(personClass3);
通过类加载器
Class> personClass4 = this.getClass().getClassLoader().loadClass("com.wujinhua.demo.Person");
// class com.wujinhua.demo.Person
System.out.println(personClass4);
有了Class对象,能做什么?
2.4 创建运行时类的对象
直接使用字节码来创建
Class personClass = Class.forName("com.wujinhua.demo.Person");
Person instance = (Person) personClass.newInstance();
System.out.println(instance);
打印结果:
Person init. com.wujinhua.demo.Person@1540e19d
获取字节码对象的构造器,创建对象
ClasspersonClass = Person.class; Constructor constructor = personClass.getConstructor(); Person person = constructor.newInstance(); System.out.println(person);
打印结果:
Person init. com.wujinhua.demo.Person@1540e19d
创建成功
思考:如果构造器私有化,还能直接创建吗?
以Baby.java为例,尝试:
ClassbabyClass = Baby.class; Constructor constructor = babyClass.getConstructor(); Baby baby = constructor.newInstance(); System.out.println(baby);
运行结果:
Exception in thread "main" java.lang.NoSuchMethodException: com.wujinhua.demo.Baby.() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getConstructor(Class.java:1825) at com.wujinhua.test.BabyClass.main(BabyClass.java:15)
此时,查看API发现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VulVW8Vz-1647784307613)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220319163303792.png)]
还有一个getDeclaredConstructor(),它与getConstructor()区别在于:
getDeclaredConstructor():返回指定参数类型的构造器,包括private。
getConstructor():只能返回指定参数类型并且是public的构造器。
改变为getDeclaredConstructor(),再次尝试:
ClassbabyClass = Baby.class; Constructor constructor = babyClass.getDeclaredConstructor(); Baby baby = constructor.newInstance(); System.out.println(baby);
运行结果:
Exception in thread "main" java.lang.IllegalAccessException: Class com.wujinhua.test.BabyClass can not access a member of class com.wujinhua.demo.Baby with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Constructor.newInstance(Constructor.java:413) at com.wujinhua.test.BabyClass.main(BabyClass.java:16)
这是因为:
在调用private私有方法的时候,会进行安全检查,抛出IllegalAccessException异常
Constructor,Method,Field对象都有setAccessible()方法
参数为true:
指示反射的对象在使用时应该取消Java语言访问检查。
提高反射效率。如果代码中必须用反射,而该句代码需要频繁调用,请设置为true
使得原本无法访问的私有成员也可以访问
参数为false:
反射的对象应该实施Java语言访问检查
解决办法:在获取构造器的时候,取消语言访问检查
ClassbabyClass = Baby.class; Constructor constructor = babyClass.getDeclaredConstructor(); // 取消Java语言访问检查 constructor.setAccessible(true); Baby baby = constructor.newInstance(); System.out.println(baby);
运行结果:
Person init. com.wujinhua.demo.Baby@1540e19d
对象创建成功
2.5 获取运行时类的完整结构通过反射,可以获取运行时类的完整结构:
Field、Method、Constructor、SuperClass、Interface、Annotation、ParameterizedType
2.5.1 实现的全部接口Class[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
Class2.5.2 继承的父类babyClass = Baby.class; Class>[] interfaces = babyClass.getInterfaces(); Class> clazz = interfaces[0]; // java.lang.Cloneable System.out.println(clazz.getName()); // Cloneable System.out.println(clazz.getSimpleName());
Class getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class
Class2.5.3 获取全部的构造器babyClass = Baby.class; Class super Baby> superclass = babyClass.getSuperclass(); // com.wujinhua.demo.Person System.out.println(superclass.getName());
Constructor[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。
Constructor[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
int getModifiers():获取访问修饰符
String getName():获取名称
Class[] getParameterTypes():获取参数的类型
Class2.5.4 获取全部的方法personClass = Person.class; Constructor>[] declaredConstructors = personClass.getDeclaredConstructors(); for (Constructor constructor : declaredConstructors) { // 获取构造器修饰符 System.out.print(Modifier.toString(constructor.getModifiers()) + "tt"); // 获取构造器名 System.out.println(constructor.getName()); }
Method[] getDeclaredMethods()Method[] getMethods()
Method类中:
Class getReturnType():获取方法的返回值类型Annotation[] getAnnotations():获取方法的所有注解Class[] getParameterTypes(): 获取全部的参数类型int getModifiers():获取方法的修饰符Class[] getExceptionTypes():获取异常信息
Class2.5.5 获取全部的属性personClass = Person.class; Method[] methods = personClass.getMethods(); for (Method method : methods) { if ("eat".equals(method.getName())) { // 获取方法名称 // eat System.out.println(method.getName()); // 获取方法返回值类型 // String Class> returnType = method.getReturnType(); System.out.println(returnType.getSimpleName()); // 获取方法修饰符 // public System.out.println(Modifier.toString(method.getModifiers())); // 获取参数类型 // java.lang.String int Class>[] parameterTypes = method.getParameterTypes(); for (Class parameter : parameterTypes) { System.out.print(parameter.getName() + "tt"); } System.out.println(); // 获取异常信息 // ClassNotFoundException IllegalArgumentException Class>[] exceptionTypes = method.getExceptionTypes(); for (Class exceptionType : exceptionTypes) { System.out.print(exceptionType.getSimpleName() + "tt"); } System.out.println(); // 获取方法注解 // MyAnnotation Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations){ Class extends Annotation> aClass = annotation.annotationType(); System.out.print(aClass.getSimpleName()); } } }
Field[] getFields()Field[] getDeclaredFields()
Field类中:
int getModifiers():以整数形式返回属性的修饰符
Class> getType():获取属性类型
String getName():获取属性名称
Object get(Object obj):调用属性的get方法
void set(Object obj, Object value):调用属性的set方法
Class2.5.6 获取AnnotationpersonClass = Person.class; // 创建实例 Person person = personClass.newInstance(); Field[] declaredFields = personClass.getDeclaredFields(); for (Field field : declaredFields) { if ("name".equals(field.getName())) { // 关闭访问权限检查 field.setAccessible(true); // 获取属性的访问修饰符 // default String modifier = Modifier.toString(field.getModifiers()); System.out.println("".equals(modifier) ? "default" : modifier); // 获取属性类型 // String System.out.println(field.getType().getSimpleName()); // 调用属性的set方法设值 field.set(person, "Willer"); // 调用属性的get方法 // Willer System.out.println(field.get(person)); } }
get Annotation(Class annotationClass) :获取指定的注解getDeclaredAnnotations():获取全部注解 2.5.7 获取泛型
Type getGenericSuperclass():获取父类泛型类型ParameterizedType:泛型类型getActualTypeArguments():获取实际的泛型类型参数数组 2.5.8 类所在包
Package getPackage() 2.6 调用运行时类的指定结构 2.6.1 调用指定方法
通过反射,调用指定方法,通过Method完成。步骤:
通过Class类的getMethod(String name,Class…parameterTypes)方法取得指定的Method对象再使用Method中的invoke(Object obj, Object… args)进行调用指定方法
Class2.6.2 调用指定属性personClass = Person.class; // 创建实例 Person person = personClass.newInstance(); // 获取指定的方法 Method method = personClass.getMethod("eat", String.class, int.class); //调用方法,当调用的方法是静态方法时, invoke中的obj可以为null System.out.println(method.invoke(person, "兰州拉面", 10));
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
Field getField(String name)getDeclaredField(String name)
在Field中:
Object get(Object obj): 取得指定对象obj上此Field的属性值void set(Object obj,Object value) :设置指定对象obj上此Field的属性值
Class3 类的加载与ClassLoader的理解 3.1 类加载的过程personClass = Person.class; // 创建实例 Person person = personClass.newInstance(); // 获取指定的属性 Field phoneNumberField = personClass.getDeclaredField("phoneNumber"); // 由于属性是private, 此处不需要检查访问合法性 phoneNumberField.setAccessible(true); // 调用set方法, 设值 phoneNumberField.set(person, "182****1832"); // 调用get方法, 取值: 182****1832 System.out.println(phoneNumberField.get(person));
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3V4JNhS0-1647784307614)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220316222945289.png)]
当程序主动使用到某个类时,如果该类还未加载进JVM内存中,则会通过加载,链接,初始化对类初始化。
其中,验证,准备,解析合称链接
**加载:**通过类的全限定名,查找此类字节码文件,利用字节码文件创建Class对象。
以JDK8为例:
| 名称 | 加载的类 | 描述 |
|---|---|---|
| Bootstrap ClassLoader(启动类加载器) | %JAVA_HOME%/jre/lib | 无法直接访问 |
| Extension ClassLoader(扩展类加载器) | %JAVA_HOME%/jre/lib/ext | 上级为Bootstrap,显示为 null |
| Application ClassLoader(应用类加载器) | java-classpath | 上级为Extension |
| Custom ClassLoader(自定义类加载器) | custom | 上级为Application |
// 获取系统类加载器 sun.misc.Launcher$AppClassLoader@18b4aac2 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println(classLoader); // 获取系统类加载器的父类加载器, 即扩展类加载器 sun.misc.Launcher$ExtClassLoader@1540e19d classLoader = classLoader.getParent(); System.out.println(classLoader); // 获取扩展类加载器的父类加载器, 即启动类加载器 null classLoader = classLoader.getParent(); System.out.println(classLoader);
加载的机制-双亲委派模式
所谓的双亲委派,就是指调用类加载器的loadClass方法时,加载查找类的规则。即:加载器加载类时,先将请求委托给自己的父类加载器执行,直到顶层的启动类加载器。父类加载器能够完成加载则成功返回,不能自加载器才自己尝试加载。
核心方法:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 1. 检查该类是否已经加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 2. 有上级类加载器, 委托上级loadClass
c = parent.loadClass(name, false);
} else {
// 3. 如果parent为空,委托BootstrapClassLoader加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 如果还没有找到,调用每个类加载器自己扩展重写的findClass方法
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派机制的优点:
避免类的重复加载避免Java的核心API被篡改
链接:
**验证:**确保Class文件合法,符合当前虚拟机要求,不会危害到虚拟机的自身安全。**准备:**进行内存分配,为static修饰的类变量分配内存,并设置初始值。不包含final修饰的静态变量,因为final变量在编译时分配。**解析:**将常量池中的符合引用替换为直接引用的过程。
**初始化:**执行类构造器方法的过程,主要完成静态块执行以及静态变量的赋值。先初始化父类,再初始化当前类。只有对类主动使用时,才会进行。触发条件包括:创建类的实例;访问类的静态方法或静态变量的时候;使用Class.forName反射类的时候;某个子类初始化的时候。
3.2 自定义类加载器步骤:
继承ClassLoader父类
遵从双亲委派机制,重写findClass方法
(注意: 不是重写loadClass方法,这样会破坏双亲委派)
读取类文件的字节码
调用父类的defineClass方法来加载类
客户端调用该类的loadClass方法
代码:
先准备两个字节码文件,存放至D盘class文件夹中,用于自定义加载器测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C6PIePRK-1647784307614)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220317225856155.png)]
以Person.class为例
package com.wujinhua.reflect.cl;
public class Person {
public Person(){
System.out.println("Person init.");
}
private int age;
private String name;
@Override
public String toString() {
return super.toString();
}
}
自定义类加载器:
public class MyClassLoader extends ClassLoader {
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 拼接字节码的全地址
String path = "D:/class/" + name + ".class";
try (ByteOutputStream os = new ByteOutputStream()) {
Files.copy(Paths.get(path), os);
// 获取字节数组
byte[] bytes = os.getBytes();
// 调用父类defineClass方法, 返回Class
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("Class file not found");
}
}
}
测试
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader cl = new MyClassLoader();
Class> demoClass1 = cl.loadClass("Person");
Class> demoClass2 = cl.loadClass("Person");
System.out.println(demoClass1);
System.out.println(demoClass1 == demoClass2);
}
}
结果:
发现报错了/(ㄒoㄒ)/~~,信息如下:
Exception in thread "main" java.lang.NoClassDefFoundError: Person (wrong name: com/wujinhua/reflect/cl/Person) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at cn.wjh1832.cl.MyClassLoader.findClass(MyClassLoader.java:34) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at cn.wjh1832.cl.Test.main(Test.java:11)
经过排查,发现当时准备的Person.java文件中,没有将包名去掉,就编译。
此时,去掉package com.wujinhua.reflect.cl;
重新编译,再进行测试:
class Person true
运行成功O(∩_∩)O
此时,随便写一个名字的文件,测试类加载器,查看结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhdOUOng-1647784307615)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220318001458921.png)]
3.3 加载资源文件以加载db.properties文件为例:
public class LoadResource {
public static void main(String[] args) throws IOException {
Properties p = new Properties();
// 获取加载器
ClassLoader cl = LoadResource.class.getClassLoader();
//ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 读取资源文件
InputStream is = cl.getResourceAsStream("db.properties");
// 加载
p.load(is);
// 读取加载后的文件信息
String driverClassName = p.getProperty("driverClassName");
// com.mysql.jdbc.Driver
System.out.println(driverClassName);
}
}
4 反射的应用
4.1 数组拷贝
在Java的System类中,有个arraycopy方法,用来完成数组的拷贝
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
如:现在有一个源数组sourceArr,目标数组targetArr,目标数组需要从源数组中拷贝部分元素。
拷贝的要求:从源数组下标为2的位置开始,拷贝长度为3
目标数组从小标为2的地方,开始拷贝
int[] sourceArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] targetArr = new int[5];
System.arraycopy(sourceArr, 2, targetArr, 2, 3);
// [0, 0, 3, 4, 5]
System.out.println(Arrays.toString(targetArr));
要求使用Java来实现该功能:
private static void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length) {
// 源数组 和 目标数组都不能为空
if (src == null || dest == null) {
throw new NullPointerException("src and dest should not be null");
}
// 源数组 和 目标数组必须是数组
if (!src.getClass().isArray() || !dest.getClass().isArray()) {
throw new ArrayStoreException("src and dest must be arrays");
}
// 检查索引是否会越界
if (srcPos < 0 || destPos < 0 || length < 0
|| srcPos + length > Array.getLength(src)
|| destPos + length > Array.getLength(dest)) {
throw new ArrayIndexOutOfBoundsException("array index is out of bounds");
}
// 源数组 和 目标数组元素类型必须一致
if (src.getClass().getComponentType() != dest.getClass().getComponentType()) {
throw new ArrayStoreException("element type of the src and dest must be same");
}
// 开始拷贝
for (int i = srcPos; i < srcPos + length; i++) {
// 对应下标源数组的值
Object srcValue = Array.get(src, i);
// 将源数组的值设置到目标数组对应的下标中
Array.set(dest, destPos, srcValue);
// 将目标数组下标往后加1
destPos++;
}
}
4.2 玩转单例模式
此处,重点探讨内部类,DCL,枚举三种模式
4.2.1 内部类package com.wujinhua.singleton;
public class InnerSingleton {
private InnerSingleton() {
}
public static InnerSingleton getInstance() {
return Holder.singleton;
}
private static class Holder {
private static final InnerSingleton singleton = new InnerSingleton();
}
}
这段代码,乍一看没有任何问题。
但是,学习了”霸道流氓“的反射技术以后,可以破坏它的单例性:
InnerSingleton singleton = InnerSingleton.getInstance(); Class4.2.2 DCL双重检测锁clazz = InnerSingleton.class; Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); InnerSingleton singleton1 = constructor.newInstance(); // false System.out.println(singleton == singleton1);
传统写法:
package com.wujinhua.singleton;
public class DCLSingleton {
private DCLSingleton() {
System.out.println(Thread.currentThread().getName() + " init");
}
private volatile static DCLSingleton SINGLETON;
public static DCLSingleton getInstance() {
if (SINGLETON == null) {
synchronized (DCLSingleton.class) {
if (SINGLETON == null) {
SINGLETON = new DCLSingleton();
}
}
}
return SINGLETON;
}
}
测试,使用20个线程,去检测单例模式:
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(DCLSingleton::getInstance).start();
}
}
发现控制台只打印:Thread-0 init
乍一看,也没有问题,事实真是这样吗?受到内部类单例模式的影响,对DCL进行改进:
public class DCLSingleton {
// 用作信号量,当创建完成之后,改变值
private static boolean flag;
private volatile static DCLSingleton SINGLETON;
private DCLSingleton() {
synchronized (DCLSingleton.class) {
if (!flag) {
flag = true;
} else {
throw new RuntimeException("illegal to create");
}
System.out.println(Thread.currentThread().getName() + " init");
}
}
public static DCLSingleton getInstance() {
if (SINGLETON == null) {
synchronized (DCLSingleton.class) {
if (SINGLETON == null) {
SINGLETON = new DCLSingleton();
}
}
}
return SINGLETON;
}
}
测试结果:
public class Test {
public static void main(String[] args) throws Exception {
DCLSingleton instance = DCLSingleton.getInstance();
Class clazz = DCLSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
DCLSingleton dclSingleton = constructor.newInstance();
// com.wujinhua.singleton.DCLSingleton@1540e19d
System.out.println(instance);
// com.wujinhua.singleton.DCLSingleton@1540e19d
System.out.println(dclSingleton);
}
}
main init Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.wujinhua.singleton.Test.main(Test.java:16) Caused by: java.lang.RuntimeException: illegal to create at com.wujinhua.singleton.DCLSingleton.(DCLSingleton.java:20) ... 5 more
这样了就安全了吗?
魔高一尺,道高一丈,利用反射获取Field再去动态改变值,依旧可以破解。
public class Test {
public static void main(String[] args) throws Exception {
DCLSingleton instance = DCLSingleton.getInstance();
Class clazz = DCLSingleton.class;
Field flagField = clazz.getDeclaredField("flag");
flagField.setAccessible(true);
flagField.set(instance, false);
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
DCLSingleton instance1 = constructor.newInstance();
// com.wujinhua.singleton.DCLSingleton@14ae5a5
System.out.println(instance);
// com.wujinhua.singleton.DCLSingleton@7f31245a
System.out.println(instance1);
}
}
即使,后面对flag进行加密了以后,也是可以破解的。
所以,DCL在反射面前,也不安全。
4.2.3 枚举(推荐)枚举单例模式常见的写法:
package com.wujinhua.singleton;
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance() {
return INSTANCE;
}
}
此时,对EnumSingleton.class进行解析,使用 javap -p EnumSingleton.class
public final class com.wujinhua.singleton.EnumSingleton extends java.lang.Enum{ public static final com.wujinhua.singleton.EnumSingleton INSTANCE; private static final com.wujinhua.singleton.EnumSingleton[] $VALUES; public static com.wujinhua.singleton.EnumSingleton[] values(); public static com.wujinhua.singleton.EnumSingleton valueOf(java.lang.String); private com.wujinhua.singleton.EnumSingleton(); public com.wujinhua.singleton.EnumSingleton getInstance(); static {}; }
发现:枚举也是一种特殊的类,也有私有构造器,那么,我们可以使用反射来进行创建对象吗?
测试:
public class Test {
public static void main(String[] args) throws Exception {
EnumSingleton instance = EnumSingleton.INSTANCE;
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
EnumSingleton instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
结果:
Exception in thread "main" java.lang.NoSuchMethodException: com.wujinhua.singleton.EnumSingleton.() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at com.wujinhua.singleton.Test.main(Test.java:16)
报错了,先看段源码,Constructor中的newInstance方法:
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
发现,两者报错的原因不一样,Java欺骗了我们。
此时,使用jad工具,来生成EnumSingleton.java源代码,jad -sjava EnumSingleton.class ,一探究竟:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package com.wujinhua.singleton;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(com/wujinhua/singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
发现:EnumSingleton.java中并不是无惨构造器,有两个参数,此时,修改测试代码,重新测试:
Constructorconstructor = clazz.getDeclaredConstructor(String.class, int.class);
结果:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.wujinhua.singleton.Test.main(Test.java:18)
报错:Cannot reflectively create enum objects
由此可以看出,反射不能破坏枚举类型,这也是推荐使用枚举创建单例对象的原因。
4.3 代理模式代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
组成
抽象角色:通过接口或抽象类声明真实角色实现业务的方法。
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
模式结构
一个是真正的你要访问的对象(目标类),一个是代理对象。
真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
代理模式分为静态代理、动态代理。
静态代理是由程序员创建或工具生成代理类的源码,再编译代码类。所谓静态,也就是程序运行前,就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
4.3.1 静态代理案例1:中介租房
房东委托中介,出租自己的房子。
中介可以带客户去看房,租房,签合同,最后收取中介费。
角色分析:
抽象角色:租房
真实角色:房东
代理角色:中介
代码步骤:
接口
package com.wujinhua.proxy.demo01;
public interface Rent {
void rent();
}
真实角色
package com.wujinhua.proxy.demo01;
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子");
}
}
代理角色
package com.wujinhua.proxy.demo01;
public class Proxy implements Rent {
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHorse();
// 调用的是真实对象, 代理的核心, 其余都为附加操作
host.rent();
contract();
fee();
}
private void seeHorse() {
System.out.println("带客户看房");
}
private void contract() {
System.out.println("和客户签约");
}
private void fee() {
System.out.println("收取中介费");
}
}
客户端访问代理角色
package com.wujinhua.proxy.demo01;
public class Client {
public static void main(String[] args) {
// 真实对象
Host host = new Host();
// 代理对象
Proxy proxy = new Proxy(host);
// 输出结果: 带客户看房
// 房东出租房子
// 和客户签约
// 收取中介费
proxy.rent();
}
}
案例2:模拟在业务方法中加日志
接口
public interface IUserService {
void add();
void delete();
void update();
void query();
}
真实对象
public class UserServiceImpl implements IUserService {
@Override
public void add() {
System.out.println("添加一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void query() {
System.out.println("查询一个用户");
}
}
代理对象
public class UserServiceLogProxy implements IUserService {
private UserServiceImpl userService;
public UserServiceLogProxy(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
private void log(String msg) {
System.out.println("执行了: [" + msg + "] 方法");
}
}
客户端
public class Client {
public static void main(String[] args) {
// 获取代理对象
UserServiceLogProxy proxy = new UserServiceLogProxy(new UserServiceImpl());
// 运行结果: 执行了: [add] 方法
// 添加一个用户
proxy.add();
}
}
静态代理的优点:
可以使真实对象的操作更加纯粹,不用去关注其他操作业务分工明确,职责清晰高扩展性代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用
缺点:
一个真实对象,就会产生一个代理对象,代码量变多 4.3.2 动态代理
动态代理的角色和静态代理一致动态代理的代理类是动态生成的,不是我们直接写好的动态代理分为两大类:基于接口的动态代理,基于类的动态代理
基于接口:JDK动态代理(在此讲解)基于类:cglibJava字节码实现:javasist
JDK动态代理核心类:
InvocationHandler 和 Proxy
InvocationHandler
官方描述:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
大致的意思:每个代理实例都有一个关联的调用处理程序,当调用代理实例的方法时,利用反射,会被分发给其他调用处理程序(真实的对象)的调用方法。
处理代理实例上的方法调用并返回结果时,调用其invoke方法
Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable
Proxy
官方描述:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
大致的意思:Proxy提供了静态方法,可以用来创建动态代理类和实例。
重点关注的方法,最终会动态生成一个代理对象:
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
改造静态代理案例:
案例1:中介租房
代码步骤:
接口,真是对象不变代理角色:
public class ProxyInvocationHandler implements InvocationHandler {
private Host host;
public void setHost(Host host) {
this.host = host;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
host.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHorse();
// 调用真实对象, 核心方法, 其余都为附加操作
Object result = method.invoke(host, args);
contract();
fee();
return result;
}
private void seeHorse() {
System.out.println("带客户看房");
}
private void contract() {
System.out.println("和客户签约");
}
private void fee() {
System.out.println("收取中介费");
}
}
客户端:
public class Client {
public static void main(String[] args) {
// 真实对象
Host host = new Host();
// 调用处理器来动态创建代理对象
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setHost(host);
// 创建代理对象
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
便于理解,上面的那句话:
执行目标对象的方法时, 会触发处理器的方法, 会把当前执行的目标对象的方法作为参数传入
在此,打了断点后,可以发现method参数的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JiJR2Lj-1647784307616)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220320211833374.png)]
案例2:模拟在业务方法中加日志
只需要将上面的ProxyInvocationHandler类的目标对象改下即可
我们可以将它作为模板
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用真实对象, 核心方法, 其余都为附加操作
Object result = method.invoke(target, args);
// 附加操作
// TODO
return result;
}
}
动态代理的优点:
一个动态代理类代理的是一个接口,一般就是对应的一个业务一个代理类可以代理多个类,只要是实现同一接口就可以了 5 内省机制
常见的作用:用于查看和操作javabean中的属性.
1) 获取JavaBean中的每一个属性名/属性类型.
2) 通过getter方法获取属性值,通过setter方法给属性设置值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgkNKpOw-1647784307617)(C:Users23865AppDataRoamingTyporatypora-user-imagesimage-20220320214423932.png)]
可以参考java.bean中的API
一般的用法:
- 一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。
案例:在jdbc中查询数据库的数据,并将结果集返回,此时,结果集会返回对象的集合或者对象属性的值
public interface IResultSetHandler{ T handle(ResultSet rs) throws Exception; }
public class BeanListHandlerimplements IResultSetHandler > { private Class
classType; public BeanListHandler(Class classType) { this.classType = classType; } public List handle(ResultSet rs) throws Exception { List list = new ArrayList (); while (rs.next()) { //每一行,封装成一个对象 T obj = classType.newInstance(); BeanInfo beanInfo = Introspector.getBeanInfo(classType, Object.class); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { String columnName = pd.getName(); Object val = rs.getObject(columnName); pd.getWriteMethod().invoke(obj, val); } list.add(obj); } return list; } } .getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 调用真实对象, 核心方法, 其余都为附加操作 Object result = method.invoke(target, args); // 附加操作 // TODO return result; } }
动态代理的优点:
一个动态代理类代理的是一个接口,一般就是对应的一个业务一个代理类可以代理多个类,只要是实现同一接口就可以了 5 内省机制
常见的作用:用于查看和操作javabean中的属性.
1) 获取JavaBean中的每一个属性名/属性类型.
2) 通过getter方法获取属性值,通过setter方法给属性设置值
[外链图片转存中…(img-OgkNKpOw-1647784307617)]
可以参考java.bean中的API
一般的用法:
- 一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。
案例:在jdbc中查询数据库的数据,并将结果集返回,此时,结果集会返回对象的集合或者对象属性的值
public interface IResultSetHandler{ T handle(ResultSet rs) throws Exception; }
public class BeanListHandlerimplements IResultSetHandler > { private Class
classType; public BeanListHandler(Class classType) { this.classType = classType; } public List handle(ResultSet rs) throws Exception { List list = new ArrayList (); while (rs.next()) { //每一行,封装成一个对象 T obj = classType.newInstance(); BeanInfo beanInfo = Introspector.getBeanInfo(classType, Object.class); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor pd : pds) { String columnName = pd.getName(); Object val = rs.getObject(columnName); pd.getWriteMethod().invoke(obj, val); } list.add(obj); } return list; } }



