Heim  >  Artikel  >  类库下载  >  类加载机制与反射

类加载机制与反射

高洛峰
高洛峰Original
2016-10-20 11:14:511726Durchsuche

1.png

本章重点介绍java.lang.reflect包下的接口和类

1.png

当程序使用某个类时,如果该类还没有被加载到内存中,那么系统会通过加载,连接,初始化三个步骤来对该类进行初始化.

类的加载时指将类的class文件读入内存,并为之创建一个java.lang.class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象.(几乎所有的类都是java.lang.Class的实例);

1.png

1.png

1.png

1.png

所以JVM最先初始化的总是java.long.Object类.

在java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和类加载器作为其唯一标识.

1.png

打印根类加载器:

public class BootstrapTest
{
    public static void main(String[] args)
    {
        // 获取根类加载器所加载的全部URL数组
        for (URL url : sun.misc.Launcher.getBootstrapClassPath().getURLs()) {
            // 遍历、输出根类加载器加载的全部URL
            System.out.println(url.toExternalForm());
        }
    }
}

----------------------------------------------------------------------
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar

扩展类加载器,这个可以加入自己的jar包,挺好玩的.

1.png

系统类加载器:这个就是我们平常自定义类的父加载器了

1.png

1.png

1.png

开发者实现自定义的类加载器需要通过继承ClassLoader来实现.

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());
    }

1.png

1.png

自定义类加载器的例子:

由于java8.0.51的URLClassLoader里重写了ClassLoader这个类里的下面这个方法,

protected Class<?> findClass(String name) throws ClassNotFoundException {        throw new ClassNotFoundException(name);
    }

所以,现在还没弄明白原因,如果我们自己重写findClass(String name),结果就是程序会先调用URLClassLoader里的findClass方法,如果这个方法找不到类,才会调用我们自己写的findClass(String name).

比如我们要动态加载某个类(如果内存存在这个类,那么闲从内存取;如果内存中不存在,那么加载那个类的java文件并编译(我不确定是不是要先检查是否有class文件,看源码和自己的测试结果是没有检查)).

例子

public class Hello
{
    public static void main(String[] args)
    {
        System.out.println("tes22t2");
        for (String arg : args)
        {
            System.out.println("运行Hello的参数:" + arg);
        }
    }
}
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.jar.Manifest;

import sun.misc.Resource;
import sun.misc.URLClassPath;

