Java SE学习笔记-基础加强之类加载机制与反射

来源:互联网 发布:土耳其旅游 知乎 编辑:程序博客网 时间:2024/04/29 07:41


1、主要内容

2、详细内容

2.1、概述

2.1.1JVM和类

当调用Java命令运行某个Java程序时候,该命令会启动一个Java虚拟机进程,同一个JVM的所有线程、所有变量都处于同一个进程里面,它们都使用该JVM进程的内存区,当系统出现以下几种情况的时候,JVM进程将会被终止。

(1)程序运行到最后正常结束;

(2)程序运行到使用System.exit(0)或者Runtime.getRuntime().exit()代码处结束进程;

(3)程序执行过程中遇到未捕获的异常或错误而结束;

(4)程序所在平台强制结束了JVM进程。

2.1.2、类的加载

当程序主动使用某个类的时候,如果该类还未被加载到内存当中,则系统会通过加载、连接、初始化这三个步骤来对该类进行初始化。如果没有以外,JVM会连续完成这3个步骤;可以将这三个步骤称之为类的加载。

类加载是指将类的class文件读入内存,并为之创建一个java.lang.Class对象,即,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

2.1.3、类的连接

当类被加载之后,系统会为之生成一个对应的Class对象,接着会进入连接阶段,连接阶段会负责把类的二进制数据合并到JRE中,类的连接可以分为三个阶段:

(1)验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致;

(2)准备:来准备阶段负责为类的静态属性分配内存,并设置为默认初始值;

(3)解析:将类的二进制数据中的符号引用替换成直接引用。

2.1.4、类的初始化

在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对静态属性进行初始化。在Java类中对静态属性指定初始值有两种方式:

(1)声明静态属性时指定初始值;

(2)使用静态代码块为静态属性指定初始值,JVM会按这些语句在程序中的排列顺序依次执行它们。

JVM初始化一个类包含如下几个步骤:

(1)假如这个类还没有被加载和连接,则程序先加载并连接该类;

(2)假如该类的直接父类还没有被初始化,则先初始化其直接父类;

(3)假如类中有初始化语句,则系统依次执行这些初始化语句。

2.1.5、类的初始化时机

Java程序首次通过下面6种方式来使用某个类或接口时候,系统就会初始化该类或者接口。

(1)创建类的实例,为某个类创建实例的方式包括:使用new关键字来创建实例,通过反射来创建实例,通过反序列化的方式创建实例;

(2)调用某个类的静态方法;

(3)访问某个类或接口的静态属性,或者为该静态属性赋值;

(4)使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。

(5)初始化某个类的子类;当初始化某个类的子类时,该子类的所有父类都会被初始化;

(6)直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类。

2.2、类加载器

2.2.1、类加载器简介

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

JVM启动时,会形成由3个类加载器组成的初始类加载器层次结构。

(1)Bootstrap ClassLoader:根类加载器;它负责加载Java的核心类;它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的,下面程序可以实现获取根类加载器所加在的核心类库。

