标签:check 高级特性 代理模式 abs http 顺序 time() warning 工厂
@
系统可能在第一次使用某个类时加载该类, 也可能采用预加载机制来加载某个类。
当调用 java 命令运行某个 Java 程序时, 该命令将会启动一个 Java 虚拟机进程, 不管该 Java 程序有多么复杂, 该程序启动了多少个线程, 它们都处于该 Java 虚拟机进程里。 同一个 JVM的所有线程、 所有变量都处于同一个进程里, 它们都使用该 JVM 进程的内存区。
当系统出现以下几种情况时, JVM 进程将被终止:
从上面的介绍可以看出, 当 Java 程序运行结束时, JVM 进程结束, 该进程在内存中的状态将会丢失。
下面以类的类变量来说明这个问题。 下面程序先定义了一个包含类变量的类:
public class A
{
// 定义该类的类变量
public static int a = 6;
}
接下来定义一个类创建 A 类的实例, 并访问 A 对象的类变量 a:
public class ATest1
{
public static void main(String[] args)
{
// 创建A类的实例
A a = new A();
// 让a实例的类变量a的值自加
a.a ++;
System.out.println(a.a);
}
}
下面程序也创建 A 对象, 并访问其类变量 a 的值:
public class ATest2
{
public static void main(String[] args)
{
// 创建A类的实例
A b = new A();
// 输出b实例的类变量a的值
System.out.println(b.a);
}
}
在 ATestl.java 程序中创建了 A 类的实例, 并让该实例的类变量 a 的值自加, 程序输出该实例的类变量 a 的值将看到 7。 运行第二个程序 ATest2 时, 程序再次创建了 A 对象, 并输出 A 对象类变量的 a 的值, 此时 a 的值是多少呢? 结果依然是 6, 并不是 7。 这是因为运行 ATestl 和 ATest2 是两次运行 JVM 进程, 第一次运行 JVM 结束后, 它对 A 类所做的修改将全部丢失——第二次运行 JVM 时将再次初始化 A 类。
类的加载由类加载器完成, 类加载器通常由 JVM 提供, 这些类加载器也是前面所有程序运行的基础, JVM 提供的这些类加载器通常被称为系统类加载器。 除此之外, 开发者可以通过继承 ClassLoader基类来创建自己的类加载器。
通过使用不同的类加载器, 可以从不同来源加载类的二进制数据, 通常有如下几种来源。
类加载器通常无须等到“ 首次使用” 该类时才加载该类, Java 虚拟机规范允许系统预先加载某些类。
当类被加载之后, 系统为之生成一个对应的 Class 对象, 接着将会进入连接阶段, 连接阶段负责把类的二进制数据合并到 JRE 中。 类连接又可分为如下三个阶段:
?(1) 验证: 验证阶段用于检验被加载的类是否有正确的内部结构, 并和其他类协调一致。
?(2) 准备: 类准备阶段则负责为类的类变量分配内存, 并设置默认初始值。
?(3 ) 解析: 将类的二进制数据中的符号引用替换成直接引用。
在类的初始化阶段, 虚拟机负责对类进行初始化, 主要就是对类变量进行初始化。
在 Java 类中对类变量指定初始值有两种方式:
例如下面代码片段:
public class Test{
// 声明变量 a 时指定初始值
static int a = 5
static int b;
static int c;
static{
// 使用静态初始化块为变量 b 指定初始值
b=6;
}
}
对于上面代码, 程序为类变量 a、 b 都显式指定了初始值, 所以这两个类变量的值分别为 5、 6, 但类变量 c 则没有指定初始值, 它将采用默认初始值 0。
声明变量时指定初始值, 静态初始化块都将被当成类的初始化语句, JVM 会按这些语句在程序中的排列顺序依次执行它们, 例如下面的类:
public class Test
{
static
{
// 使用静态初始化块为变量b指定出初始值
b = 6;
System.out.println("----------");
}
// 声明变量a时指定初始值
static int a = 5;
static int b = 9; // ①
static int c;
public static void main(String[] args)
{
System.out.println(Test.b);
}
}
上面代码先在静态初始化块中为 b 变量赋值, 此时类变量 b 的值为 6; 接着程序向下执行, 执行到①号代码处, 这行代码也属于该类的初始化语句, 所以程序再次为类变量 b 赋值。 也就是说, 当 Test类初始化结束后, 该类的类变量 b 的值为 9。
JVM 初始化一个类包含如下几个步骤:
当 Java 程序首次通过下面 6 种方式来使用某个类或接口时, 系统就会初始化该类或接口:
类加载器负责将.class 文件( 可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的java.lang.Class 对象。
类加载器负责加载所有的类, 系统为所有被载入内存中的类生成java.lang.Class 实例。
一个类被载入 JVM 中, 同 一个类就不会被再次载入了——正如一个对象有一个唯一的标识一样, 一个载入 JVM 中的类也有一个唯一的标识。 在 Java 中, 一
个类用其全限定类名( 包括包名和类名) 作为标识; 但在 JVM 中, 一个类用其全限定类名和其类加载器作为唯一标识。 例如, 如果在 pg 的包中有一个名为 Person 的类, 被类加载器 ClassLoader 的实例 kl负责加载, 则该 Person 类对应的 Class 对象在 JVM 中表示为 Person、pg、kl ) 这意味着两个类加载器加载的同名类: (Person 、pg、 kl ) 和( Person、 pg、 kl2) 是不同的, 它们所加载的类也是完全不同、互不兼容的。
当 JVM 启动时, 会形成由三个类加载器组成的初始类加载器层次结构。
根类加载器(Bootstrap ClassLoader):其负责加载Java的核心类,比如String、System这些类
拓展类加载器(Extension ClassLoader):其负责加载JRE的拓展类库
系统类加载器(System ClassLoader):其负责加载CLASSPATH环境变量所指定的JAR包和类路径
除了可以使用 Java 提供的类加载器之外, 开发者也可以实现自己的类加载器, 自定义的类加载器通过继承 ClassLoader 来实现。
双亲委派模型:如上图所示的类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。子类加载器和父类加载器不是以继承(Inheritance)的关系来实现,而是通过组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
JVM 的类加载机制主要有如下三种:
下面程序示范了访问 JVM 的类加载器:
import java.util.*;
import java.net.*;
import java.io.*;
public class ClassLoaderPropTest {
public static void main(String[] args) throws IOException {
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:" + systemLoader);
/*
* 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定 如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为
* 系统类加载器的加载路径
*/
Enumeration<URL> em1 = systemLoader.getResources("");
while (em1.hasMoreElements()) {
System.out.println(em1.nextElement());
}
// 获取系统类加载器的父类加载器:得到扩展类加载器
ClassLoader extensionLader = systemLoader.getParent();
System.out.println("扩展类加载器:" + extensionLader);
System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的parent: " + extensionLader.getParent());
}
}
运行结果:
系统类加载器是 AppClassLoader 的实例, 扩展类加载器 PlatformClassLoader
的实例。 实际上, 这两个类都是 URLClassLoader 类的实例。
根类加载器并不是Java实现的,而且由于程序通常须访问根加载器,因此访问扩展类加载器的父类加载器时返回NULL。
类加载器加载 Class 大致要经过如下 8 个步骤(对应上面的双亲委派模型):
其中,第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass() 方法来实现自己的载入过程。
JVM 中除根类加载器之外的所有类加载器都是 ClassLoader 子类的实例, 开发者可以通过扩展ClassLoader 的子类, 并重写该 ClassLoader 所包含的方法来实现自定义的类加载器。 查阅 API 文档中关于 ClassLoader的方法不难发现, ClassLoader 中包含了大量的 protected 方法 这些方法都可被子类重写。
ClassLoader 类有如下两个关键方法:
loadClass()方法的执行步骤如下:
从上面步骤中可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托、缓冲机制两 种策略;如果重写loadClass()方法,则实现逻辑更为复杂。
在 ClassLoader 里还有一个核心方法:Class defineClass(String name, byte[] b, int off, int len),该方法 负责将指定类的字节码文件(即Class文件,如Hello.class)读入字节数组byte[] b内,并把它转换为 Class对象,该字节码文件可以来源于文件、网络等。
defineClass()方法管理JVM的许多复杂的实现,它负责将字节码分析成运行时数据结构,并校验有效性等。
除此之外,ClassLoader里还包含如下一些普通方法。
下面程序开发了一个自定义的ClassLoader,该ClassLoader通过重写findClass()方法来实现自定义 的类加载机制。这个ClassLoader可以在加载类之前先编译该类的源文件,从而实现运行Java之前先编 译该程序的目标,这样即可通过该ClassLoader直接运行Java源文件。
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
String fileName = root + File.separatorChar
+ className.replace(‘.‘, File.separatorChar) + ".class";
try {
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("E:\\temp");
Class<?> testClass = null;
try {
testClass = classLoader.loadClass("com.neo.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
Java 为 ClassLoader 提供了一个 URLClassLoader 实现类, 该类也是系统类加载器和扩展类加载器的父类( 此处的父类, 就是指类与类之间的继承关系)。 URLClassLoader 功能比较强大, 它既可以从本地文件系统获取二进制文件来加载类, 也可以从远程主机获取二进制文件来加载类。
在应用程序中可以直接使用 URLClassLoader 加载类, URLClassLoader 类提供了如下两个构造器:
一旦得到了 URLClassLoader 对象之后, 就可以调用该对象的 loadClass()方法来加载指定类。 下面程序示范了如何直接从文件系统中加载 MySQL 驱动, 并使用该驱动来获取数据库连接。 通过这种方式来获取数据库连接, 可以无须将 MySQL 驱动添加到 CLASSPATH 环境变量中。
import java.sql.*;
import java.util.*;
import java.net.*;
public class URLClassLoaderTest {
private static Connection conn;
// 定义一个获取数据库连接方法
public static Connection getConn(String url, String user, String pass) throws Exception {
if (conn == null) {
// 创建一个URL数组
URL[] urls = { new URL("file:mysql-connector-java-5.1.30-bin.jar") };
// 以默认的ClassLoader作为父ClassLoader,创建URLClassLoader
URLClassLoader myClassLoader = new URLClassLoader(urls);
// 加载MySQL的JDBC驱动,并创建默认实例
Driver driver = (Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").getConstructor().newInstance();
// 创建一个设置JDBC连接属性的Properties对象
Properties props = new Properties();
// 至少需要为该对象传入user和password两个属性
props.setProperty("user", user);
props.setProperty("password", pass);
// 调用Driver对象的connect方法来取得数据库连接
conn = driver.connect(url, props);
}
return conn;
}
public static void main(String[] args) throws Exception {
System.out.println(getConn("jdbc:mysql://localhost:3306/mysql", "root", "32147"));
}
}
Java 程序中的许多对象在运行时都会出现两种类型: 编译时类型和运行时类型, 例如代码: Person p=new Student();,这行代码将会生成一个 p 变量, 该变量的编译时类型为 Person,运行时类型为 Student;除此之外, 还有更极端的情形, 程序在运行时接收到外部传入的一个对象, 该对象的编译时类型是 Object,但程序又需要调用该对象运行时类型的方法。
在 Java 程序中获得 Class 对象通常有如下三种方式:
对于第一种方式和第二种方式都是直接根据类来取得该类的 Class 对象, 相比之下, 第二种方式有如下两种优势:
也就是说, 大部分时候都应该使用第二种方式来获取指定类的 Class 对象。 但如果程序只能获得一个字符串, 例如”java.lang.String”, 若需要获取该字符串对应的 Class 对象, 则只能使用第一种方式, 使用Class 的 forName(String clazzName)方法获取 Class 对象时, 该方法可能抛出一个 ClassNotFoundException异常。
一旦获得了某个类所对应的 Class 对象之后, 程序就可以调用 Class 对象的方法来获得该对象和该类的信息了。
Class 类提供了大量的实例方法来获取该 Class 对象所对应类的详细信息,Class 类大致包含如下方法, 下面每个方法都可能包括多个重载的版本, 应该参照官方API。
下面 4 个方法用于获取 Class 对应类所包含的构造器:
下面 4 个方法用于获取 Class 对应类所包含的方法:
如下 4 个方法用于访问 Class 对应类所包含的成员变量:
如下几个方法用于访问 Class 对应类上所包含的 Annotation:
如下方法用于访问该 Class 对象对应类包含的内部类:
如下方法用于访问该 Class 对象对应类所在的外部类:
如下方法用于访问该 Class 对象对应类所实现的接口:
如下几个方法用于访问该 Class 对象对应类所继承的父类:
如下方法用于获取 Class 对象对应类的修饰符、 所在包、 类名等基本信息:
除此之外, Class 对象还可调用如下几个判断方法来判断该类是否为接口、 枚举、 注解类型等:
上面的多个 getMethod()方法和 getConstructor()方法中, 都需要传入多个类型为 Class<?〉的参数, 用于获取指定的方法或指定的构造器。 关于这个参数的作用, 假设某个类内包含如下三个 info 方法签名:
在程序中获取该方法使用如下代码:
// 前一个参数指定方法名, 后面的个数可变的 Class 参数指定形参类型列表
clazz.getMethod("info" , String.class)
如果需要获取第三个 info 方法, 则使用如下代码:
// 前一个参数指定方法名, 后面的个数可变的 Class 参数指定形参类型列表
clazz.getMethod("info" , String.class, Integer.class)
下面程序示例了如何通过该 Class 对象来获取对应类的详细信息:
import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;
// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {
}
@Retention(value = RetentionPolicy.RUNTIME)
@interface Annos {
Anno[] value();
}
// 使用4个注解修饰该类
@SuppressWarnings(value = "unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest {
// 为该类定义一个私有的构造器
private ClassTest() {
}
// 定义一个有参数的构造器
public ClassTest(String name) {
System.out.println("执行有参数的构造器");
}
// 定义一个无参数的info方法
public void info() {
System.out.println("执行无参数的info方法");
}
// 定义一个有参数的info方法
public void info(String str) {
System.out.println("执行有参数的info方法" + ",其str参数值:" + str);
}
// 定义一个测试用的内部类
class Inner {
}
public static void main(String[] args) throws Exception {
// 下面代码可以获取ClassTest对应的Class
Class<ClassTest> clazz = ClassTest.class;
// 获取该Class对象所对应类的全部构造器
Constructor[] ctors = clazz.getDeclaredConstructors();
System.out.println("ClassTest的全部构造器如下:");
for (Constructor c : ctors) {
System.out.println(c);
}
// 获取该Class对象所对应类的全部public构造器
Constructor[] publicCtors = clazz.getConstructors();
System.out.println("ClassTest的全部public构造器如下:");
for (Constructor c : publicCtors) {
System.out.println(c);
}
// 获取该Class对象所对应类的全部public方法
Method[] mtds = clazz.getMethods();
System.out.println("ClassTest的全部public方法如下:");
for (Method md : mtds) {
System.out.println(md);
}
// 获取该Class对象所对应类的指定方法
System.out.println("ClassTest里带一个字符串参数的info()方法为:" + clazz.getMethod("info", String.class));
// 获取该Class对象所对应类的上的全部注解
Annotation[] anns = clazz.getAnnotations();
System.out.println("ClassTest的全部Annotation如下:");
for (Annotation an : anns) {
System.out.println(an);
}
System.out.println("该Class元素上的@SuppressWarnings注解为:"
+ Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
System.out.println("该Class元素上的@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
// 获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("ClassTest的全部内部类如下:");
for (Class c : inners) {
System.out.println(c);
}
// 使用Class.forName方法加载ClassTest的Inner内部类
Class inClazz = Class.forName("ClassTest$Inner");
// 通过getDeclaringClass()访问该类所在的外部类
System.out.println("inClazz对应类的外部类为:" + inClazz.getDeclaringClass());
System.out.println("ClassTest的包为:" + clazz.getPackage());
System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
}
}
API:java.lang.Class
Class 对象可以获得该类里的方法( 由 Method 对象表示)、 构造器( 由 Constructor 对象表示)、 成员变量( 由 Field 对象表示), 这三个类都位于 java.lang.reflect 包下, 并实现了 java.lang.reflect.Member接口。 程序可以通过 Method 对象来执行对应的方法, 通过 Constructor 对象来调用对应的构造器创建实例, 能通过 Field 对象直接访问并修改对象的成员变量值。
通过反射来生成对象需要先使用 Class 对象获取指定的 Constructor 对象, 再调用 Constructor 对象的 newlnstance()方法来创建该 Class 对象对应类的实例。 通过这种方式可以选择使用指定的构造器来创建实例。
在很多 Java EE (例如Spring)框架中都需要根据配置文件信息来创建 Java 对象,从配置文件读取的只是某个类的字符串类名, 程序需要根据该字符串来创建对应的实例, 就必须使用反射。
下面程序就实现了一个简单的对象池, 该对象池会根据配置文件读取 key-value 对, 然后创建这些对象, 并将这些对象放入一个 HashMap 中:
import java.util.*;
import java.io.*;
public class ObjectPoolFactory {
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String, Object> objectPool = new HashMap<>();
// 定义一个创建对象的方法
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName) throws Exception, IllegalAccessException, ClassNotFoundException {
// 根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.getConstructor().newInstance();
}
// 该方法根据指定文件来初始化对象池
// 它会根据配置文件来创建对象
public void initPool(String fileName)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try (FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames()) {
// 每取出一对key-value对,就根据value创建一个对象
// 调用createObject()创建对象,并将对象添加到对象池中
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (Exception ex) {
System.out.println("读取" + fileName + "异常");
}
}
public Object getObject(String name) {
// 从objectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String[] args) throws Exception {
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a")); // ①
System.out.println(pf.getObject("b")); // ②
}
}
程序调用 Class 对象的 newlnstance()方法即可创建一个 Java 对象。 程序中的 initPool()方法会读取属性文件, 对属性文件中每个 key-value 对创建一个 Java 对象, 其中 value 是该 Java 对象的实现类, 而 key 是该 Java 对象放入对象池中的名字。 为该程序提供如下属性配置文件:
a=java.util.Date
b=javax.swing.JFrame
如果不想利用默认构造器来创建 Java 对象, 而想利用指定的构造器来创建 Java 对象, 则需要利用Constructor 对象, 每个 Constructor 对应一个构造器。 为了利用指定的构造器来创建 Java 对象, 需要如下三个步骤:
下面程序利用反射来创建一个 JFrame 对象, 而且使用指定的构造器:
import java.lang.reflect.*;
public class CreateJFrame {
public static void main(String[] args) throws Exception {
// 获取JFrame对应的Class对象
Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
// 获取JFrame中带一个字符串参数的构造器
Constructor ctor = jframeClazz.getConstructor(String.class);
// 调用Constructor的newInstance方法创建对象
Object obj = ctor.newInstance("测试窗口");
// 输出JFrame对象
System.out.println(obj);
}
}
当获得某个类对应的 Class 对象后, 就可以通过该 Class 对象的 getMethods()方法或者 getMethod()方法来获取全部方法或指定方法—这两个方法的返回值是 Method 数组, 或者 Method 对象。每个 Method 对象对应一个方法, 获得 Method 对象后, 程序就可通过该 Method 来调用它对应的方法。 在 Method 里包含一个 invoke()方法, 该方法的签名如下:
下面程序对前面的对象池工厂进行加强, 允许在配置文件中增加配置对象的成员变量的值, 对象池工厂会读取为该对象配置的成员变量值, 并利用该对象对应的 setter 方法设置成员变量的值:
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
public class ExtendedObjectPoolFactory {
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String, Object> objectPool = new HashMap<>();
private Properties config = new Properties();
// 从指定属性文件中初始化Properties对象
public void init(String fileName) {
try (FileInputStream fis = new FileInputStream(fileName)) {
config.load(fis);
} catch (IOException ex) {
System.out.println("读取" + fileName + "异常");
}
}
// 定义一个创建对象的方法
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName) throws Exception {
// 根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.getConstructor().newInstance();
}
// 该方法根据指定文件来初始化对象池
// 它会根据配置文件来创建对象
public void initPool() throws Exception {
for (String name : config.stringPropertyNames()) {
// 每取出一个key-value对,如果key中不包含百分号(%)
// 这就表明是根据value来创建一个对象
// 调用createObject创建对象,并将对象添加到对象池中
if (!name.contains("%")) {
objectPool.put(name, createObject(config.getProperty(name)));
}
}
}
// 该方法将会根据属性文件来调用指定对象的setter方法
public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
for (String name : config.stringPropertyNames()) {
// 每取出一对key-value对,如果key中包含百分号(%)
// 即可认为该key用于控制调用对象的setter方法设置值
// %前半为对象名字,后半控制setter方法名
if (name.contains("%")) {
// 将配置文件中的key按%分割
String[] objAndProp = name.split("%");
// 取出调用setter方法的参数值
Object target = getObject(objAndProp[0]);
// 获取setter方法名:set + "首字母大写" + 剩下部分
String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1);
// 通过target的getClass()获取它的实现类所对应的Class对象
Class<?> targetClass = target.getClass();
// 获取希望调用的setter方法
Method mtd = targetClass.getMethod(mtdName, String.class);
// 通过Method的invoke方法执行setter方法
// 将config.getProperty(name)的值作为调用setter方法的参数
mtd.invoke(target, config.getProperty(name));
}
}
}
public Object getObject(String name) {
// 从objectPool中取出指定name对应的对象
return objectPool.get(name);
}
public static void main(String[] args) throws Exception {
ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
epf.init("extObj.txt");
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}
为上面程序提供如下配置文件:
a=javax.swing.JFrame
b=javax.swing.JLabel
#set the title of a
a%title=Test Title
通过 Class 对象的 getFields()或 getField()方法可以获取该类所包括的全部成员变量或指定成员变量。
Field 提供了如下两组方法来读取或设置成员变量值:
使用这两个方法可以随意地访问指定对象的所有成员变量, 包括 private 修饰的成员变量。
import java.lang.reflect.*;
class Person {
private String name;
private int age;
public String toString() {
return "Person[name:" + name + " , age:" + age + " ]";
}
}
public class FieldTest {
public static void main(String[] args) throws Exception {
// 创建一个Person对象
Person p = new Person();
// 获取Person类对应的Class对象
Class<Person> personClazz = Person.class;
// 获取Person的名为name的成员变量
// 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
Field nameField = personClazz.getDeclaredField("name");
// 设置通过反射访问该成员变量时取消访问权限检查
nameField.setAccessible(true);
// 调用set()方法为p对象的name成员变量设置值
nameField.set(p, "Yeeku.H.Lee");
// 获取Person类名为age的成员变量
Field ageField = personClazz.getDeclaredField("age");
// 设置通过反射访问该成员变量时取消访问权限检查
ageField.setAccessible(true);
// 调用setInt()方法为p对象的age成员变量设置值
ageField.setInt(p, 30);
System.out.println(p);
}
}
在 java.lang.reflect 包下还提供了一个 Array 类, Array 对象可以代表所有的数组。 程序可以通过使用 Array 来动态地创建数组, 操作数组元素等。
Array 提供了如下几类方法:
下面程序示范了如何使用 Array 来生成数组, 为指定数组元素赋值, 并获取指定数组元素的方式:
import java.lang.reflect.*;
public class ArrayTest1 {
public static void main(String args[]) {
try {
// 创建一个元素类型为String ,长度为10的数组
Object arr = Array.newInstance(String.class, 10);
// 依次为arr数组中index为5、6的元素赋值
Array.set(arr, 5, "疯狂Java讲义");
Array.set(arr, 6, "轻量级Java EE企业应用实战");
// 依次取出arr数组中index为5、6的元素的值
Object book1 = Array.get(arr, 5);
Object book2 = Array.get(arr, 6);
// 输出arr数组中index为5、6的元素
System.out.println(book1);
System.out.println(book2);
} catch (Throwable e) {
System.err.println(e);
}
}
}
代理分为静态代理和动态代理,静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。
Proxy 提供了如下两个方法来创建动态代理类和动态代理实例:
实际上, 即使采用第一个方法生成动态代理类之后, 如果程序需要通过该代理类来创建对象, 依然需要传入一个 InvocationHandler 对象。 也就是说, 系统生成的每个代理对象都有一个与之关联的InvocationHandler 对象。
程序中可以采用先生成一个动态代理类, 然后通过动态代理类来创建代理对象的方式生成一个动态代理对象。 代码片段如下:
// 创建一个 InvocationHandler 对象
InvocationHandler handler = new MylnvocationHandler ( ...);
// 使 用 Proxy 生成一个动态代理类 proxyClass
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader()
, new Class[] { Foo.class });
// 会取 proxyClass_中带一个
Constructor ctor = proxyClass. getConstructor(new Class[]
InvocationHandler 参数的构造器
{ InvocationHandler?class });
// 调用 ctor 的 newlnstance 方法来创建动态实例
Foo f (Foo)ctor.newlnstance(new Object[]{handler});
上面代码也可以简化成如下代码:
// 创建一个 InvocationHandler 对象
InvocationHandler handler = new MyInvocationHandler(...);
// 使用 Proxy 直接生成一个动态代理对象
Foo f = (Foo)Proxy.newProxyInstance(Foo.class.getClassLoader()
, new Class[]{Foo.class} , handler);
下面程序示范了使用 Proxy 和 InvocationHandler 来生成动态代理对象:
import java.lang.reflect.*;
interface Person {
void walk();
void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler {
/*
* 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法 其中: proxy:代表动态代理对象 method:代表正在执行的方法
* args:代表调用目标方法时传入的实参。
*/
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("----正在执行的方法:" + method);
if (args != null) {
System.out.println("下面是执行该方法时传入的实参为:");
for (Object val : args) {
System.out.println(val);
}
} else {
System.out.println("调用该方法没有实参!");
}
return null;
}
}
public class ProxyTest {
public static void main(String[] args) throws Exception {
// 创建一个InvocationHandler对象
InvocationHandler handler = new MyInvokationHandler();
// 使用指定的InvocationHandler来生成一个动态代理对象
Person p = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[] { Person.class },
handler);
// 调用动态代理对象的walk()和sayHello()方法
p.walk();
p.sayHello("孙悟空");
}
}
上面程序首先提供了一个 Person 接口, 该接口中包含了 walk()和 sayHello()两个抽象方法, 接着定义了一个简单的 InvocationHandler 实现类, 定义该实现类时需要重写 invoke()方法—调用代理对象的所有方法时都会被替换成调用该 invoke()方法。 该 invoke()方法中的三个参数解释如下。
API:java.lang.reflect.Proxy
API:java.lang.reflect.InvocationHandler
根据前面介绍的 Proxy 和 InvocationHandler, 实在很难看出这种动态代理的优势。 下面介绍一种更实用的动态代理机制。
开发实际应用的软件系统时, 通常会存在相同代码段重复出现的情况, 在这种情况下, 对于许多刚开始从事软件开发的人而言, 他们的做法是: 选中那些代码, 一路“ 复制“ 、“ 粘贴”, 立即实现了系统功能, 如果仅仅从软件功能上来看, 他们确实己经完成了软件开发。通过这种“ 复制”、 “ 粘贴” 方式开发出来的软件如图a所示。
在这种情况下, 大部分稍有经验的开发者都会将这段深色代码段定义成一个方法, 然后让另外三段代码段直接调用该方法即可。 在这种方式下, 软件系统的结构如图 b 所示。
对于如图 b 所示的软件系统, 如果需要修改深色部分的代码, 则只要修改一个地方即可, 而调用该方法的代码段, 不管有多少个地方调用了该方法, 都完全无须任何修改, 只要被调用方法被修改了,所有调用该方法的地方就会自然改变——通过这种方式, 大大降低了软件后期维护的复杂度。
但采用这种方式来实现代码复用依然产生一个重要问题: 代码段 1、 代码段 2、 代码段 3 和深色代码段分离开了, 但代码段 1、 代码段 2 和代码段 3 又和一个特定方法耦合了! 最理想的效果是: 代码块1、 代码块 2 和代码块 3 既可以执行深色代码部分, 又无须在程序中以硬编码方式直接调用深色代码的方法, 这时就可以通过动态代理来达到这种效果。
由于 JDK 动态代理只能为接口创建动态代理, 所以下面先提供一个 Dog 接口, 该接口代码非常简单, 仅仅在该接口里定义了两个方法。
public interface Dog {
// info方法声明
void info();
// run方法声明
void run();
}
上面接口里只是简单地定义了两个方法, 并未提供方法实现。 如果直接使用 Proxy 为该接口创建动态代理对象, 则动态代理对象的所有方法的执行效果又将完全一样。 实际情况通常是, 软件系统会为该Dog 接口提供一个或多个实现类。 此处先提供一个简单的实现类: GunDog:
public class GunDog implements Dog {
// 实现info()方法,仅仅打印一个字符串
public void info() {
System.out.println("我是一只猎狗");
}
// 实现run()方法,仅仅打印一个字符串
public void run() {
System.out.println("我奔跑迅速");
}
}
Dog 的实现类为每个方法提供了一个简单实现。 再看需要实现的功能: 让代码段 1、 代码段 2 和代码段 3 既可以执行深色代码部分, 又无须在程序中以硬编码方式直接调用深色代码的方法。 此处假设 info()、 run()两个方法代表代码段 1、 代码段 2, 那么要求: 程序执行 info()、 nm()方法时能调用某个通用方法, 但又不想以硬编码方式调用该方法。
下面提供一个 DogUtil类, 该类里包含两个通用方法:
public class DogUtil {
// 第一个拦截器方法
public void method1() {
System.out.println("=====模拟第一个通用方法=====");
}
// 第二个拦截器方法
public void method2() {
System.out.println("=====模拟通用方法二=====");
}
}
借助于 Proxy 和 InvocationHandler 就可以实现 当程序调用 info()方法和 run()方法时, 系统可以“自动” 将 methodl()和 method2()两个通用方法插入 info()和 run()方法中执行。
这个程序的关键在于下面的 MylnvokationHandler 类, 该类是一个 InvocationHandler 实现类, 该实现类的 invoke()方法将会作为代理对象的方法实现:
import java.lang.reflect.*;
public class MyInvokationHandler implements InvocationHandler {
// 需要被代理的对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
DogUtil du = new DogUtil();
// 执行DogUtil对象中的method1。
du.method1();
// 以target作为主调来执行method方法
Object result = method.invoke(target, args);
// 执行DogUtil对象中的method2。
du.method2();
return result;
}
}
上面程序实现 invoke()方法时包含了一行关键代码, 这行代码通过反射以 target作为主调来执行 method 方法, 这就是回调了 target 对象的原有方法。 在粗体字代码之前调用 DogUtil对象的 methodl()方法, 在粗体字代码之后调用 DogUtil 对象的 method2()方法。
下面再为程序提供一个 MyProxyFactory 类, 该对象专为指定的 target 生成动态代理实例:
import java.lang.reflect.*;
public class MyProxyFactory {
// 为指定target生成动态代理对象
public static Object getProxy(Object target) throws Exception {
// 创建一个MyInvokationHandler对象
MyInvokationHandler handler = new MyInvokationHandler();
// 为MyInvokationHandler设置target对象
handler.setTarget(target);
// 创建、并返回一个动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
}
}
上面的动态代理工厂类提供了一个 getProxy()方法, 该方法为 target 对象生成一个动态代理对象,这个动态代理对象与 target 实现了相同的接口, 所以具有相同的 public 方法—从这个意义上来看, 动态代理对象可以当成 target 对象使用。 当程序调用动态代理对象的指定方法时,实际上将变为执行MylnvokationH andler 对象的 invoke()方法。
例如, 调用动态代理对象的 info()方法, 程序将幵始执行invoke()方法, 其执行步骤如下:
通过上面的执行过程可以发现: 当使用动态代理对象来代替 target 对象时, 代理对象的方法就实现了前面的要求—程序执行 info()、 nm()方法时既能“ 插入” methodl()、 method2()通用方法,但 GunDog 的方法中又没有以硬编码方式调用 methodl()和 method2()方法。
以一个主程序来测试这种动态代理的效果。
public class Test {
public static void main(String[] args) throws Exception {
// 创建一个原始的GunDog对象,作为target
Dog target = new GunDog();
// 以指定的target来创建动态代理
Dog dog = (Dog) MyProxyFactory.getProxy(target);
dog.info();
dog.run();
}
}
上面程序中的 dog 对象实际上是动态代理对象, 只是该动态代理对象也实现了 Dog 接口, 所以也可以当成 Dog 对象使用。 程序执行 dog 的 info()和 run()方法时, 实际上会先执行 DogUtil 的 method1()方法, 再执行 target 对象的 info()和 run()方法, 最后执行 DogUtil 的 method2()方法。
运行上面程序, 会看到如图 c 所示的运行结果:
这种动态代理在 AOP ( Aspect Orient Programming, 面向切面编程) 中被称为 AOP 代理, AOP 代理可代替目标对象, AOP 代理包含了目标对象的全部方法。 但 AOP 代理中的方法与目标对象的方法存在差异: AOP 代理里的方法可以在执行目标方法之前、 之后插入一些通用处理。
AOP 代理包含的方法与目标对象包含的方法示意图如图 d所示。
CGLIB动态代理:
JDK中提供的生成动态代理类的机制有个鲜明的特点是:
某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。
比如:如果上面例子的GunDog实现了继承自Dog接口的方法外,另外实现了方法eat(),则在产生的动态代理类中不会有这个方法了!更极端的情况是:如果某个类没有实现接口,那么这个类就不能用JDK产生动态代理了!
这时候就应该引入CGLIB动态代理了,“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”
CGLIB创建某个类A的动态代理类的模式是:
?1.查找A上的所有非final的public类型的方法定义;
?2.将这些方法的定义转换成字节码;
?3.将组成的字节码转换成相应的代理的class对象;
?4.实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)
参考:
【1】:程序员圈子里,有哪些高颜值程序媛? ?? 路人甲的回答
【2】:程序员圈子里,有哪些高颜值程序媛?
【3】:身边有个漂亮的女程序员是什么体验?
【3】:《疯狂Java讲义》
【4】:《深入理解Java虚拟机:JVM高级特性与最佳实践》
【5】:JVM内存模型——JAVA的根基
【6】:《揭秘Java虚拟机-JVM设计原理与实现》
【7】:纯洁的微笑:Jvm 系列(一):Java 类的加载机制
【8】:JAVA类加载机制全解析
【9】:廖雪峰的官方网站:Class类
【10】:Java技术驿站:Java基础系列-静态代理和动态代理
【11】:廖雪峰的官方网站:动态代理
【12】:Spring系列之IOC的原理及手动实现
【13】:菜鸟教程:工厂模式
【14】:犀利豆的博客:徒手撸框架--实现IOC
【15】:Java代理模式
【16】:Java的三种代理模式
【17】:从代理机制到Spring AOP
标签:check 高级特性 代理模式 abs http 顺序 time() warning 工厂
原文地址:https://www.cnblogs.com/three-fighter/p/13053111.html