【java】解析java类加载与反射机制

目录结构:

contents structure [+]

在这篇文章中,笔者将会结合自己的理解,以及java疯狂讲义,记录下java的类加载和反射机制,希望能够对你有所帮助。

1.类的加载、连接和初始化

当我们运行一个java程序时,会启动一个JVM虚拟机进程,java的虚拟机进程之间是不能够交换数据的。同一个JVM的所有线程,所有变量都处于同一个进程里,他们都使用该JVM的内存区。

当程序使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对类进行初始化。如果没有意外,jvm将会连续完成这三个步骤,有时也把这三个步骤统称为类的加载和类初始化。
【java】解析java类加载与反射机制

1.1 类的加载

类加载指的是将类的class文件读入内存,并且为之创建一个java.lang.Class对象,也就是说当程序中使用任何类时,都会为之创建一个java.lang.Class对象。
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建类加载器。

1.2 类的连接

当类被加载之后,系统会为之生成一个Class对象,接着将进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。连接阶段分为三个步骤:
a.验证:验证阶段用于验证被加载的类是否有正确的内部结构,并和其他类协调一致。
b.准备:准备阶段负责为类变量分配内存,并且设置默认的初始值。
c.解析:解析阶段会让JVM检查类文件中所引用的类型是否都是已知的类型,如果有运行时未知的类,那么它们也会被加载进来。

1.3 类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化,在java类中对类变量进行初始化有两种方式:1.声明类变量时指定初始值。2.使用静态初始化块对类变量指定初始值。
类变量和静态初始化块的执行优先级是同级的,例如:

public class Test {
static{
n=10;
}
static int n=9;
public static void main(String[] args) {
System.out.println("n="+n);//
}
}

这里的输出值是9,并非10,因为静态初始化块和类变量是同级的。当程序加载、连接完成以后,进入准备初始化阶段,这个时候n为0,程序按照先后循序首先使用10为n赋值,然后再使用9为n赋值。

经过上面的介绍,我们已经知道一个可使用的对象需要经历加载、链接、初始化。在类加载的时候,需要使用类加载器,介绍继续介绍类加载器的作用。

2.类加载器

类加载器负责将.class文件加载到内存中,并为之生成java.lang.Class对象。

2.1 类加载器机制

当JVM启动时,会形成由三个类加载器组成的类加载器结构。
Bootstrap ClassLoader:根类加载器
Extension ClassLoader:扩展类加载器
System ClassLoader:系统类加载器
如果用户自定义了类加载器,那么还有用户类加载器。所有的类加载器都是java.lang.ClassLoader的子类(除了根类加载器,因为它是由JVM实现的),并且这些类加载器是具有层次结构的。

如图:
【java】解析java类加载与反射机制

根类加载器:负责加载java的核心类。
扩展类加载器:负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或者java.ext.dirs指定的目录)中的jar包。
系统类加载器:负责在JVM启动时,加载来自java命令的-classpath选项,或是java.class.path系统属性,或是CLASSPATH环境变量所指定的JAR包和类路径。

    public static void main(String[] args) throws IOException {
System.out.println("//系统类加载器");
ClassLoader systemLoader=ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器:"+systemLoader);
/* 获得系统类加载器的加载路径
* 通常为CLASSPATH环境变量指定的值。
* 如果没有指定CLASSPATH环境变量,那么默认以当前路径作为系统类的加载路径。
*/
Enumeration<URL> em1=systemLoader.getResources(".");
while(em1.hasMoreElements()){
System.out.println(em1.nextElement());
} System.out.println("//扩展类加载器");
ClassLoader extensionLoader=systemLoader.getParent();//系统类加载器的父类就是扩展类加载器
System.out.println("扩展类加载器:"+extensionLoader);
System.out.println("扩展类加载器的加载路径:"+System.getProperty("java.ext.dirs"));
System.out.println("扩展类加载器的父加载器:"+extensionLoader.getParent());//获得根加载器 System.out.println("//根类加载器");
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(int i=0;i<urls.length;i++){
System.out.println(urls[i].toExternalForm());
}
}

打印结果为:

//系统类加载器
系统类加载器:sun.misc.Launcher$AppClassLoader@addbf1
file:/D:/workspace/pc141/Test/bin/
//扩展类加载器
扩展类加载器:sun.misc.Launcher$ExtClassLoader@42e816
扩展类加载器的加载路径:D:\App\jdk\jre1.6\lib\ext;C:\Windows\Sun\Java\lib\ext
扩展类加载器的父加载器:null
//根类加载器
file:/D:/App/jdk/jre1.6/lib/resources.jar
file:/D:/App/jdk/jre1.6/lib/rt.jar
file:/D:/App/jdk/jre1.6/lib/sunrsasign.jar
file:/D:/App/jdk/jre1.6/lib/jsse.jar
file:/D:/App/jdk/jre1.6/lib/jce.jar
file:/D:/App/jdk/jre1.6/lib/charsets.jar
file:/D:/App/jdk/jre1.6/lib/modules/jdk.boot.jar
file:/D:/App/jdk/jre1.6/classes

从上面的打印结果可以看出,系统类加载器的父加载器是扩展类加载器,扩展类类加载器的父加载器是根加载器,但是为什么调用扩展类加载器的getParent()却返回null呢?这是因为根加载器比较特殊,它不是ClassLoader的子类。

2.2 自定义类加载器

JVM中,除了根类加载器之外的所有加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写ClassLoader所包含的方法来实现自定义类加载器。
因此需要实现自定义加载器,只需要继承自ClassLoader即可。接下来讲解一下ClassLoader的两个关键方法:

protected Class<?> loadClass(String name,boolean resolve):该方法是ClassLoader的入口点,根据指定名称来加载类。该方法的加载过程会经历如下三个步骤:
a.调用findLoadedClass(String)检查当前类是否被加载。
b.调用该类加载器的父类加载器的loadClass方法,如果父类加载器为空,则调用根类加载器。
c.调用findClass查找类。

protected Class<?> findClass(String name):根据指定名称操作类。

自定义的加载器,可以通过重写上面两个方法来实现,通常推荐重写findClass方法。

下面是类加载器的案例,使用该类加载器加载目标类,不需要对目标类进行编译即可运行:

public class CompileClassLoader extends ClassLoader {
private byte[] getBytes(String fileName) throws IOException{
File file=new File(fileName);
long length=file.length();
byte[] raw=new byte[(int)length];
FileInputStream fin=null;
fin=new FileInputStream(file);
int r=fin.read(raw);
if(r!=length){
throw new IOException("无法读取全部文件");
}
fin.close();
return raw; } //定义编译java的指定方法
private boolean compile(String javaFile) throws IOException{
System.out.println("CompileClassLoader:正在编译"+javaFile+"...");
//调用系统的javac命令
Process p=Runtime.getRuntime().exec("javac "+javaFile); try {
p.waitFor();//让其他线程等待这个执行完毕
} catch (InterruptedException e) {
System.out.println(e);
} //获取线程的退出值
int res=p.exitValue();
//判断编译是否成功
return res==0;
} @Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz=null;
//将包路径中的点(.)替换为斜线(/)
String fileStub=name.replace(".", "/");
String javaFileName=fileStub+".java";
String classFileName=fileStub+".class";
File javaFile=new File(javaFileName);
File classFile=new File(classFileName);
//当java源文件存在,且class文件不存在,或者Java源文件的修改时间比Class文件的修改时间晚,则重新编译
if(javaFile.exists() && (!classFile.exists() || javaFile.lastModified()>classFile.lastModified())){
try{
if(!compile(javaFileName) || !classFile.exists()){
throw new ClassNotFoundException("ClassNotFoundException:"+javaFileName);
}
}catch(Exception e){
e.printStackTrace();
}
}
//如果class文件存在,系统负责将该文件转化为class文件
if(classFile.exists()){
try{
//将class文件的二进制数据读入数组
byte[] raw=getBytes(classFileName);
//调用ClassLoader的defineClass方法将二进制数据转化为Class对象
clazz=defineClass(name, raw, 0,raw.length);
}catch(Exception e){
e.printStackTrace();
}
}
if(clazz==null){
throw new ClassNotFoundException(name);
}
return clazz;
}
public static void main(String[] args) throws Exception{
if(args.length<1){
System.out.println("缺少目标类,请按如下格式运行java源文件");
System.out.println("java CompileClassLoader ClassName");
return;
}
//第一个参数是需要运行的类
String progClass=args[0];
//剩下的参数作为运行目标类的参数
String[] progArgs=new String[args.length-1];
System.arraycopy(args, 1, progArgs, 0, args.length-1); CompileClassLoader ccl=new CompileClassLoader();
//加载需要运行的类
Class<?> clazz=ccl.loadClass(progClass);
Method main=clazz.getMethod("main", (new String[0]).getClass());
Object progArr=progArgs;
main.invoke(main, progArr);
}
}