public class BootstrapTest{public static void main(String[] args){// 获取根类加载器所加载的全部URL数组URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();// 遍历、输出根类加载器加载的全部URLfor (int i = 0; i < urls.length; i++){System.out.println(urls[i].toExternalForm());}}}

运行的结果如下所示:

(2)Extension ClassLoader:扩展类加载器;它负责加载JRE的扩展陌路中的JAR包中的类。

(3)System ClassLoader:系统类加载器;它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所制定的JAR包和类路径。

2.2.2、类加载机制

JVM的类加载机制主要有如下三种机制:

(1)全盘负责:指当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其它Class也将有该类加载器负责载入,除非显式使用另外一个类加载器来载入;

(2)父类委托:指先让父类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类;

(3)缓存机制:指所有加载过的Class都会被缓存,当程序中需要使用某个Class时候,类加载器先从缓存区中搜寻该Class,只有当缓存去中不存在该Class对象时候,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

以下代码演示访问JVM的类加载器。

public class ClassLoaderPropTest{public static void main(String[] args){// 获取系统类加载器ClassLoader systemLoader = ClassLoader.getSystemClassLoader();System.out.println("系统类加载器为: " + systemLoader);// 获取系统类加载器的父类加载期,得到扩展类加载器ClassLoader extensionLoader = systemLoader.getParent();System.out.println("扩展类加载器: " + extensionLoader);System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));System.out.println("扩展类加载器的parent: " + extensionLoader.getParent());}}

运行的结果如下所示:

类加载器加载Class大致要经过如下8个步骤,

(1)检测此Class是否载入过(即在缓存区中是否有此Class),有则直接进入第8步(红线表示),否则直接进入第2步;

(2)如果父类加载器不存在(如果没有父类加载器,则要么parent一定是根类加载器,要么本身就是根类加载器),则调到第4步执行;如果父类加载器存在,则接着执行第3步;

(3)请求使用父类加载器去载入目标类,如果成功载入则调到第8步,否则接着执行第5步;

(4)请求使用根类加载器来载入目标类,如果成功载入则跳到第8步,否则跳到第7步。

(5)当前类加载器尝试寻早Class文件,如果找到则执行第6步,如果找不到则跳到第7步;

(6)从文件中载入Class,成功载入后跳入第8步;

(7)抛出ClassNotFoundException异常;

(8)返回对应的java.lang.Class对象。

如下图所示:

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

2.2.3、自定义类加载机制

public abstract class ClassLoader extends Object

JVM中除了根类加载器之外的所有类家族齐齐都是ClassLoader子类的实例,可以继承该类并实现该类的方法,重要的三个方法如下所示:

protected Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException

(1)是ClassLoader类的入口点,根据指定的二进制名称来加载类,此方法默认实现将按以下顺序搜索类:

a) 调用findLoadedClass(String)来检查是否已经加载类;

b) 在父类加载器上调用loadClass方法;如果父类加载器为null,则使用虚拟机的内置类加载器;

c) 调用findClass(String)方法查找类。

protected Class<?> findClass(String name) throws ClassNotFoundException

2)根据二进制名称来查找类。

protected final Class<?> defineClass(String name,byte[] b, int off,int len)throws ClassFormatError

(3)该方法负责将指定类的字节码文件(即Class文件)读入字节数组中,并将它转换为Class对象。

编程步骤:

(1)编写一个对文件内容进行简单加密的程序。

(2)编写了一个自己的类装载器,可实现对加密过的类进行装载和解密。

(3)编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName

实验步骤:

(1)对不带包名的class文件进行加密,加密结果存放到另外一个目录,

(2)运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoaderjava MyClassLoader MyTest F:\itcast

(3)用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

(4)删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

示例代码如下所示:

public class MyClassLoader extends ClassLoader{private String path = null;public MyClassLoader(String path) throws Exception// 检查文件是否存在{File f = new File(path);if (!f.isDirectory()){throw new RuntimeException(path + " is not a directory");}this.path = path;}// 覆盖ClassLoader的findClass方法public Class findClass(String name){try{File f = new File(path, name.substring(name.lastIndexOf('.') + 1) + ".class");FileInputStream fis = new FileInputStream(f);ByteArrayOutputStream bos = new ByteArrayOutputStream();cypher(fis, bos);// 加密class字节码byte[] buf = bos.toByteArray();fis.close();bos.close();return defineClass(name, buf, 0, buf.length);}catch (Exception e){try{throw new ClassNotFoundException(name + " is not found!");}catch (ClassNotFoundException e1){e1.printStackTrace();}}return null;}// 加密方法public static void cypher(InputStream istream, OutputStream ostream) throws Exception{// 下面这段代码可能遇到255的字节,当成byte就成了-1int b = 0;while ((b = istream.read()) != -1){ostream.write(((byte) b) ^ 0xff);}}public static void main(String[] args) throws Exception{// 文件元源必须是以class结尾if (!args[0].endsWith("class")){// args[1]代表是文件目地ClassLoader loader = new MyClassLoader(args[1]);Class cls = loader.loadClass(args[0]);Method m = cls.getMethod("test");m.invoke(cls.newInstance());return;}else{FileInputStream fis = new FileInputStream(args[0]);File f = new File(args[1], new File(args[0]).getName());// 不用检查目录最后是否有目录分割符FileOutputStream fos = new FileOutputStream(f);cypher(fis, fos);fis.close();fos.close();}}}
public class ClassLoaderTest{public static void main(String[] args) throws Exception{System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());System.out.println(System.class.getClassLoader());ClassLoader loader = ClassLoaderTest.class.getClassLoader();while (loader != null){System.out.println(loader.getClass().getName());loader = loader.getParent();}System.out.println(loader);System.out.println(new ClassLoaderAttachment().toString());Class clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");Date d1 = (Date) clazz.newInstance();System.out.println("d1" + d1);}}

2.2.4URLClassLoader

public class URLClassLoader  extends SecureClassLoader

URLClassLoader类是ClassLoader的实现类,该类也是系统类加载器和扩展类加载器的父类;它既可以从本地文件系统获取二进制文件来记在类,也可以从远程主机获取二进制文件加载类。

该类有两个构造方法,如下所示:

URLClassLoader(URL[] urls)     使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader,该对象从urls锁指定的系列路径来查询并加载类。URLClassLoader(URL[] urls, ClassLoader parent)      为给定的 URL 构造新 URLClassLoader,其它功能和前一个构造器相同。
一旦得到URLClassLoader对象之后,就可以调用该对象的loadClass()方法来加载指定的类。

2.3、反射

2.3.1Class

public final class Class<T>extends Objectimplements Serializable, GenericDeclaration, Type, AnnotatedElement

Class类代表Java类,Class类的实例表示正在运行的Java应用程序的类和接口。

有三种方法可以获得各个字节码对应的实例对象(Class类型):

(1)类名.class,比如System.class;

(2)对象.getClass();比如:new Date().getClass();

(3)使用Class.forName(“类名”),比如:Class.forName(“java.util.Date”);

Class类中共创建了九个预定义对象,表示八个基本类型和void;这些类对象由Java虚拟机常见,与其表示的基本了性同名,即boolean,byte,char,short,int,long,floatdouble

这些对象仅能通过声明为public static final的变量访问。

可以使用isPrimitive方法来判断该对象是否是预定义对象,方法格式如下所示:

public boolean isPrimitive():判定指定的 Class 对象是否表示一个基本类型

关于数组类型的Class实例对象,可以通过isArray()方法来判断,方法格式如下所示:

public boolean isArray():判定此 Class 对象是否表示一个数组类

如果此对象表示一个数组类,则返回true;否则返回false

2.3.2、定义

反射就是将Java类中的各种成分映射成相应的Java类;例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量、方法,构造方法,包等信息也可以用一个个Java类来表示。

Class类中提供了以下几种方法来分别获得其中的变量,方法,构造方法,修饰符和包等信息,而这些信息就是用相应类的实例对象表示,它们分别是FieldMethodConstructorPackage等。

public Field getField(String name) throws NoSuchFieldException, SecurityException  通过getField()方法获取一个Field对象,它反映此Class对象所表示的类或接口的指定公共成员字段;public Method getMethod(String name,Class<?>... parameterTypes)throws NoSuchMethodException, SecurityException  通过getMethod()方法获取一个Method对象,它反映此Class对象所表示的类或接口的指定公共成员方法。public Constructor<T> getConstructor(Class<?>... parameterTypes)throws NoSuchMethodException,SecurityException  通过getConstructor()方法获取一个Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。public Package getPackage()  通过getPackage()方法获取此类的包。 
2.3.3、Constructor类:代表某个类的一个构造方法
public final class Constructor<T>extends AccessibleObjectimplements GenericDeclaration, Member
  Constructor提供关于类的单个构造方法的信息以及对它的访问权限;Constructor还允许在将实参与带有底层构造方法的newInstance()匹配时进行扩展转换,但是转换发生错误,则会抛出IllegalArgumentException。
  可以通过getConstructors()方法来得到某个类的所有构造方法,示例代码如下所示:
Constructor [] constructors = Class.forName("java.lang.String").getConstructors();
  可以通过getConstructor()方法来得到某个类的一个构造方法,示例代码如下所示:
//获取方法时要用到类型Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
  创建实例对象有两种方式:
  (1)通常方式:直接通过new关键字实例化对象,示例代码如下所示:
String str = new String(new StringBuffer("abcde"));
  (2)反射方式:通过newInstance()方法获得,示例代码如下所示:
//调用获得的方法时要用到上面相同类型的实例变量String str = (String)constructor.newInstance(new StringBuffer("abcde"));
  关于Class.newInstance()方法:该方法内部先得到默认的构造方法,然后用该构造方法来创建实例对象。
public T newInstance()throws InstantiationException,IllegalAccessException
 创建此Class类对象所表示的类的一个新实例。如同用一个带有一个空参数列表的new 表达式实例化该类。如果该类尚未初始化,则初始化这个类。

2.3.4、Field类:代表某个类中的一个成员变量
public final class Fieldextends AccessibleObjectimplements Member
  Field提供有关类或接口的单个字段的信息,以及对它的动态反问权限。反射的字段可能是一个类(静态)字段或实例字段。
public Field[] getFields()throws SecurityException    返回一个包含某些Field对象的数组,这些对象反映此Class对象所表示的类或接口的所有可访问公共字段。public Field getDeclaredField(String name)throws NoSuchFieldException,SecurityExceptionpublic Field[] getDeclaredFields()throws SecurityException  返回一个Field对象,该对象反映此Class对象所表示的类或接口的指定已声明字段。Name参数是一个String类型,它指定所需字段的简称。
 
2.3.5、Method类:代表某个类中的一个成员方法
public final class Methodextends AccessibleObjectimplements GenericDeclaration, Member
  Method提供类或接口上单独某个方法(以及如何访问该方法)的信息;所反映的方法可能是类方法或者实例方法(包括抽象方法)。
  可以通过getMethod()方法获得类中的某个方法,示例代码如下所示:
Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
  调用方法:一共有两种;
通常方式:System.out.println(str.charAt(1));反射方式:通过invoke()方法,如果传递给该方法的第一个参数为null,说明该Method对象对应的是一个静态方法!public Object invoke(Object obj,Object... args)throws IllegalAccessException,IllegalArgumentException,InvocationTargetException
2.3.6、执行main方法
(1)目标:写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
(2)问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
(3)解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
示例代码:
class TestArguments{public static void main(String[] args){for (String arg : args){System.out.println(arg);}}}

通过传统方式调用代码如下所示:

TestArguments.main(new String[]{"111","222","333"});

运行结果如下所示:

通过反射的方式调用TestArguments这个类的main方法,代码如下所示:

String startingClassName = args[0];Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);//下面语句使用会出现异常见下图mainMethod.invoke(null, new String[]{ "111", "222", "333" });

运行时错误结果如下所示:

通过第(3)步中的解决方法运行时代码如下所示:

String startingClassName = args[0];Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);//编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了mainMethod.invoke(null, new Object[] { new String[] { "111", "222", "333" } });mainMethod.invoke(null, (Object) new String[] { "111" });

