JVM和类
同一个JVM的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。
JVM进程终止条件:
1、程序运行到最后正常结束
2、程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序
3、程序执行过程中遇到未捕获的异常或错误而结束
4、程序所在平台强制结束了JVM进程
由上可知,当Java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。
类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,即当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象
系统中所有的类实际上也是实例,都是java.lang.Class的实例
类的加载由类加载器完成,类加载通常由JVM提供。JVM提供的类加载器称为系统类加载器。
开发者可以通过继承ClassLoader基类来创建自己的类加载器
记载类的来源:
1、从本地文件系统加载class文件
2、从JAR包加载class文件
3、通过网络加载class文件
4、把一个Java源文件动态编译,并执行加载
Java虚拟机规范允许系统预先加载某些类
类的连接
类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接分为三个阶段:
(1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
(2)准备:类准备阶段负责为类的变量分配内存,并设置默认的初始值
(3)解析:将类的二进制数据中的符号引用替换成直接引用
类的初始化
虚拟机负责对类进行初始化,主要对类变量进行初始化。
声明变量时指定初始值,静态初始化块都将被当成类的初始化语句,JVM会按这些语句在程序中的排列顺序依次执行它们。
1 package com.edu.ynu.java.learn.nio; 2 3 public class Test 4 { 5 6 7 8 9 static 10 { 11 // 使用静态初始化块为变量b指定初始值 12 b = 6; 13 System.out.println("---------------------------------"); 14 } 15 static int a = 5; 16 static int b = 9; 17 static int c; 18 public static void main(String[] args) 19 { 20 System.out.println(Test.b); 21 } 22 }
按照初始化顺序上述的代码的执行结果为9;
JVM初始化一个类包含如下几个步骤:
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
当执行第2步骤时,系统对直接父类的初始化步骤也遵循此步骤1~3;如果该直接父类又有直接父类,则系统再次重复这三个步骤,依次类推。
类初始化的时机
当java程序首次通过6种方式类使用某个类或接口时,系统就会初始化该类或接口
1、创建类的实例。为某个类创建实例的方式包括:使用new操作符来创建实例,通过反射来创建实例,通过序列化的方式来创建实例
2、通过某个类的方法(静态方法)
3、访问某个类或接口的类变量,或为该类变量赋值
4、使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。例如:Class.forName("Person")
5、初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化
6、直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类
注意:对于final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”,Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。
反之,如果final修饰的类变量的值不能在编译时确定下来,则必须等到运行时才可以确定该类变量的值,如果通过该类来访问它的类变量,则会导致该类被初始化。
1 //采用系统当前时间为static final类变量赋值
2 static final String compileConstant=System.currentTimeMillis()+"";
当使用ClassLoader类的loadClass()方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName()静态方法才会导致强制初始化该类
1 package com.edu.ynu.java.learn.reflect; 2 3 class Tester 4 { 5 static 6 { 7 System.out.println("Tester类的静态初始化块..."); 8 } 9 } 10 11 public class ClassLoaderTest 12 { 13 public static void main(String[] args) 14 { 15 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 16 //下面语句仅仅是加载Tester类 17 try 18 { 19 classLoader.loadClass("com.edu.ynu.java.learn.reflect.Tester"); 20 } catch (ClassNotFoundException e) 21 { 22 e.printStackTrace(); 23 } finally 24 { 25 } 26 System.out.println("系统加载Tester类"); 27 // 下面语句才会初始化Tester类 28 try 29 { 30 Class.forName("com.edu.ynu.java.learn.reflect.Tester"); 31 } catch (ClassNotFoundException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 }
类加载器
类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存上,并为之生成对应的java.lang.Class对象。
一个载入JVM的类也有一个唯一的标识,其包括你全限定类名和其类加载器作为其唯一标识。其中全限定类名(包括包名和类名)
例如,包名为pg,一个名为Person的类,类加载器ClassLoader的实例k1负责加载,其在JVM中表示为(Person、pg、k1)
意味着不同的加载器,加载同一个的类,他们所加载的类也是完全不同、互补兼容的
当JVM启动时,会形成有三个类加载器组成的初始类加载器层次结构
1、Bootstrap ClassLoader:根类加载器
2、Extension ClassLoader:拓展类加载器
3、System ClassLoader:系统类加载器
Bootstrap ClassLoader被称为引导(也称为原始或根)类加载器,它负责加载Java的核心类。在Sun的JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.net.URL; 4 5 public class BootstrapTest 6 { 7 public static void main(String[] args) 8 { 9 URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); 10 11 for (int i = 0; i < urls.length; i++) 12 { 13 System.out.println(urls[i].toExternalForm()); 14 } 15 } 16 }
Extension Classloader被称为拓展类加载器,负责加载JRE的拓展目录(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR包的类。通过这种方式,就可以为Java拓展核心类以外的新功能,只要把自己开发的类打包成JAR文件然后放入JAVA_HOME/jre/lib/ext路径即可。
System Classloader被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。
程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。
如果没有特别指定,用户的自定义的类加载器都已此加载器为父类加载器。
类加载器的机制
1、全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负载载入,除非显示使用另外一个类加载器来载入
2、父类委托。先让parent(父)类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己路径中加载该类。
3、缓存机制:缓存机制将会保证所有的加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换层Class对象,存入缓存区中。因此修改了Class后,必须重新启动JVM,程序所做的修改才会生效。
除了可以使用Java提供的类加载器之外,开发者还可以实现自己的类加载器
1 package com.edu.ynu.java.learn; 2 3 import java.io.IOException; 4 import java.net.URL; 5 import java.util.Enumeration; 6 7 public class ClassLoaderPropTest 8 { 9 public static void main(String[] args) 10 { 11 ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); 12 System.out.println("系统类加载器:" + systemLoader); 13 /** 14 * 获取系统类加载器的加载路径---通常由CLASSPATH环境变量指定 15 * 如果操作系统没有指定CLASSPATH环境变量,则默认以当前路径作为系统类加载器路径 16 */ 17 try 18 { 19 Enumeration<URL> em1 = systemLoader.getResources(""); 20 while (em1.hasMoreElements()) 21 { 22 System.out.println(em1.nextElement()); 23 } 24 ClassLoader extensionLader = systemLoader.getParent(); 25 System.out.println("拓展类加载器: " + extensionLader); 26 System.out.println("拓展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); 27 System.out.println("拓展类加载器parent:" + extensionLader.getParent()); 28 } catch (IOException e) 29 { 30 e.printStackTrace(); 31 } finally 32 { 33 34 } 35 36 } 37 }
程序运行的结果:
1 Connected to the target VM, address: '127.0.0.1:6420', transport: 'socket' 2 系统类加载器:sun.misc.Launcher$AppClassLoader@14dad5dc 3 file:/D:/development/developSpace/javaworkspace/JavaBase/java_learning/Excluded/production/java_learning/ 4 拓展类加载器: sun.misc.Launcher$ExtClassLoader@3fb4f649 5 拓展类加载器的加载路径:D:\development\developTools\jdk1.8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext 6 拓展类加载器parent:null 7 Disconnected from the target VM, address: '127.0.0.1:6420', transport: 'socket'
由上面的运行结果可以看到,拓展类加载器的父加载器是null,并不是返回根类加载器,这是因为根类加载器并没有继承ClassLoader抽象类,所以拓展类加载器的getParent()方法返回null,但实际上,拓展类加载器的父类加载器是根类加载器,只是根类加载器并不是Java实现的,程序通常无须访问根类加载器,因此访问拓展类加载器的父类加载器时返回null.
从运行结果可以看出,系统类加载器是AppClassLoader的实例,拓展类加载器是ExtClassLoader的实例,实际上这两个类都是URLClassLoader类的实例
类加载器加载Class需要经过的步骤:
1、检查此Classs是否载入过(即在缓存区中是否有此Class),如果有则直接进入第8步,否则接着执行第2步。
2、如果父类加载器不存在(如果没有父类加载器,则要么parent一定是根类加载器,要么本身就是根类加载器),则跳到第4步执行,如果父类加载器存在,则接着执行第3步。
3、请求使用父类加载器载入目标类,如果成功载入则跳到第8步,否则接着执行第5步
4、请求使用根类加载器来载入目标类,如果成功则跳到第8步,否则跳到第7步
5、当前类加载器蚕食寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,如果找不到则跳转到第7步
6、从文件中载入Class,成功载入后跳到第8步
7、抛出ClassNotFoundException异常
8、返回对应的java.lang.Class对象
其中,第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程。
创建并使用自定义的类加载器
JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过拓展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
ClassLoader类重要的方法
> loadClass(String name,boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象
>findClasss(String name):根据指定名称来查找类
需要实现自定义的ClassLoader,则可以通过重写上述的方法来来实现,推荐使用findClass()方法,而不是重写loadClass()方法。
loadClass()方法的执行步骤如下:
1、用findLoadedClass(String)来检查是佛已经加载类,如果已经加载则直接返回
2、在父类加载器上调用loadClass()方法。如果父类加载器为null,则使用根类加载器来加载
3、调用findClass(String)方法查找类
因此,如果重写loadClass()方法,则需要重写父类委托、缓冲机制两种策略,而重写findClass()方法可以避免上述步骤
Class defineClass(String name,byte[] b,int off,int len),该方法负责将指定流泪的字节码文件(即Class文件,如Hello.class)读入自己数组byte[]中,并把它转换为Class对象,该字节码文件可以源于文件、网络等。该方法是final的,并且管理JVM的许多复杂的实现,它负责将字节码分析成运行时数据结构,并校验有效性等。
自定义的ClassLoader案例:
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.lang.reflect.InvocationTargetException; 8 import java.lang.reflect.Method; 9 10 public class CompileClassLoader extends ClassLoader 11 { 12 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException 13 { 14 if (args.length < 1) 15 { 16 System.out.println("缺少目标类,请按如下格式运行Java源文件:"); 17 System.out.println("java CompileClassLoader ClassName"); 18 } 19 String progClass = args[0]; 20 //剩下的参数将作为运行目标类时的参数 21 //将这些参数复制到一个新的数组中 22 String[] progArgs = new String[args.length - 1]; 23 System.arraycopy(args, 1, progArgs, 0, progArgs.length); 24 CompileClassLoader ccl = new CompileClassLoader(); 25 26 //加载需要的运行的类 27 Class<?> clazz = ccl.loadClass(progClass); 28 //获取需要运行的类的主方法 29 Method main = clazz.getMethod("main", (new String[0]).getClass()); 30 Object argsArray[] = {progArgs}; 31 main.invoke(null, argsArray); 32 } 33 34 private byte[] getBytes(String filename) 35 { 36 File file = new File(filename); 37 long len = file.length(); 38 byte[] raw = new byte[(int) len]; 39 try (FileInputStream fin = new FileInputStream(file);) 40 { 41 // 一次读取Class文件的全部二进制数 42 int r = fin.read(raw); 43 if (r != len) 44 { 45 throw new IOException("无法读取全部文件:" + r + "!=" + len); 46 47 } 48 } catch (FileNotFoundException e) 49 { 50 e.printStackTrace(); 51 } catch (IOException e) 52 { 53 e.printStackTrace(); 54 } finally 55 { 56 57 } 58 return raw; 59 } 60 //重写ClassLoader的findClass方法 61 62 // 定义编译指定Java文件的方法 63 private boolean compile(String javaFile) throws IOException 64 { 65 System.out.println("CompileClassLoader:正在编译 " + javaFile + "..."); 66 // 调用系统的javac命令 67 Process p = Runtime.getRuntime().exec("javac " + javaFile); 68 try 69 { 70 p.waitFor(); 71 } catch (InterruptedException e) 72 { 73 System.out.println(e); 74 e.printStackTrace(); 75 } 76 //获取javac线程的退出值 77 int ret = p.exitValue(); 78 // 返回编译是否成功 79 return ret == 0; 80 } 81 82 @Override 83 protected Class<?> findClass(String name) throws ClassNotFoundException 84 { 85 Class classs = null; 86 //将包路径中的点(.)替换成斜线(/) 87 String fileStub = name.replace(".", "/"); 88 String javaFilename = fileStub + ".java"; 89 String classFilename = fileStub + ".class"; 90 File javaFile = new File(javaFilename); 91 File classFile = new File(classFilename); 92 if ((javaFile.exists() && (!classFile.exists())) || (javaFile.lastModified() > classFile.lastModified())) 93 { 94 try 95 { 96 if (!compile(javaFilename) || !classFile.exists()) 97 { 98 throw new ClassNotFoundException("ClassNotFoundException" + javaFilename); 99 } 100 } catch (IOException e) 101 { 102 e.printStackTrace(); 103 } 104 } 105 // 如果Class文件存在,系统负责将该文件转换成Class对象 106 if (classFile.exists()) 107 { 108 byte[] raw = getBytes(classFilename); 109 classs = defineClass(name, raw, 0, raw.length); 110 } 111 if (classs == null) 112 { 113 throw new ClassNotFoundException(name); 114 } 115 return classs; 116 } 117 }
使用自定义的类加载器,可以实现如下常见的功能
1、执行代码前自动验证数字签名
2、根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译*.class文件
3、根据用户需求来动态的加载类
4、根据应用需求把其他数据以字节码的形式加载到应用中
URLClassLoader类
Java为ClassLoader提供了一个URLClassLoader实现类,该类是系统系统类加载器和拓展类加载器的父类(此处的父类,就是指类与类之间的继承关系)
URLClassLoader既可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类
在应用程序中可以直接使用URLClassLoader加载类,URLClassLoader类提供的构造器如下:
>URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类
>URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象,其他功能与前一个构造器相同
一旦得到了URLClassLoader对象之后,就可以调用该对象的loadClass()方法来加载指定类。
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 import java.net.URLClassLoader; 6 import java.sql.Connection; 7 import java.sql.Driver; 8 import java.sql.SQLException; 9 import java.util.Properties; 10 11 public class URLClassLoaderTest 12 { 13 private static Connection conn; 14 15 public static Connection getConn(String url, String user, String pass) 16 { 17 if (conn == null) 18 { 19 try 20 { 21 URL[] urls = {new URL("file:D:\\development\\developData\\ali-repository\\mysql\\mysql-connector-java\\5.1.39\\mysql-connector-java-5.1.39.jar")}; 22 URLClassLoader myurlClassLoader = new URLClassLoader(urls); 23 Driver driver = (Driver) myurlClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); 24 Properties properties = new Properties(); 25 properties.setProperty("user", user); 26 properties.setProperty("password", pass); 27 conn = driver.connect(url, properties); 28 29 } catch (MalformedURLException e) 30 { 31 e.printStackTrace(); 32 } catch (IllegalAccessException e) 33 { 34 e.printStackTrace(); 35 } catch (InstantiationException e) 36 { 37 e.printStackTrace(); 38 } catch (ClassNotFoundException e) 39 { 40 e.printStackTrace(); 41 } catch (SQLException e) 42 { 43 e.printStackTrace(); 44 } 45 } 46 return conn; 47 } 48 49 public static void main(String[] args) 50 { 51 System.out.println(getConn("jdbc:mysql://localhost:3306/mysql", "root", "root")); 52 } 53 }
此处的URL可以以file:为前缀,表明从本地文件系统加载;可以以http:为前缀,表明从互联网通过HTTP 访问来加载;也可以以ftp:为前缀,表明从互联网通过FTP访问来加载
通过反射查看类信息
JAVA程序中很多对象都会出现两种类型:编译时类型和运行时类型,例如:Person p=new Student();代码将会生成一个p变量该变量在编译时类型为Person,运行时类型为Student。另外还会存在程序在运行时接收到外部传入的一个对象,该对象的编译时类型是Object,但程序又需要调用该对象的运行时的类型的方法。
解决该问题的方法如下:
1、第一种做法是假设在编译时和运行时都知道类型的具体给信息。在这种情况下,可以先使用instanceof运算符进行判断,再利用强制类型转换将其转换成运行时类型的变量。
2、采用反射依靠运行时信息来发现该对象和类的真实信息。
获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象接可以访问JVM中的这个类。Java程序中或的Class对象的方式:
》使用Class类的forName(String className)静态方法。该方法需要将传入字符串参数,该字符串参数为类的全限定类名(完整的报名)
》调用某个类的class属性类获取该类对应的Class对象。例如,Person.class将会返回Person类对应的Class对象
》调用某个对象额getClass()方法。
相比于第一种方法,第二种方式如下两种优势:
1、代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在
2、程序性能更好。因为这种方式无须调用方法费,所以性能更好
因此,尽可能的使用第二种方式来获取指定类的Class对象
从Class中获取信息
Class类提供额大量的实例方法类获取该Class对象所对应的类的构造函数,方法,属性,类上包含的Annotation,内部类,实现的接口,继承的父类,修饰符、所在包、类名,也可以判断该类是否为接口,枚举,注解等
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.annotation.Annotation; 4 import java.lang.annotation.Repeatable; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.reflect.Constructor; 8 import java.lang.reflect.Method; 9 import java.util.Arrays; 10 11 12 // 定义可重复注解 13 @Repeatable(Annos.class) 14 @interface Anno 15 { 16 } 17 18 @Retention(value = RetentionPolicy.RUNTIME) 19 @interface Annos 20 { 21 Anno[] value(); 22 } 23 24 // 使用4个注解修改该类 25 @SuppressWarnings(value = "unchecked") 26 @Deprecated 27 // 使用重复注解修改该类 28 @Anno 29 @Anno 30 public class ClassTest 31 { 32 private ClassTest() 33 { 34 35 } 36 37 38 public ClassTest(String name) 39 { 40 System.out.println("执行有参宿的构造器"); 41 } 42 43 public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException 44 { 45 Class<ClassTest> clazz = ClassTest.class; 46 //获取该class对象所对应的类的全部构造器 47 48 Constructor[] constructors = clazz.getDeclaredConstructors(); 49 System.out.println("ClassTest的全部构造器如下:"); 50 for (Constructor c : constructors) 51 { 52 System.out.println(c); 53 } 54 // 获取该Class对象所对应类的全部public方法 55 Method[] mtds = clazz.getMethods(); 56 System.out.println("ClassTest的全部public方法如下:"); 57 for (Method md : mtds) 58 { 59 System.out.println(md); 60 } 61 // 获取该Class对象对应类的指定方法 62 System.out.println("ClassTest里带一个字符串参数的info方法为:" + clazz.getMethod("info", String.class)); 63 // 获取该Class对象所对应类的全部注解 64 Annotation[] annotations = clazz.getAnnotations(); 65 System.out.println("ClassTest的全部Annotation如下:"); 66 for (Annotation annotation : annotations) 67 { 68 System.out.println(annotation); 69 } 70 System.out.println("该Class元素上的@SuppressWarnings注解为:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class))); 71 System.out.println("该Class元素上的@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class))); 72 //获取该Class对象所对应的类的全部内部类 73 Class<?>[] inners = clazz.getDeclaredClasses(); 74 System.out.println("ClassTest的全部内部类如下:"); 75 for (Class c : inners) 76 { 77 System.out.println(c); 78 } 79 // 使用Class.forName()方法加载ClassTest的Inner内部类 80 Class inClazz = Class.forName("com.edu.ynu.java.learn.reflect.ClassTest$Inner"); 81 // 通过getDeclaringClass()访问该类所在的外部类 82 System.out.println("inClazz对应的外部类为:" + inClazz.getDeclaringClass()); 83 System.out.println("ClassTest的包为: " + clazz.getPackage()); 84 System.out.println("ClassTest的父类为: " + clazz.getSuperclass()); 85 } 86 87 // 定义一个无参数的info方法 88 public void info() 89 { 90 System.out.println("执行无参数的info方法"); 91 } 92 93 // 定义一个有一个参数的info方法 94 public void info(String str) 95 { 96 System.out.println("执行有参数的info方法" + ",其str参数值:" + str); 97 } 98 99 class Inner 100 { 101 102 } 103 }
值得指出的是:虽然定义的ClassTest类时使用了@SuppressWarning注解,但程序运行时无法分析出该类里包含的该注解,这是因为@SuppressWarnings使用了@Retention(value=SOURCE)修改,这表明@SupressWarings只能保存在源代码级别上,而通过ClassTest.class获取该类的运行时Class对象,所以程序无法访问到@SuppressWarning注解
对于只能在源代码上保留的注解,使用运行时获得的Class对象无法访问该注解
Java8新增的方法参数反射
Java 8在java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor,Method两个子类
该类提供了如下方法:
1、isVarArgs()方法判断该方法或构造器是否包含数量可变的形参
2、getModifiers()方法获取该方法后构造器的修饰符
3、int getParameterCount():获取该构造器或方法的形参个数
4、Parameter[] getParameters():获取该构造器或方法的所有形参
Parameter是Java8中新加入的API,该类提供了大量方法来获取参数信息
需要指出的是,使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresenet()方法将会返回false,调用getName()方法也不能得到该参数的形参名。如果希望javac命名编译Java源文件时可以保留形参信息,则需要为该命令指定-parameters选项
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Parameter; 5 import java.util.List; 6 7 class Test 8 { 9 public void replace(String str, List<String> list) 10 { 11 12 } 13 } 14 15 public class MethodParameterTest 16 { 17 public static void main(String[] args) throws NoSuchMethodException 18 { 19 // 获取String类 20 Class<Test> clazz = Test.class; 21 // 获取String类的带两个参数的replace()方法 22 Method replace = clazz.getMethod("replace", String.class, List.class); 23 System.out.println("replace方法参数个数: " + replace.getParameterCount()); 24 Parameter[] parameters = replace.getParameters(); 25 int index = 1; 26 for (Parameter p : parameters) 27 { 28 if (p.isNamePresent()) 29 { 30 System.out.println("---第" + index + "个参数信息---"); 31 System.out.println("参数名:" + p.getName()); 32 System.out.println("形参类型:" + p.getType()); 33 System.out.println("泛型类型:" + p.getParameterizedType()); 34 } 35 } 36 } 37 }
需要使用如下命令来编译该程序:
1 javac -parameters -d . MethodParameterTest.java
使用反射生成并操作对象
创建对象
1、使用class对象的newInstance()方法来创建Classs对象对应类的实例,执行newInstance()方法实际上是利用默认构造器来创建该类的实例
2、先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应的类的实例。
示例程序
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 import java.util.HashMap; 7 import java.util.Map; 8 import java.util.Properties; 9 10 public class ObjectPoolFactory 11 { 12 //定义一个对象池,前面是对象名,后面是实际对象 13 private Map<String, Object> objectPool = new HashMap<>(); 14 15 public static void main(String[] args) 16 { 17 ObjectPoolFactory opf = new ObjectPoolFactory(); 18 opf.initPool("a.txt"); 19 System.out.println(opf.getObject("Date")); 20 System.out.println(opf.getObject("JFrame")); 21 } 22 23 // 定义一个创建对象的方法,该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象 24 private Object createObject(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException 25 { 26 Class<?> clazz = Class.forName(className); 27 return clazz.newInstance(); 28 } 29 30 /** 31 * 该方法根据指定来初始化对象池,它会根据配置文件来创建对象 32 * 33 * @param fileName 34 */ 35 public void initPool(String fileName) 36 { 37 try (FileInputStream fis = new FileInputStream(fileName);) 38 { 39 Properties properties = new Properties(); 40 properties.load(fis); 41 for (String name : properties.stringPropertyNames()) 42 { 43 // 每取出一个key-value对,就根据value创建一个对象,调用createObject()创建对象,并将对象添加到对象池中 44 objectPool.put(name, createObject(properties.getProperty(name))); 45 } 46 } catch (FileNotFoundException e) 47 { 48 e.printStackTrace(); 49 } catch (IOException e) 50 { 51 System.out.println("读取" + fileName + "异常"); 52 e.printStackTrace(); 53 } catch (IllegalAccessException e) 54 { 55 e.printStackTrace(); 56 } catch (InstantiationException e) 57 { 58 e.printStackTrace(); 59 } catch (ClassNotFoundException e) 60 { 61 e.printStackTrace(); 62 } 63 } 64 65 public Object getObject(String name) 66 { 67 // 从objectPool中取出指定name对应的对象 68 return objectPool.get(name); 69 } 70 }
其中a.txt文件配置如下:
1 Date=java.util.Date
2 JFrame=javax.swing.JFrame
Spring就是采用XML配置文件的方式,创建对象的
利用Constructor对象创建Java对象的步骤如下:
1、获取该类的Class对象
2、利用Class对象的getConstructor()方法来获取指定的构造器
3、调用Constructor的newInstance()方法来创建Java对象
程序案例:
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.InvocationTargetException; 5 6 public class CreateJFrame 7 { 8 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException 9 { 10 // 获取JFrame对应的Class对象 11 Class<?> jframeClass = Class.forName("javax.swing.JFrame"); 12 // 获取JFrame中带一个字符串参数的构造器 13 Constructor constructor = jframeClass.getConstructor(String.class); 14 Object object = constructor.newInstance("反射创建的窗口"); 15 System.out.println(object); 16 } 17 }
调用方法
调用方法的步骤:
1、通过Class对象的getMethods()方法或者getMethod()方法来获取全部方法方法或指定的方法
2、通过Method对象的invoke()方法,实现方法调用
invoke()方法的签名如下:
》》》Object invoke(Object obj,Object...args):该方法中的obj是执行该方法的主调,后面的args是执行该方法的传入方法的实参
1 package com.edu.ynu.java.learn.reflect; 2 3 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.IOException; 7 import java.lang.reflect.InvocationTargetException; 8 import java.lang.reflect.Method; 9 import java.util.HashMap; 10 import java.util.Map; 11 import java.util.Properties; 12 13 public class ExtendedObjectPoolFactory 14 { 15 // 定义一个对象池,前面是对象名,后面是实际对象 16 private Map<String, Object> objectPool = new HashMap<>(); 17 private Properties config = new Properties(); 18 19 public static void main(String[] args) throws IllegalAccessException, ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException 20 { 21 ExtendedObjectPoolFactory eopf = new ExtendedObjectPoolFactory(); 22 eopf.init("extObj.txt"); 23 eopf.initPool(); 24 eopf.initProperty(); 25 System.out.println(eopf.getObject("a")); 26 27 } 28 29 // 从指定属性文件中初始化Properties对象 30 public void init(String fileName) 31 { 32 try (FileInputStream fis = new FileInputStream(fileName)) 33 { 34 config.load(fis); 35 } catch (FileNotFoundException e) 36 { 37 e.printStackTrace(); 38 } catch (IOException e) 39 { 40 System.out.println("读取" + fileName + "异常"); 41 e.printStackTrace(); 42 } 43 } 44 45 // 定义一个创建对象的方法,该方法只要传入一个字符串类名,程序可以根据类名生成Java对象 46 private Object createObject(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException 47 { 48 // 根据字符串来获取对应的Class对象 49 Class<?> classs = Class.forName(className); 50 // 使用clazz对应的类默认构造器创建实例 51 return classs.newInstance(); 52 } 53 54 public void initPool() throws IllegalAccessException, InstantiationException, ClassNotFoundException 55 { 56 for (String name : config.stringPropertyNames()) 57 { 58 // 每取出一个key-value对,如果key中不包含百分号(%),这就表明是根据value来创建一个对象 59 // 调用createObject创建对象,并将对象添加到对象池中 60 if (!name.contains("%")) 61 { 62 objectPool.put(name, createObject(config.getProperty(name))); 63 } 64 } 65 } 66 67 // 该方法将会根据属性文件来调用指定对象的setter方法 68 public void initProperty() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException 69 { 70 for (String name : config.stringPropertyNames()) 71 { 72 //每取出一对key-value对,如果key中包含百分号(%)即可认为该key用于控制调用对象的setter方法设置值 73 // %前半为对象名,后半控制setter方法 74 if (name.contains("%")) 75 { 76 String[] objAndProp = name.split("%"); 77 // 取出调用setter方法的参数值 78 Object target = getObject(objAndProp[0]); 79 // 获取setter方法名:set+"首字母大写"+剩下部分 80 String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1); 81 Class<?> targetClass = target.getClass(); 82 Method method = targetClass.getMethod(mtdName, String.class); 83 //通过Method的invoke方法执行setter方法,将config.getProperty(name)的值作为调用setter方法的参数 84 method.invoke(target, config.getProperty(name)); 85 } 86 } 87 } 88 89 public Object getObject(String name) 90 { 91 // 从objectPool中取出指定的name对应的对象 92 return objectPool.get(name); 93 } 94 }
其中extObj.txt内容如下:
1 a=javax.swing.JFrame
2 b=javax.swing.JLabel
3 a%title=Test_Title
Spring框架就是通过这种方式将成员变量的以及依赖对象等都放在配置文件中进行管理的,从而是实现了较好的解耦,这也是Spring框架的IoC的秘密
当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的setAccessible()方法
setAccessible()方法签名如下:
1、setAccessible(boolean flag):将Method对象的accessible设置为指定的布尔值。
值为true,指示该Method在使用时应该取消Java语言的访问权限检查;
值为false,则指示该Method在使用时要实施Java语言的访问权限检查
实际上,setAccessible()方法并不属于Method,而是属于它的父类AccessibleObject。因此Method、Constructor、Field都可调用该方法,从而实现通过反射来调用private方法、private构造器和private成员变量。
访问成员变值
通过Class对象的getFields()或getField()方法可以获得该类所包含的全部成员变量或指定成员变量
1、getXxx(Object obj):获取obj对象的该成员变量的值。此处的Xxx对应8中基本类型,如果该成员变量是引用类型,则取消get后面的Xxx。
2、setXxx(Object obj,Xxx val):将obj对象的该成员变量设置为val值。此处的Xxx对应8中基本类型,如果该成员变量是引用类型,则取消get后面的Xxx。
1 package com.edu.ynu.java.learn; 2 3 import java.lang.reflect.Field; 4 5 public class FieldTest 6 { 7 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException 8 { 9 //创建一个Person对象 10 // Person p = new Person(); 11 // 获取Person类对应的Class对象 通过下面两种方法都可以创建的Person对象 12 Class<Person> personClass = Person.class; 13 Person p = personClass.newInstance(); 14 // 使用getDeeclaredField()方法表明可获取各种访问控制符的成员变量 15 Field nameField = personClass.getDeclaredField("name"); 16 // 设置通过反射访问该成员变量的是取消权限检查 17 nameField.setAccessible(true); 18 // 通过set()方法为p对象的name成员变量的设置值 19 nameField.set(p, "leo"); 20 21 Field ageField = personClass.getDeclaredField("age"); 22 ageField.setAccessible(true); 23 ageField.set(p, 123); 24 25 System.out.println(p); 26 27 } 28 } 29 30 class Person 31 { 32 private String name; 33 private int age; 34 35 @Override 36 public String toString() 37 { 38 return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; 39 } 40 41 42 }
操作数组
java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用Array来动态的创建数组,操作数据元素等
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class ArrayTest1 6 { 7 public static void main(String[] args) 8 { 9 10 Object array = Array.newInstance(String.class, 10); 11 // 设置索引为5,6的值 12 Array.set(array, 5, "java编程思想"); 13 Array.set(array, 7, "java并发编程的艺术"); 14 // 依次取出array数组中index为5、6的元素的值 15 Object book1 = Array.get(array, 5); 16 Object book2 = Array.get(array, 7); 17 18 // 输出array数组中index为5,7的元素 19 System.out.println(book1); 20 System.out.println(book2); 21 } 22 }
稍微复杂的Array创建的三维数组的案例
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class ArrayTest2 6 { 7 public static void main(String[] args) 8 { 9 Object array = Array.newInstance(String.class, 3, 4, 10); 10 // 获取array数组中index为2的元素,该元素应该是二维数组 11 Object arrObj = Array.get(array, 2); 12 Array.set(arrObj, 2, new String[]{"Java编程思想", "Java并发编程的艺术", "Effective java", "简洁之道"}); 13 // 获取arrObj数组中index为3的元素,该元素应该是一维数组 14 Object anArray = Array.get(arrObj, 3); 15 Array.set(anArray, 9, "疯狂的python讲义"); 16 String[][][] castArray = (String[][][]) array; 17 System.out.println(castArray[2][3][9]); 18 System.out.println(castArray[2][2][0]); 19 System.out.println(castArray[2][2][1]); 20 System.out.println(castArray[2][2][2]); 21 System.out.println(castArray[2][2][3]); 22 23 } 24 }
使用反射生成JDK动态代理
Proxy提供了用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类。如果在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态的创建实例。也可以使用Proxy来创建动态代理实例。
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 import java.lang.reflect.Proxy; 6 7 interface Person 8 { 9 void walk(); 10 11 void sayHello(String name); 12 } 13 14 class MyInvokationHandler implements InvocationHandler 15 { 16 /** 17 * 执行动态代理的所有方法时,都会白替换成执行如下的invoke方法 18 * 19 * @param proxy 代表动态代理对象 20 * @param method 代表正在执行的方法 21 * @param args 代表调用目标方法时传入的实参 22 * @return 23 * @throws Throwable 24 */ 25 @Override 26 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 27 { 28 System.out.println("----正在执行的方法:" + method); 29 if (args != null) 30 { 31 System.out.println("下面是执行该方法时传入的实参为:"); 32 for (Object val : args) 33 { 34 System.out.println(val); 35 } 36 } 37 else 38 { 39 System.out.println("调用该方法没有实参!"); 40 } 41 return null; 42 } 43 } 44 45 public class ProxyTest 46 { 47 public static void main(String[] args) 48 { 49 // 创建一个InvocationHandler对象 50 InvocationHandler handler = new MyInvokationHandler(); 51 // 使用指定的InvocationHandler来生成一个动态代理对象 52 Person person = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler); 53 // 调用动态代理对象的walk()和sayHello()方法 54 person.walk(); 55 person.sayHello("飞科剃须刀"); 56 } 57 }
运行结果如下:
1 ----正在执行的方法:public abstract void com.edu.ynu.java.learn.reflect.Person.walk() 2 调用该方法没有实参! 3 ----正在执行的方法:public abstract void com.edu.ynu.java.learn.reflect.Person.sayHello(java.lang.String) 4 下面是执行该方法时传入的实参为: 5 飞科剃须刀
不管程序是执行代理对象的walk()方法,还是执行代理对象的sayHello()方法,实际上都是执行InvocationHandler对象的invoke()方法
动态代理和AOP
在实际软件开发中,通常会存在相同的代码段重复出现的情况,在这种情况下,通常有两种做法
1、将相同的代码段“复制”,"粘贴"方式贴到不同位置
2、将相同的代码段独立出来,然后在不同的地方调用的相同的代码段
对于第一种方式,存在如果需要更改的代码,则任何被粘贴的地方都需要修改
对于第二种方式,存在公共代码块脱离了,需要的代码的地方与代码公共的地方耦合了,
而最理想的效果是:方法既能执行公共代码块,同时又无须以硬编码的方式直接调用公共代码块,对于JDK中的动态代理方法能够达到如下效果
1 package com.edu.ynu.java.learn.Proxy; 2 3 public interface Dog 4 { 5 // info()方法声明 6 void info(); 7 8 // run()方法声明 9 void run(); 10 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 public class GunDog implements Dog 4 { 5 //实现info()方法,仅仅打印一个字符串 6 @Override 7 public void info() 8 { 9 System.out.println("我是一个猎狗"); 10 } 11 12 // 实现run()方法 13 @Override 14 public void run() 15 { 16 System.out.println("我奔跑迅速"); 17 } 18 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 public class DogUtil 4 { 5 public void method1() 6 { 7 System.out.println("======模拟第一个通用方法====="); 8 } 9 10 public void method2() 11 { 12 System.out.println("=====模拟第二个通用方法======"); 13 } 14 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 public class MyInvokationHandler implements InvocationHandler 7 { 8 private Object target; 9 10 public void setTarget(Object target) 11 { 12 this.target = target; 13 } 14 15 @Override 16 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 17 { 18 DogUtil du = new DogUtil(); 19 du.method1(); 20 System.out.println(proxy.getClass()); 21 Object result = method.invoke(target, args); 22 du.method2(); 23 return result; 24 } 25 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 import java.lang.reflect.Proxy; 4 5 public class MyProxyFactory 6 { 7 public static Object getProxy(Object target) 8 { 9 MyInvokationHandler handler = new MyInvokationHandler(); 10 handler.setTarget(target); 11 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); 12 } 13 }
1 package com.edu.ynu.java.learn.Proxy; 2 3 4 5 public class Test 6 { 7 public static void main(String[] args) 8 { 9 Dog target=new GunDog(); 10 Dog dog= (Dog)MyProxyFactory.getProxy(target); 11 dog.info(); 12 dog.run(); 13 } 14 }
反射和泛型
从JDk 5以后,Java的Class类增加了泛型功能,从而允许使用泛型来限制Class类,例如,String.class的类型实际上是Class<String>。如果Class对应的类暂时未知,则使用Class<?>。通过在反射中使用泛型,可避免使用反射生成的对象需要强制类型转换
使用Class<T>泛型可以避免使用强制类型你给转换
1 ackage com.edu.ynu.java.learn.reflect; 2 3 public class CrazyitObjectFactory 4 { 5 public static Object getInstance(String clsName) 6 { 7 try 8 { 9 Class cls = Class.forName(clsName); 10 return cls.newInstance(); 11 } catch (ClassNotFoundException e) 12 { 13 e.printStackTrace(); 14 } catch (IllegalAccessException e) 15 { 16 e.printStackTrace(); 17 } catch (InstantiationException e) 18 { 19 e.printStackTrace(); 20 } 21 return null; 22 } 23 }
1 获取实例后需要强制类型转换
2 Date d=(Date)Crazyit.getInstance("java.util.Date");
甚至出现如下代码:
1 JFrame f=(JFrame)Crazyit.getInstance("java.util.Date");
如果将上面的CrazyitObjectFactory工厂类改写成泛型的Class,程序可以变为
1 package com.edu.ynu.java.learn.reflect; 2 3 import javax.swing.*; 4 import java.util.Date; 5 6 public class CrazyitObjectFactory2 7 { 8 public static <T> T getInstance(Class<T> cls) 9 { 10 try 11 { 12 return cls.newInstance(); 13 } catch (InstantiationException e) 14 { 15 e.printStackTrace(); 16 } catch (IllegalAccessException e) 17 { 18 e.printStackTrace(); 19 } 20 return null; 21 } 22 23 public static void main(String[] args) 24 { 25 //获取实例后无需类型转换 26 Date d = CrazyitObjectFactory2.getInstance(Date.class); 27 JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class); 28 } 29 }
示范泛型的优势,可以对Array的newInstance()方法进行包装
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class CrazyitArray 6 { 7 public static <T> T[] newInstance(Class<T> compponentType, int length) 8 { 9 return (T[]) Array.newInstance(compponentType, length); 10 } 11 12 public static void main(String[] args) 13 { 14 // 创建一维数组 15 String[] arr = CrazyitArray.newInstance(String.class, 10); 16 // 创建二维数组 17 int[][] intArr = CrazyitArray.newInstance(int[].class, 5); 18 arr[5] = "疯狂Java讲义"; 19 // 初始化二维数组 20 intArr[1] = new int[]{23, 12}; 21 System.out.println(arr[5]); 22 System.out.println(intArr[1][0]); 23 } 24 }
使用反射来获取泛型信息
为了获得指定成员变量的泛型类型,应先使用如下方法来获取成员变量的泛型类型
1 //获得成员变量f的泛型类型
2 Type gType=f.getGenericType();
然后将Type对象强制在类型转换为ParameterizedType对象,ParameterType代表被参数化的类型
1 package com.edu.ynu.java.learn.reflect; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.ParameterizedType; 5 import java.lang.reflect.Type; 6 import java.util.Map; 7 8 public class GenericTest 9 { 10 private Map<String, Integer> score; 11 12 public static void main(String[] args) throws NoSuchFieldException 13 { 14 Class<GenericTest> classs = GenericTest.class; 15 Field field = classs.getDeclaredField("score"); 16 // 直接使用getType()取出类型只对普通类型的成员变量有效 17 Class<?> a = field.getType(); 18 // 下面将看到仅输出java.util.Map 19 System.out.println("score的类型是:" + a); 20 // 获取成员变量f的泛型类型 21 Type gType = field.getGenericType(); 22 // 如果gType类型是ParameterizedType对象 23 if (gType instanceof ParameterizedType) 24 { 25 // 强制类型转换 26 ParameterizedType pType = (ParameterizedType) gType; 27 // 获取原始类型 28 Type rType = pType.getRawType(); 29 System.out.println("原始类型是:" + rType); 30 // 取得泛型类型的泛型参数 31 Type[] tArgs = pType.getActualTypeArguments(); 32 System.out.println("泛型信息是:"); 33 for (int i = 0; i < tArgs.length; i++) 34 { 35 System.out.println("第" + i + "个泛型类型是:" + tArgs[i]); 36 } 37 } 38 else 39 { 40 System.out.println("获取泛型类型出错!"); 41 } 42 } 43 }
Type也是java.lang.reflect包下的一个接口,该接口代表所有类型的公共高级接口,Class是Type接口的实现类。Type包括原始类型,参数化类型、数组类型、类型变量和基本类型。