接下来提供一个简单的主类:

public class Hello
{
public static void main(String[] args){
System.out.println("hello");
}
}

可以无需编译Hello.java,直接使用如下命令运行:

java CompileClassLoader Hello

上面的类加载器提供了在运行程序之前先编译源代码的功能。

2.3 URLClassLoader类

Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类。URLClassLoader既可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件来加载类。
该类提供了如下的构造函数:
URLClassLoader(URL[] urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的系列路径来查询并加载类。
URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父类加载器创建一个ClassLoader对象。

得到ClassLoader的对象后,就可以通过loaderClass方法来加载指定类:
例如下面的代码是获取数据库的连接:

public class Test3 {
private static Connection conn; public static Connection getConn(String url,String user,String pass) throws Exception{
if(conn==null){
URL[] urls={new URL("file:mysql-connector-java-5.1.30.jar")}; URLClassLoader myClassLoader=new URLClassLoader(urls); Driver driver=(Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance(); Properties props=new Properties();
props.setProperty("user",user);
props.setProperty("password",pass); conn=driver.connect(url, props);
}
return conn;
}
public static void main(String[] args){
getConn("jdbc:mysql://localhost:3306/mysql","root","1234");
}
}

上面程序中,创建URLClassLoader时传入一个URL数组参数,该ClassLoader就可以从这系列URL指定的资源中加载指定类,这里的URL可以以file:为前缀,表明从本地加载;可以以http:为前缀,表明从互联网通过HTTP加载;也可以通过ftp:为前缀,表明从互联网FTP访问来加载。

经过笔者的测试发现,如果使用URLClassLoader从远程互联网上加载JAR包,并不会下载该JAR到本地,然后再运行。它是直接将JAR中的数据解析为内存的中的数据,因此通过URLClassLoader从远程加载JAR包的操作是比较安全的。

3.反射的常规操作

3.1 获取Class对象的方式

a.使用数据类型.class的方式可以获取该类型的Class对象。
       b.使用对象.getClass()的方式可以获取该类型的Class对象。
       c.使用包装类的TYPE属性获取该包装类对应基本数据类型的Class对象。
       d.使用Class类的forName()方法获取参数类型的Class对象。

3.2 反射机制的相关API

通过一个对象获得完整的包名和类名

package net.xsoftlab.baike;
public class TestReflect {
public static void main(String[] args) throws Exception {
TestReflect testReflect = new TestReflect();
System.out.println(testReflect.getClass().getName());
// 结果 net.xsoftlab.baike.TestReflect
}
}

实例化Class类对象

package net.xsoftlab.baike;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> class1 = null;
Class<?> class2 = null;
Class<?> class3 = null;
// 一般采用这种形式
class1 = Class.forName("net.xsoftlab.baike.TestReflect");
class2 = new TestReflect().getClass();
class3 = TestReflect.class;
System.out.println("类名称 " + class1.getName());
System.out.println("类名称 " + class2.getName());
System.out.println("类名称 " + class3.getName());
}
}

获取一个对象的父类与实现的接口

package net.xsoftlab.baike;
import java.io.Serializable;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
// 取得父类
Class<?> parentClass = clazz.getSuperclass();
System.out.println("clazz的父类为:" + parentClass.getName());
// clazz的父类为: java.lang.Object
// 获取所有的接口
Class<?> intes[] = clazz.getInterfaces();
System.out.println("clazz实现的接口有:");
for (int i = 0; i < intes.length; i++) {
System.out.println((i + 1) + ":" + intes[i].getName());
}
// clazz实现的接口有:
// 1:java.io.Serializable
}
}