运行时结果如下所示:

提示:

(1)获取一个类的完整名称:使用功能键“F2”,如图所示:

(2)在MyEclipse中如何传递一个参数运行,步骤如下图所示:

2.3.7、数组的反射

(1)具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class

基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

(2)Arrays.asList()方法处理int[]String[]时的差异。

int[] a1 = new int[] { 1, 2, 3 };String[] a4 = new String[] { "a", "b", "c" };System.out.println("直接打印出对象中内容");System.out.println(a1);System.out.println(a4);System.out.println("Arrays.asList()方法输出");System.out.println(Arrays.asList(a1));System.out.println(Arrays.asList(a4));

运行结果如下所示:

3Array工具类用于完成对数组的反射操作。

// 通过反射打印数组private static void printObject(Object obj){Class clazz = obj.getClass();if (clazz.isArray()){int len = Array.getLength(obj);for (int i = 0; i < len; i++){System.out.println(Array.get(obj, i));}}else{System.out.println(obj);}}

2.4JavaBean内省

2.4.1JavaBean

JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。

一个类被当作JavaBean使用时,JavaBean的属性是根据方法名推断出来的,它根本看不到java类内部的成员变量。

2.4.2、综合案例

步骤:

(1)演示用eclipse自动生成 ReflectPoint类的settergetter方法。

(2)直接new一个PropertyDescriptor对象的方式来了解JavaBean API的价值

(3)得到BeanInfo最好采用“obj.getClass()”方式,而不要采用“类名.class”方式,这样程序更通用。

(4)采用遍历BeanInfo的所有属性方式来查找和设置某个RefectPoint对象的x属性。在程序中把一个类当作JavaBean来看,就是调用IntroSpector.getBeanInfo方法, 得到的BeanInfo对象封装了把这个类当作JavaBean看的结果信息。