import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
/**
 * Description:
 * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
public class CompileClassLoader extends ClassLoader
{
    // 读取一个文件的内容
    private byte[] getBytes(String filename)
        throws IOException
    {
        File file = new File(filename);
        long len = file.length();
        byte[] raw = new byte[(int)len];
        try(
            FileInputStream fin = new FileInputStream(file))
        {
            // 一次读取class文件的全部二进制数据
            int r = fin.read(raw);
            if(r != len)
            throw new IOException("无法读取全部文件:"
                + r + " != " + len);
            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 ie)
        {
            System.out.println(ie);
        }
        // 获取javac线程的退出值
        int ret = p.exitValue();
        // 返回编译是否成功
        return ret == 0;
    }
    // 重写ClassLoader的findClass方法
    @Override
    protected Class<?> findClass(String tmpName)
        throws ClassNotFoundException
    {
        System.out.println(tmpName);
        Class clazz = null;
        // 将包路径中的点(.)替换成斜线(/);
        String className = tmpName.replace("." , "/").replace("1" , "");
     // 这里因为我是在eclipse里编辑的,而java文件统一放到src里,class文件统一放到了bin里, 所以我需要加前缀,
        String javaFilename = "src/"+className + ".java";
        String classFilename = "bin/"+className + ".class";
        File javaFile = new File(javaFilename);
        System.out.println(javaFile.getAbsolutePath());
        File classFile = new File(classFilename);
        // 当指定Java源文件存在,且class文件不存在、或者Java源文件
        // 的修改时间比class文件修改时间更晚,重新编译
        if(javaFile.exists() && (!classFile.exists()
            || javaFile.lastModified() > classFile.lastModified()))
        {
            try
            {
                // 如果编译失败,或者该Class文件不存在
                if(!compile(javaFilename) || !classFile.exists())
                {
                    throw new ClassNotFoundException(
                        "ClassNotFoundExcetpion:" + javaFilename);
                }
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }
        // 如果class文件存在,系统负责将该文件转换成Class对象
        if (classFile.exists())
        {
            try
            {
                // 将class文件的二进制数据读入数组
                byte[] raw = getBytes(classFilename);
                // 调用ClassLoader的defineClass方法将二进制数据转换成Class对象
                clazz = defineClass(className,raw,0,raw.length);
            }
            catch(IOException ie)
            {
                ie.printStackTrace();
            }
        }
        // 如果clazz为null,表明加载失败,则抛出异常
        if(clazz == null)
        {
            throw new ClassNotFoundException(className);
        }
        return clazz;
    }

    // 定义一个主方法
    public static void main(String[] args) throws Exception
    {
        // 如果运行该程序时没有参数,即没有目标类
        args = new String[]{"Hello","java疯狂讲义w"};
        // 第一个参数是需要运行的类
        String progClass = args[0];
        // 剩下的参数将作为运行目标类时的参数,
        // 将这些参数复制到一个新数组中
        String[] progArgs = new String[args.length-1];
        System.arraycopy(args , 1 , progArgs
            , 0 , progArgs.length);
        CompileClassLoader ccl = new CompileClassLoader();
        // 加载需要运行的类
        Class<?> clazz = ccl.loadClass(progClass);
        // 获取需要运行的类的主方法
        Method main = clazz.getMethod("main" , (new String[0]).getClass());
        Object[] argsArray = {progArgs};
        main.invoke(null,argsArray);
    }
}

输出结果是

tes22t2
运行Hello的参数:java疯狂讲义w

打断点可见,并没有调用我们自定义的findClass(String tmpName)方法.

当我们把
args = new String[]{"Hello","java疯狂讲义w"};

改为
args = new String[]{"Hello1","java疯狂讲义w"};

 时,由于URLClassLoader并没有找到被加载的类Hello1,所以最后会调用我们自定义的findClass方法.

当然,更规范的是把
 Class<?> clazz = ccl.loadClass(progClass);
改为
Class<?> clazz = ccl.findClass(progClass);

输出结果:

Hello1/Users/liuxin/work/workspace2/learnJava/src/Hello.java
tes22t2
运行Hello的参数:java疯狂讲义w

这里我们可以看到,如果要动态加载某个类,不用自己覆写findClass方法,只要如下代码就好:

import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.jar.Manifest;

import sun.misc.Resource;
import sun.misc.URLClassPath;

import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
public class CompileClassLoader extends ClassLoader
{
    public static void main(String[] args) throws Exception
    {
        // 如果运行该程序时没有参数,即没有目标类
        args = new String[]{"Hello","java疯狂讲义w"};
        // 第一个参数是需要运行的类
        String progClass = args[0];
        // 剩下的参数将作为运行目标类时的参数,
        // 将这些参数复制到一个新数组中
        String[] progArgs = new String[args.length-1];
        System.arraycopy(args , 1 , progArgs
            , 0 , progArgs.length);
        CompileClassLoader ccl = new CompileClassLoader();
        // 加载需要运行的类
        Class<?> clazz = ccl.loadClass(progClass);
        // 获取需要运行的类的主方法
        Method main = clazz.getMethod("main" , (new String[0]).getClass());
        Object[] argsArray = {progArgs};
        main.invoke(null,argsArray);
    }
}

----------------输出结果--------------------
tes22t2
运行Hello的参数:java疯狂讲义w

18.2.4 URLClassLoader类

java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类(此处的父类,就是指类与类之间的继承关系).URLClassLoader功能强大,可以从本地或远程主机获取二进制文件来加载类.

下面是一个例子

import java.sql.*;
import java.util.*;
import java.net.*;
/**
 * Description:
 * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
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").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
    {
        Connection temconn = getConn("jdbc:mysql://localhost:3306/mybatis"
                , "root" , "password");
            try{
                String sql = "INSERT INTO `mybatis`.`users` (`NAME`, `age`) VALUES (&#39;java8&#39;, &#39;2&#39;)";
                java.sql.PreparedStatement stmt = temconn.prepareStatement(sql);
                stmt.execute();
                stmt.close();
                String sql2 = "select * from `mybatis`.`users`";
                java.sql.PreparedStatement stmt2 = temconn.prepareStatement(sql2);
                ResultSet rs = stmt2.executeQuery();
                ResultSetMetaData m=rs.getMetaData();
                //显示列,表格的表头
                int columns=m.getColumnCount();
                for(int i=1;i<=columns;i++)
                   {
                    System.out.print(m.getColumnName(i));
                    System.out.print("\t\t");
                   }
                System.out.println();
                   //显示表格内容
                   while(rs.next())
                   {
                    for(int i=1;i<=columns;i++)
                    {
                     System.out.print(rs.getString(i));
                     System.out.print("\t\t");
                    }
                    System.out.println();
                   }
                stmt2.close();
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                temconn.close();
            }
    }
}

所以前面的动态加载类,可以用URLClassLoader改写如下:(为了弄明白路径,把Hello.java放到了learnJava包里去了)

public static void main(String[] args) throws Exception
    {
        args = new String[]{"learnJava.Hello","java疯狂讲义w"};
        String progClass = args[0];
        String[] progArgs = new String[args.length-1];
        System.arraycopy(args , 1 , progArgs, 0 , progArgs.length);
        URL[] urls = {new URL("file:")};
        Class<?> clazz = (new URLClassLoader(urls)).loadClass(progClass);
        // 获取需要运行的类的主方法
        Method main = clazz.getMethod("main" , (new String[0]).getClass());
        Object[] argsArray = {progArgs};
        main.invoke(null,argsArray);
    }

1.png

18.3 通过反射来查看类的信息

什么时候会用到反射?

1.png

从Class中获取信息,方法分以下几类:

1.获取构造器

2.获取方法

3.获取属性

上面这三类方法,每一类都分4个方法,例如:(单个,多个) * (按权限,不顾权限)

1.png

4.获取注解,这个太多:

1.png

5.获取内部类

Class>[] getDeclaredClasses():返回该Class对象对应类包含的全部内部类

6.获取外部类

Class>[] getDeclaringClasse():返回该Class对象对应类所在的外部类

7.获取接口

Class>[] getInterfaces():返回该Class对象对应类所实现的全部接口

8.其他的如下:

1.png

 例子:

// 定义可重复注解
@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<?> 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());
    }
}

18.3.3 java8 新增的方法参数反射

1.png

关于反射方法的参数的名字,这个比较麻烦,如下的例子

1.png

class Test
{
    public void replace(String str, List<String> list){}
}
public class MethodParameterTest
{
    public static void main(String[] args)throws Exception
    {
        // 获取Tesy的类
        Class<Test> clazz = Test.class;
        // 获取Test类的带两个参数的replace()方法
        Method replace = clazz.getMethod("replace"
            , String.class, List.class);
        // 获取指定方法的参数个数
        System.out.println("replace方法参数个数:" + replace.getParameterCount());
        // 获取replace的所有参数信息
        Parameter[] parameters = replace.getParameters();
        System.out.println((new File("")).getAbsolutePath());
        int index = 1;
        // 遍历所有参数
        for (Parameter p : parameters)
        {
            if (p.isNamePresent())
            {
                System.out.println("---第" + index++ + "个参数信息---");
                System.out.println("参数名:" + p.getName());
                System.out.println("形参类型:" + p.getType());
                System.out.println("泛型类型:" + p.getParameterizedType());
            }
        }
    }
}

 所以,上面这个例子在用eclipse编译时,总是找不到参数名.没找到设置的地方,只能用javac编译

编译命令如下:
javac -parameters -encoding GBK  -d . MethodParameterTest.java 

因为疯狂java讲义里的源码都是GBK编码,而一般的电脑默认utf-8,所以需要指定编码方式

运行:
java MethodParameterTest
----------------------结果--------------------------
replace方法参数个数:2
/Users/liuxin/work/workspace2/learnJava/src
---第1个参数信息---
参数名:str
形参类型:class java.lang.String
泛型类型:class java.lang.String
---第2个参数信息---
参数名:list
形参类型:interface java.util.List
泛型类型:java.util.List<java.lang.String>

18.4 使用反射生成并操作对象

1.png

1.png

例子:这个个别地方没理解,但是这是一个反射的典型应用,需要好好理解

import java.util.*;
import java.io.*;
import java.lang.reflect.*;
/**
 * Description:
 * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
 * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
 * <br/>This program is protected by copyright laws.
 * <br/>Program Name:
 * <br/>Date:
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 1.0
 */