获取某个类中的全部构造函数 - 详见下例
通过反射机制实例化一个类的对象

package net.xsoftlab.baike;
import java.lang.reflect.Constructor;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> class1 = null;
class1 = Class.forName("net.xsoftlab.baike.User");
// 第一种方法,实例化默认构造方法,调用set赋值
User user = (User) class1.newInstance();
user.setAge(20);
user.setName("Rollen");
System.out.println(user);
// 结果 User [age=20, name=Rollen]
// 第二种方法 取得全部的构造函数 使用构造函数赋值
Constructor<?> cons[] = class1.getConstructors();
// 查看每个构造方法需要的参数
for (int i = 0; i < cons.length; i++) {
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.print("cons[" + i + "] (");
for (int j = 0; j < clazzs.length; j++) {
if (j == clazzs.length - 1)
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + ",");
}
System.out.println(")");
}
// 结果
//cons[0] (int,java.lang.String)
//cons[1] (java.lang.String)
// cons[2] ()
user = (User) cons[0].newInstance(21, "Rollen");
System.out.println(user);
// 结果 User [age=0, name=Rollen]
user = (User) cons[1].newInstance("jame");
System.out.println(user);
// 结果 User [age=20, name=Rollen]
}
}
class User {
private int age;
private String name;
public User() {
super();
}
public User(String name) {
super();
this.name = name;
}
public User(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}

获取某个类的全部属性

package net.xsoftlab.baike;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
System.out.println("===============本类属性===============");
// 取得本类的全部属性
Field[] field = clazz.getDeclaredFields();
for (int i = 0; i < field.length; i++) {
// 权限修饰符
int mo = field[i].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = field[i].getType();
System.out.println(priv + " " + type.getName() + " " + field[i].getName() + ";");
} System.out.println("==========实现的接口或者父类的属性==========");
// 取得实现的接口或者父类的属性
Field[] filed1 = clazz.getFields();
for (int j = 0; j < filed1.length; j++) {
// 权限修饰符
int mo = filed1[j].getModifiers();
String priv = Modifier.toString(mo);
// 属性类型
Class<?> type = filed1[j].getType();
System.out.println(priv + " " + type.getName() + " " + filed1[j].getName() + ";");
}
}
}

获取某个类的全部方法

package net.xsoftlab.baike;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestReflect implements Serializable {
private static final long serialVersionUID = -2862585049955236662L;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
Method method[] = clazz.getMethods();
for (int i = 0; i < method.length; ++i) {
Class<?> returnType = method[i].getReturnType();
Class<?> para[] = method[i].getParameterTypes();
int temp = method[i].getModifiers();
System.out.print(Modifier.toString(temp) + " ");
System.out.print(returnType.getName() + " ");
System.out.print(method[i].getName() + " ");
System.out.print("(");
for (int j = 0; j < para.length; ++j) {
System.out.print(para[j].getName() + " " + "arg" + j);
if (j < para.length - 1) {
System.out.print(",");
}
}
Class<?> exce[] = method[i].getExceptionTypes();
if (exce.length > 0) {
System.out.print(") throws ");
for (int k = 0; k < exce.length; ++k) {
System.out.print(exce[k].getName() + " ");
if (k < exce.length - 1) {
System.out.print(",");
}
}
} else {
System.out.print(")");
}
System.out.println();
}
}
}

获得某个类的所有构造方法

           Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
Constructor[] constructors= clazz.getConstructors();
for(int i=0;i<constructors.length;i++){
StringBuilder sb=new StringBuilder();
Constructor contructor=constructors[i];
int modifier_id= contructor.getModifiers();
String modifier= Modifier.toString(modifier_id);
sb.append(modifier);
String name=contructor.getName();
sb.append(" "+name);
Class[] params= contructor.getParameterTypes();
sb.append(" (");
for(int param=0;param<params.length;param++){
Class c=params[param];
if(param!=params.length-1){
sb.append(c.getName()+" param"+(param+1)+",");
}else{
sb.append(c.getName()+" param"+(param+1));
}
}
sb.append(");");
System.out.println(sb.toString());
}

通过反射机制调用某个类的方法