示例代码如下所示:

public class ReflectPoint{// birthday属性private Date birthday = new Date();// x和y属性public int x;public int y;// set和get方法    。。。。。。public ReflectPoint(int x, int y){super();this.x = x;this.y = y;}@Overridepublic String toString(){return "ReflectPoint [str1=" + str1 + ", str2=" + str2 + ", str3=" + str3 + "]";}@Overridepublic int hashCode(){final int prime = 31;int result = 1;result = prime * result + x;result = prime * result + y;return result;}@Overridepublic boolean equals(Object obj){if (this == obj) return true;if (obj == null) return false;if (getClass() != obj.getClass()) return false;ReflectPoint other = (ReflectPoint) obj;if (x != other.x) return false;if (y != other.y) return false;return true;}}
public class IntroSpectorTest{public static void main(String[] args) throws Exception{ReflectPoint pt1 = new ReflectPoint(3, 5);String propertyName = "x";// 调用获取属性方法Object retVal = getProperty(pt1, propertyName);System.out.println("获取到的x属性值为: " + retVal);Object value = 7;// 调用设置属性方法setProperty(pt1, propertyName, value);// 打印调用设置属性方法后的大小System.out.println("调用设置属性方法后的x类型为: " + BeanUtils.getProperty(pt1, "x").getClass().getName());// 调用设置属性方法BeanUtils.setProperty(pt1, "x", "9");System.out.println("通过BeanUtils类调用设置属性方法后的x属性值为: " + pt1.getX());// 调用设置属性方法为birthday属性设值BeanUtils.setProperty(pt1, "birthday.time", "111");System.out.println("通过BeanUtils类调用设置生日属性后的值为: " + BeanUtils.getProperty(pt1, "birthday.time"));// 通过PropertyUtils类调用设置属性方法PropertyUtils.setProperty(pt1, "x", 9);System.out.println("通过PropertyUtils类调用设置x属性的值为:  " + PropertyUtils.getProperty(pt1, "x"));}// 传递了一个对象、属性名和设置值,完成属性修改的功能。private static void setProperty(Object pt1, String propertyName, Object value) throws IntrospectionException,IllegalAccessException, InvocationTargetException{PropertyDescriptor pd2 = new PropertyDescriptor(propertyName, pt1.getClass());Method methodSetX = pd2.getWriteMethod();methodSetX.invoke(pt1, value);}// 获取属性值private static Object getProperty(Object pt1, String propertyName) throws IntrospectionException,IllegalAccessException, InvocationTargetException{BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();Object retVal = null;for (PropertyDescriptor pd : pds){if (pd.getName().equals(propertyName)){Method methodGetX = pd.getReadMethod();retVal = methodGetX.invoke(pt1);break;}}return retVal;}}

运行结果如下所示:


2.5、代理

2.5.1、概念和作用

代理是指要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能。

2.5.2、代理架构图

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下图所示:

交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

2.6.3、动态代理

JVM在运行期间动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现一个或者多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标的结果外,还可以在代理方法中如下四个位置加上系统功能代码:

1.在调用目标方法之前

2.在调用目标方法之后

3.在调用目标方法前后

4.在处理目标方法异常的catch块中

(1)使用ProxyInvocationHandler创建

分析JVM动态生成的类并让动态生成的类成为目标类的代理,步骤如下:

   1创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。

   2编码列出动态类中的所有构造方法和参数签名

   3编码列出动态类中的所有方法和参数签名

   4创建动态类的实例对象

   5用反射获得构造方法

   6编写一个最简单的InvocationHandler,并调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去;打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。

   7将创建动态类的实例对象的代理改成匿名内部类的形式编写。

   8Proxy.newInstance方法直接一步就创建出代理对象。

   9怎样将目标类传进去?

直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。

InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。

让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。