public class ExtendedObjectPoolFactory
{
    // 定义一个对象池,前面是对象名,后面是实际对象
    private Map<String ,Object> objectPool = new HashMap<>();
    private Properties config = new Properties();
    // 从指定属性文件中初始化Properties对象
    public void init(String fileName)
    {
//        File tmpFi = new File(fileName);
//        System.out.println(tmpFi.getAbsolutePath());
        try(FileInputStream fis = new FileInputStream(fileName))
        {
            config.load(fis);
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
            System.out.println("读取" + fileName + "异常");
        }
    }
    // 定义一个创建对象的方法,
    // 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
    private Object createObject(String clazzName)
        throws InstantiationException
        , IllegalAccessException , ClassNotFoundException
    {
        // 根据字符串来获取对应的Class对象
        Class<?> clazz =Class.forName(clazzName);
        // 使用clazz对应类的默认构造器创建实例
        return clazz.newInstance();
    }
    // 该方法根据指定文件来初始化对象池,
    // 它会根据配置文件来创建对象
    public void initPool()throws InstantiationException
        ,IllegalAccessException , ClassNotFoundException
    {
        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("src/extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

例子二:使用指定的构造器来构造对象.

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);
    }
}

1.png

18.4.3 访问成员变量值

1.png

例子:

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类操作数组