package net.xsoftlab.baike;
import java.lang.reflect.Method;
public class TestReflect {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
// 调用TestReflect类中的reflect1方法
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());
// Java 反射机制 - 调用某个类的方法1.
// 调用TestReflect的reflect2方法
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");
// Java 反射机制 - 调用某个类的方法2.
// age -> 20. name -> 张三
}
public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}
public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}
}

通过反射机制操作某个类的属性

package net.xsoftlab.baike;
import java.lang.reflect.Field;
public class TestReflect {
private String proprety = null;
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("net.xsoftlab.baike.TestReflect");
Object obj = clazz.newInstance();
// 可以直接对 private 的属性赋值
Field field = clazz.getDeclaredField("proprety");
field.setAccessible(true);
field.set(obj, "Java反射机制");
System.out.println(field.get(obj));
}
}

4.使用反射生成动态代理

在java.lang.reflect包下提供了一个Proxy类和InvocationHandler接口,使用它们可以生成动态代理类和代理对象。

4.1 使用Proxy和InvocationHandler创建动态代理

Proxy提供了用于创建动态代理类和代理对象的静态方法,它是所有动态代理类的父类。如果需要为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类;如果需要为一个或多个接口动态创建实例,可以通过Proxy来创建动态代理实例。

Proxy提供了如下两个方法来动态创建代理类和动态代理实例:
static Class<?> getProxyClass(ClassLoader loader,Class<?>... interface):创建一个动态代理类所对应的Class对象,该代理类将实现interface所指定的所有接口。
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h):创建一个动态代理对象,该代理对象的实现类实现了interface所指定的接口,执行代理对象的每个方法都会被替换为执行InvocationHandler对象的invoke方法。

接下里是使用getProxyClass获得动态代理类,然后通过动态代理类再创建动态代理对象。

//创建一个InvocationHandler对象
InvocationHanler handler=new InvocationHandler(...);
//使用Proxy生成一个动态代理类
Class proxyClass=Proxy.getProxyClass(Foo.class.getClassLoader(),new Class[]{Foo.class});
//获取proxyClass类中带一个InvocationHandler参数的构造器
Constructor ctor=proxyClass.getConstructor(new Class[]{InvocationHandler.class});
//调用ctor的newInstance来创建动态实例
Foo f=(Foo)ctor.newInstance(new Object[]{handler});

上面的代码等价于:

//创建一个InvocationHandler对象
InvocationHanler handler=new InvocationHandler(...);
//使用Proxy生成一个动态代理对象
Foo f=(Foo)Proxy.newInstance(Foo.class.getClassLoader(),new Class[]{Foo.class},handler);

接下来是一个完整的栗子:

interface Person{
void walk();
void sayHello(String name);
}
class MyInvacationHandler implements InvocationHandler {
/**
* 执行动态代理对象的所有方法都会被替换为执行如下的invoke方法
* 其中:
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("method:"+method.toGenericString());
if(args==null){
System.out.println("没有参数");
}else{
System.out.println(Arrays.toString(args));
}
return 1;
}
}
public class ProxyTest {
public static void main(String[] args) {
//创建一个InvocationHandler对象
InvocationHandler invokeH=new MyInvacationHandler();
//使用指定的InvocationHandler来生成一个动态代理对象
Person p=(Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, invokeH);
//调用动态代理的walk和sayHello方法
p.walk();
p.sayHello("你好");
}
}

4.2 动态代理和AOP

上面介绍了Proxy和InvocationHandler的使用,上面的使用不太容易看出动态代理的优势。

在开发中,如果需要对同一个功能进行复用,可以通过复制粘贴功能代码部分,但是在开发中一般情况下都会将这个功能包装为一个方法然后在需要使用的地方直接调用就可以了。
【java】解析java类加载与反射机制
通过方法调用实现代码复用虽然解决了代码块与相同代码的耦合,但是却提高了代码块与特定方法的耦合(代码块一,代码块二,代码块三与蓝色区域的耦合度高了)。最理想的效果是:代码块一,代码块二,代码块三既可以执行蓝色区域的代码部分,又无需以硬编码的方式来直接调用蓝色区域的方法。这时就可以通过动态代理来实现这个功能。

接下来使用Proxy和InvocationHandler实现当程序调用info()或run()方法时,系统可以“自动”将method1()和method2()两个方法插入info()和run()方法中执行。
首先提供一个Dog接口,在该接口中提供两个方法:

public interface Dog {
//info()方法声明
void info();
//run()方法
void run();
}

接下来为Dog接口提供一个或多个实现类。

public class GunDog implements Dog{
//实现info方法,仅仅打印一个字符串
@Override
public void info() {
System.out.println("我是一只猎狗");
}
//实现run()方法,仅仅打印一个字符串
@Override
public void run() {
System.out.println("迅速奔跑");
}
}

下面提供一个DogUtil类,提供两个通用方法:

public class DogUtil {
public static void method1(){
System.out.println("=====模拟第一个通用方法=====");
}
public static void method2(){
System.out.println("=====模拟第二个通用方法=====");
}
}

提供InvocationHandler的实现类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler{
private Object target;
public void setTarget(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//执行DogUtil对象中method1方法
DogUtil.method1();
//以target作为主调执行method方法
Object result= method.invoke(target, args);
//执行DogUtil对象中的method2方法
DogUtil.method2();
return result;
}
}

接着提供一个ProxyFactory,用于生成动态代理对象。

import java.lang.reflect.Proxy;
public class MyProxyFactory {
public static Object getProxy(Object target){
MyInvocationHandler myInvocationHandler=new MyInvocationHandler();
//设置需要代理的对象
myInvocationHandler.setTarget(target);
//创建并返回动态代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),myInvocationHandler);//使用指定的类加载器,需要实现的接口,自定义Handler
}
}

然后是测试类:

public class Test {
public static void main(String[] args) {
Dog dog=(Dog)MyProxyFactory.getProxy(new GunDog());
dog.info();
dog.run();
}
}

输出如下:

=====模拟第一个通用方法=====
我是一只猎狗
=====模拟第二个通用方法=====
=====模拟第一个通用方法=====
迅速奔跑
=====模拟第二个通用方法=====

上面程序中的dog对象实际是动态代理对象,只是该动态代理对象也实现了Dog接口,所以也可以当成Dog对象使用。通过上面的代码可以发现,使用动态代理可以非常方便地解耦。
像上面这种动态代理被称为AOP(Aspect Orient Programming,面向切面编程)代理,AOP代理可替代目标对象,AOP代理包含了目标对象的全部方法。
下面这张图片对上面的程序流程进行了简单解释:
【java】解析java类加载与反射机制

5.反射与泛型

从JDK5以后,Java的Class类增加了泛型功能,从而允许泛型来限制Class类,例如,String.class的类型实际是Class<String>。如果Class对应的类暂时未知,则使用Class<?>。通过在反射中使用泛型,可以避免使用反射生成的对象需要进行强制转化。

5.1 泛型与Class类

使用Class<T>泛型可以避免强制转化,这样可以增加类型安全。
例如:

public class ObjectFactory{
public static Object getInstance(String clsName){
try{
Class cls=Class.forName(clsName);
return cls.newInstance();
}catch(Exception e){
e.printStackTrace();
return null;
}
}
}

上面程序中使用指定的字符串类型创建一个新对象,但这个对象的类型是Object.如果需要使用的话,还必需进行强制转化。

Date d=(Date)ObjectFactory.getInstance("java.util.Date");

上面的代码显示是类型不安全的,接下来使用Class<T>来重写上面的方法。

public static <T> T getInstance(Class<T> cls){
try{
return cls.newInstance();
}catch(Exception e){
e.printStackTrace();
return null;
}
}

在上面程序的getInstance()方法中传入一个Class<T>参数,这是一个泛型化的Class对象,调用Class对象的newInstance()方法将返回一个T对象。
接下来使用泛型类对Array.newInstance方法进行封装:

public class MyArrayFactory{
public static <T> T[] getInstance(Class<T> componentType,int length){
return (T[])Array.newInstance(componentType, length);
}
}

然后就可以按照如下的代码调用:

String[] strs=MyArrayFactory.getInstance(String.class,10);
上一篇:Centos 6/7 常用命令总结 (基础)


下一篇:LInux学习之常用命令ls