• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

类加载机制与反射

java 搞代码 4年前 (2022-01-09) 23次浏览 已收录 0个评论

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

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

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

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

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

打印根类加载器:

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.jarfile:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/rt.jarfile:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jsse.jarfile:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jce.jarfile:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/charsets.jarfile:/Library/Java/JavaVirtualMachines/jdk1.8.0_51.jdk/Contents/Home/jre/lib/jfr.jar

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

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

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

public static void main(String[] args)        throws IOException    {        // 获取系统类加载器        ClassLoader systemLoader = ClassLoader.getSystemClassLoader();        System.out.println("系统类加载器:" + systemLoader);        /*        获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定        如果操作系统没有指定CLASSPATH环境变量,默认以当前路径作为        系统类加载器的加载路径        */        Enumeration 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());    }

自定义类加载器的例子:

由于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.OverrideAnnotat<mark>来源gaodaimacom搞#^代%!码网</mark>ionOf;/** * Description: * <br />网站: 疯狂Java联盟 * <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 [email protected] * @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.javates22t2运行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 />网站: 疯狂Java联盟 * <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 [email protected] * @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 ('java8', '2')";                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);    }

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

什么时候会用到反射?

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

1.获取构造器

2.获取方法

3.获取属性

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

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

5.获取内部类

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

6.获取外部类

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

7.获取接口

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

8.其他的如下:

例子:

// 定义可重复注解@Repeatable(Annos.class)@interface Anno {}@Retention(value=RetentionPolicy.RUNTIME)@interface Annos {    Anno[] value();}// 使用4个注解修饰该类@SuppressWarnings(value="unchecked")@Deprecated// 使用重复注解修饰该类@Anno@Annopublic 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 新增的方法参数反射

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

class Test{    public void replace(String str, List list){}}public class MethodParameterTest{    public static void main(String[] args)throws Exception    {        // 获取Tesy的类        Class 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

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

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

import java.util.*;import java.io.*;import java.lang.reflect.*;/** * Description: * <br />网站: 疯狂Java联盟 * <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 [email protected] * @version 1.0 */public class ExtendedObjectPoolFactory{    // 定义一个对象池,前面是对象名,后面是实际对象    private Map 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);    }}

18.4.3 访问成员变量值

例子:

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

例子:

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动态代理或动态代理对象.

动态代理的例子:

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();    }}
--------------结果-----------------=====模拟第一个通用方法=====我是一只猎狗=====模拟通用方法二==========模拟第一个通用方法=====我奔跑迅速=====模拟通用方法二=====

18.6 反射和泛型

例子1:泛型工厂类

public class CrazyitObjectFactory2{    public static  T getInstance(Class 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 score;    public static void main(String[] args)        throws Exception    {        Class 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("获取泛型类型出错!");        }    }}

搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:类加载机制与反射

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址