1.png

例子:

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);
        }
    }
}

操作多维数组的例子:

public class ArrayTest2
{
    public static void main(String args[])
    {
        /*
          创建一个三维数组。
          根据前面介绍数组时讲的:三维数组也是一维数组,
          是数组元素是二维数组的一维数组,
          因此可以认为arr是长度为3的一维数组
        */
        Object arr = Array.newInstance(String.class, 3, 4, 10);
        // 获取arr数组中index为2的元素,该元素应该是二维数组
        Object arrObj = Array.get(arr, 2);
        // 使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,
        // 所以传入Array的set()方法的第三个参数是一维数组。
        Array.set(arrObj , 2 , new String[]
        {
            "疯狂Java讲义",
            "轻量级Java EE企业应用实战"
        });
        // 获取arrObj数组中index为3的元素,该元素应该是一维数组。
        Object anArr  = Array.get(arrObj, 3);
        Array.set(anArr , 8  , "疯狂Android讲义");
        // 将arr强制类型转换为三维数组
        String[][][] cast = (String[][][])arr;
        // 获取cast三维数组中指定元素的值
        System.out.println(cast[2][3][8]);
        System.out.println(cast[2][2][0]);
        System.out.println(cast[2][2][1]);
    }
}

18.5 使用反射生成JDK动态代理

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口,可生成JDK动态代理或动态代理对象.

1.png

动态代理的例子:

interface Person
{
    void walk();
    void sayHello(String name);
}
class MyInvokationHandler implements InvocationHandler
{
    /*
    执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
    其中:
    proxy:代表动态代理对象
    method:代表正在执行的方法
    args:代表调用目标方法时传入的实参。
    */
    @Override
    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("孙悟空");
    }
}

18.5.2 动态代理和AOP

一个AOP的实现例子:

public interface Dog
{
    // info方法声明
    void info();
    // run方法声明
    void run();
}
public class GunDog implements Dog
{
    // 实现info()方法,仅仅打印一个字符串
    public void info()
    {
        System.out.println("我是一只猎狗");
    }
    // 实现run()方法,仅仅打印一个字符串
    public void run()
    {
        System.out.println("我奔跑迅速");
    }
}
public class DogUtil
{
    // 第一个拦截器方法
    public void method1()
    {
        System.out.println("=====模拟第一个通用方法=====");
    }
    // 第二个拦截器方法
    public void method2()
    {
        System.out.println("=====模拟通用方法二=====");
    }
}
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;
    }
}
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);
    }
}
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();
    }
}
--------------结果-----------------
=====模拟第一个通用方法=====
我是一只猎狗
=====模拟通用方法二=====
=====模拟第一个通用方法=====
我奔跑迅速
=====模拟通用方法二=====

1.png

18.6 反射和泛型

例子1:泛型工厂类

public class CrazyitObjectFactory2
{
    public static <T> T getInstance(Class<T> cls)
    {
        try
        {
            return cls.newInstance();
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args)
    {
        // 获取实例后无须类型转换
        Date d = CrazyitObjectFactory2.getInstance(Date.class);
        JFrame f = CrazyitObjectFactory2.getInstance(JFrame.class);
    }
}

例子2:使用反射来获取泛型信息

public class GenericTest
{
    private Map<String , Integer> score;
    public static void main(String[] args)
        throws Exception
    {
        Class<GenericTest> clazz = GenericTest.class;
        Field f = clazz.getDeclaredField("score");
        // 直接使用getType()取出的类型只对普通类型的成员变量有效
        Class<?> a = f.getType();
        // 下面将看到仅输出java.util.Map
        System.out.println("score的类型是:" + a);
        // 获得成员变量f的泛型类型
        Type gType = f.getGenericType();
        // 如果gType类型是ParameterizedType对象
        if(gType instanceof ParameterizedType)
        {
            // 强制类型转换
            ParameterizedType pType = (ParameterizedType)gType;
            // 获取原始类型
            Type rType = pType.getRawType();
            System.out.println("原始类型是:" + rType);
            // 取得泛型类型的泛型参数
            Type[] tArgs = pType.getActualTypeArguments();
            System.out.println("泛型信息是:");
            for (int i = 0; i < tArgs.length; i++)
            {
                System.out.println("第" + i + "个泛型类型是:" + tArgs[i]);
            }
        }
        else
        {
            System.out.println("获取泛型类型出错!");
        }
    }
}


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:java图形用户界面列表框Nächster Artikel:JAVA Collections工具类