   示例代码如下所示:

public class ProxyTest{public static void main(String[] args) throws Exception{// 创建实现Collection接口的动态类Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);// 查看其名称System.out.println("名称为 : " + clazzProxy1.getName());System.out.println("---------动态类中所有构造方法和参数签名--------");// 列出动态类中所有构造方法和参数签名Constructor[] constructors = clazzProxy1.getConstructors();for (Constructor constructor : constructors){// 获取参数签名String name = constructor.getName();StringBuilder sBuilder = new StringBuilder(name);sBuilder.append('(');Class[] clazzParams = constructor.getParameterTypes();for (Class clazzParam : clazzParams){sBuilder.append(clazzParam.getName()).append(',');}if (clazzParams != null && clazzParams.length != 0){sBuilder.deleteCharAt(sBuilder.length() - 1);}sBuilder.append(')');// 打印出其构造方法和参数签名System.out.println("其构造方法和参数签名为 :" + sBuilder.toString());}// 列出动态类中所有方法和参数签名System.out.println("********动态类中所有方法和参数签名********");Method[] methods = clazzProxy1.getMethods();for (Method method : methods){String name = method.getName();StringBuilder sBuilder = new StringBuilder(name);sBuilder.append('(');Class[] clazzParams = method.getParameterTypes();for (Class clazzParam : clazzParams){sBuilder.append(clazzParam.getName()).append(',');}if (clazzParams != null && clazzParams.length != 0){sBuilder.deleteCharAt(sBuilder.length() - 1);}sBuilder.append(')');System.out.println(sBuilder.toString());}// 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去System.out.println("******** 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去********");Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHander1());System.out.println(proxy1);System.out.println(proxy1.toString());proxy1.clear();// 将创建动态类的实例对象的代理改成匿名内部类的形式编写Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{return null;}});final ArrayList target = new ArrayList();System.out.println("--------用Proxy.newInstance方法直接一步就创建出代理对象--------");Collection proxy3 = (Collection) getProxy(target, new MyAdvice());Object obj = proxy3.add("zxx");proxy3.add("bxd");proxy3.add("lhm");// 打印创建的对象和调用对象的没有返回值的方法和getClass方法System.out.println();System.out.println(proxy3.size() + " -->" + proxy3.getClass().getName());}private static Object getProxy(final Object target, final Advice advice){// 用Proxy.newInstance方法直接一步就创建出代理对象Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{advice.beforeMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}}class MyInvocationHander1 implements InvocationHandler{@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{return null;}}
public interface Advice{ void beforeMethod(Method method); void afterMethod(Method method);}
public class MyAdvice implements Advice{long beginTime = 0;public void afterMethod(Method method){System.out.println("after itcast to work");long endTime = System.currentTimeMillis();System.out.println(method.getName() + " running time of " + (endTime - beginTime));}public void beforeMethod(Method method){System.out.println("come to itcast to study");beginTime = System.currentTimeMillis();}}

运行结果如下所示:

(2)工作原理图


(3)实现AOP功能的封装与配置

步骤如下:

 1工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。

 2BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

xxx=com.itheima.day3.aopframework.ProxyFactoryBeanxxx.advice=com.itheima.day3.MyAdvicexxx.target=java.util.ArrayList

 3ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供目标和通知的配置参数信息

 4编写客户端应用:

编写实现Advice接口的类和在配置文件中进行配置

调用BeanFactory获取对象

示例代码如下所示:

public class AopFrameworkTest{public static void main(String[] args) throws Exception{InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");Object bean = new BeanFactory(ips).getBean("xxx");System.out.println("bean.getClass().getName() : " + bean.getClass().getName());        ((Collection)bean).clear();}}
public class BeanFactory{Properties props = new Properties();public BeanFactory(InputStream ips){try{props.load(ips);}catch (IOException e){e.printStackTrace();}}public Object getBean(String name){String className = props.getProperty(name);Object bean = null;try{Class clazz = Class.forName(className);bean = clazz.newInstance();}catch (Exception e){e.printStackTrace();}if (bean instanceof ProxyFactoryBean){Object proxy = null;ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;try{Advice advice = (Advice) Class.forName(props.getProperty(name + ".advice")).newInstance();Object target = Class.forName(props.getProperty(name + ".target")).newInstance();proxyFactoryBean.setAdvice(advice);proxyFactoryBean.setTarget(target);proxy = proxyFactoryBean.getProxy();}catch (Exception e){e.printStackTrace();}return proxy;}return null;}}
public class ProxyFactoryBean{private Advice advice;private Object target;public Object getProxy(){Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler(){@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable{advice.beforeMethod(method);Object retVal = method.invoke(target, args);advice.afterMethod(method);return retVal;}});return proxy3;}// set和get方法。。。。。。}
原创粉丝点击