JavaSE 拾遗(18)——JavaSE 高新技术基础增强...类加载器和动态代理


类加载器


类加载器及其委托机制的深入分析

什么是类加载器

类加载就是在需要的时候把 class 字节码文件从硬盘加载到内存中 JVM 的方法区中,并完成 Verifying、Preparing、Resolving、Initialing,把字节码数据转换为 Class 对象的功能模块。


框图中各个步骤简单介绍如下:
Loading:文章前面介绍的类加载,将文件系统中的Class文件载入到JVM内存(运行数据区域)
Verifying:检查载入的类文件是否符合Java规范和虚拟机规范。
Preparing:为这个类分配所需要的内存,确定这个类的属性、方法等所需的数据结构。(Prepare a data structure that assigns the memory required by classes and indicates the fields, methods, and interfaces defined in the class.)
Resolving:将该类常量池中的符号引用都改变为直接引用。(不是很理解)
Initialing:初始化类的局部变量,为静态域赋值,同时执行静态初始化块。


类加载器的层级结构

JVM 虚拟机中允许安装多个类加载器,默认有 3 个类加载器,每个类加载器负责加载特定位置的类,这 3 个类加载器分别是:BootStrap、ExtClassLoader、AppClassLoader。类加载器也是 java 类 ClassLoader,类加载器本身也需要被加载,所有最终有一个类加载器不是 java 类,这就是 BootStrap。

类加载器被组织成一种层级结构关系,也就是父子关系。其中,Bootstrap是所有类加载器的父亲。如下图所示:


--Bootstrap class loader:
当运行java虚拟机时,这个类加载器被创建,它加载一些基本的java API,包括Object这个类。需要注意的是,这个类加载器不是用java语言写的,而是用C/C++写的。
--Extension class loader:
这个加载器加载出了基本API之外的一些拓展类,比如 jre/lib/ext 目录中的类,包括一些与安全性能相关的类。
--System Class Loader:
它加载应用程序中的类,也就是在你的classpath中配置的类。
--User-Defined Class Loader:
这是开发人员通过拓展ClassLoader类定义的自定义加载器,加载程序员定义的一些类。


委派模式(Delegation Mode)

类加载器的委托模式是指每个类加载器在加载类时,该类加载器首先检查自己的命名空间,该类是否已经加载(这个对应 ClassLoder 的findLoadedClass 方法),如果没有加载再委托给其上级类加载器,只有当所有上级类加载器不能加载类时,才会由该类加载器去尝试加载。

整个loadClass() 方法的执行步骤如下:

  1. 用findLoadedClass(String )来检查是否已经加载类,如果已经加载则直接返回。
  2. 在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器来加载
  3. 调用findClass(String)方法查找类

当 JVM 需要一个类A的时候,1.JVM派当前线程的类加载器去加载类A(默认是系统类加载器 appClassLoader,我们也可以自己指定当前线程类加载器);2.如果类A中还使用了类B,那么JVM会派刚刚加载类A的类加载器去加载类B(因为类加载器的可见性问题决定 上层类加载器对底层类加载器中的类不可见,所以只能派当前的类加载器加载,然后再根据委托机制,加载类B的加载器可能是当前类加载器或者是其上级类加载器);3.还可以使用 ClassLoader.loadClass() 方法来指定某个类加载器去加载类。

下层的加载器会将将任务委托给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查完了之后,按照相反的顺序进行加载,如果 Bootstrap 加载器找不到这个类,则往下委托,直到最初委托的类加载器找到类文件,如果最初的类加载器仍然找不到类,则抛出 NoClassFound 异常。对于某个特定的类加载器来说,一个Java 类只能被载入一次,也就是说在Java虚拟机中,类的完整标识是(classLoader,package,className)。一个类可以被不同的类加载器加载。


/**
 * 需求:获取一般类的类加载器
 */
package cn.itcast.day2.classloader;

/**
 * @author vivianZhao
 */
public class ClassLoaderTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 获取运行时加载本类的类加载器的类名, 打印结果说明系统默认类加载器是 appClassLoader
		System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
		
		// 获取本类的类加载器的类加载器的类名 appClassLoader 是 BootStrap 加载的
		System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getClassLoader());
		
		// 获取 appClassLoader 的父类名
		Class<?> superClass = ClassLoaderTest.class.getClassLoader().getClass();
		String blankString = "";
		while (superClass != null) {
			System.out.println(blankString + superClass.getName());
			blankString = blankString + "    ";
			superClass = superClass.getSuperclass();
		}
		
		// 获取 appClassLoader 所有上层类加载器
		blankString = "";
		ClassLoader superClassLoader = ClassLoaderTest.class.getClassLoader();
		while (superClassLoader != null) {
			System.out.println(blankString + superClassLoader.getClass().getName());
			blankString = blankString + "    ";
			superClassLoader = superClassLoader.getParent();
		}
	}
}

打印结果为:

null
sun.misc.Launcher$AppClassLoader
    java.net.URLClassLoader
        java.security.SecureClassLoader
            java.lang.ClassLoader
                java.lang.Object
sun.misc.Launcher$AppClassLoader
    sun.misc.Launcher$ExtClassLoader


自定义类加载器的原理分析

自定义类加载器主要是继承 ClassLoader 类

ClassLoader 类

java.lang.Object
      java.lang.ClassLoader


ClassLoader 中常用的方法

Class<?> loadClass(String name) 
          使用指定的二进制名称来加载类。 loadClass 会委托上级 ClassLoader 类加载该类,如果上级 Classloader 类不能加载该类,它才会调用 findClass 去加载该类

protected  Class<?> findClass(String name) 
          使用指定的二进制名称查找类。 

定义我们自己的 ClassLoader 一般只是重写 findClass 方法,读入我们的字节码文件中的数据存入字节数组中,然后调用 ClassLoader 的 defineClass 方法,完成加载该类Verifying、Preparing、Resolving、Initialing 的工作,defineClass 会返回已经加载完成的 Class 对象。


编写对class文件进行加密、解密的类加载器

myeclipse 运行的时候的当前用户目录是 project 的根目录。

软件开发中的目录配置的时候最好不要使用有空格的目录,因为很多时候我们的程序在处理配置参数的时候都是把空格当做两个参数的分割符,比如 dos 命令行就是,这样就会把我们一个路径的参数认为是两个参数。

使用异或一个 8 bits 的整数来加密

package cn.itcast.day2.classloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MyClassLoader extends ClassLoader {
	private String classPath;
	
	public MyClassLoader() {
		// TODO Auto-generated constructor stub
	}
	
	public MyClassLoader(String classPath) {
		// TODO Auto-generated constructor stub
		this.classPath = classPath;
	}
	
	/**
	 * @method: main
	 * @description: 对指定目录下的 Class file 进行加密,输出加密后的 class 文件到指定目录
	 * @param args
	 * @return: void
	 * @author: vivianZhao
	 * @date: 2013-7-23 上午11:21:22
	 * @version: 1.0
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		String srcPath = "D:\\Users\\vivianZhao\\Workspaces\\MyEclipse Professional\\"
				+ "Java5NewFeature\\bin\\cn\\itcast\\day2\\classloader\\ClassLoderAttachment.class";
		String destPath = "itcastlib";
		destPath += "\\" + srcPath.substring(srcPath.lastIndexOf('\\') + 1);
		FileInputStream fileInputStream = new FileInputStream(srcPath);
		FileOutputStream fileOutputStream = new FileOutputStream(destPath);
		cypher(fileInputStream, fileOutputStream);
		fileInputStream.close();
		fileOutputStream.close();
	}
	
	public static void cypher(InputStream inputStream, OutputStream outputStream)
			throws Exception {
		int buf;
		while ((buf = inputStream.read()) != -1) {
			outputStream.write(buf ^ 0xff);
		}
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		ByteArrayOutputStream bos = null;
		try {
			// 1.先用 File 字节码输入流读入 class 的字节码文件,转存入 ByteArray 输出流中
			InputStream ins = new FileInputStream(classPath + "//" + name + ".class"); 
			bos = new ByteArrayOutputStream(); 
			int b = 0;
			while ((b = ins.read()) != -1) {
				bos.write(b ^ 0xff); 
			}
			ins.close();
			byte[] bytes = bos.toByteArray(); // 2.把 ByteArray 输出流中的字节数据转为 byte[]
			// 3.使用 defineClass 方法用 byte 数组中的字节码数据定义一个类,返回该类的 Class 对象,我们通过
			// 使用该 Class 对象来使用这个类
			return defineClass(null, bytes, 0, bytes.length);
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try {
				bos.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return super.findClass(name);
	}

}
什么时候使用上级类加载器加载自定义的类,什么时候使用自己的类加载器加载自定义的类,这依靠自定义的类的目录,如果自定义的类在 project 下 bin 目录中自定义类对应的包的目录下,也就是 classpath 目 录下,那么上级加载器,自定义的类加载器的上级类加载器是当前线程的类加载器会加载该类(我们定义的类加载器的上级类加载器是当前线程类加载器,我们自定义的类加载器的类也是被该类加载器加载的),默认是 appClassLoader,如果当前线程类加载器找不到该 class ,就会让我们自定义的类加载器自己加载。

使用我们自定义的类加载器加载类

		Class<?> clazzClass = new MyClassLoader("itcastlib").loadClass("ClassLoderAttachment");
		System.out.println(clazzClass.getClassLoader().getClass().getName());

类加载器的一个高级问题的实验分析

类加载器中的类的可见性问题:下层的加载器能够看到上层加载器中的类,反之则不行,也就是是说委托只能从下到上。
类加载器中依赖类的类加载器和当前类的实际类加载器相同(由于委托机制,类的实际类加载器和指定的类加载器并不一定相同,因为有可能是其上级类加载器加载的),比如 class A extends B,我们在用指定的类加载器加载 A 类之后,系统会自动的加载 B 类,因为 A 类依赖于 B 类,如果依据委托机制最终加载的 A 类的加载器是 ClassLoaderEnd,那么 B 类一会用 ClassLoaderEnd 加载。这就导致,我们依赖的类的类加载器和其上级类加载加载的类,而不能是其下级类加载器加载的类。 所以类加载器的委派机制 和 当前类依赖的类只能和当前类的类加载器相同 这两个原则共同决定了,使用指定类加载器加载我们的类的时候,相互依赖的类一定要放在同一个类加载器管理的空间中,不然可能发生找不到类的情况。



动态代理

分析代理类的作用与原理及 AOP 概念

生活中的代理
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?代理商可以给他提供除核心业务外额外的服务。

程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 (参看下面的原理图)
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

代理的架构图


AOP 面向方面编程

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService  ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------


用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }


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


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

动态代理技术

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!需要写成百上千个代理类。又因为采用 AOP 编程方法的话,扩展的功能已经独立出来目标的功能也已经存在,要生成代理类的过程是重复并且简单的,所以提供了可以在 JVM 运行期动态生成出类的字节码这种技术,把生成代理类的这个重复的过程独立出来,就是动态代理技术,这种动态生成的类往往被用作代理类,所以就叫动态代理类。


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

CGLIB 库除了可以动态生成实现接口的动态类,可以动态生成一个类的子类这种动态类,一个类的子类也可以用作该类的代理(这也说明实现功能的扩展,既可以用继承也可以用组合,也就是既可以用横向扩展技术也可以用纵向扩展技术),所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。


代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能增强的代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中


创建动态类及查看其方法列表信息

api 提供的动态生成代理类的功能封装在 Proxy 类中,相关的信息查看 api 文档。

/**
 * 需求:使用 Proxy 创建动态代理类的 Class 对象,打印动态代理类的函数列表
 */
package cn.itcast.proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

/**
 * @class: DynamicProxyDemo
 * @package: cn.itcast.proxy
 * @description: TODO
 * @author: vivianZhao
 * @date: 2013-7-23 下午6:14:31
 * @version: 1.0
 */
public class DynamicProxyDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		createDynamicProxyAndMethodList();
	}

	public static void createDynamicProxyAndMethodList() {
		// 使用 Proxy 创建动态代理类
		Class<?> clazz = Proxy.getProxyClass(Collection.class.getClassLoader(),
				Collection.class);

		// 使用反射技术打印类的 Constructor
		System.out.println("------------------constructor------------------------");
		for (Constructor<?> constructor : clazz.getConstructors()) {
			StringBuilder constructorSignature = new StringBuilder(
					constructor.getName());
			Class<?>[] parameterTypes = constructor.getParameterTypes();
			printMethod(constructorSignature, parameterTypes);
		}

		// 使用反射技术打印类的 Method
		System.out.println("------------------method-----------------------------");
		for (Method method : clazz.getMethods()) {
			StringBuilder methodSignature = new StringBuilder(
					method.getName());
			Class<?>[] parameterTypes = method.getParameterTypes();
			printMethod(methodSignature, parameterTypes);
		}
	}

	private static void printMethod(StringBuilder functionSignature,
			Class<?>[] parameterTypes) {
		functionSignature.append('(');
		for (Class<?> parameterType : parameterTypes) {
			functionSignature.append(parameterType.getName() + ", ");
		}
		if(functionSignature.substring(functionSignature.length() - 2).equals(", ")){
			functionSignature.deleteCharAt(functionSignature.length() - 1);
			functionSignature.deleteCharAt(functionSignature.length() - 1);
		}
		functionSignature.append(')');
		System.out.println(functionSignature);
	}

}


创建动态类的实例对象及调用其方法

	public static void createDynamicProxyInstance() throws Exception {
		// 1.获取实现指定接口的代理类的 Class 对象
		Class proxyClass = Proxy.getProxyClass(
				Collection.class.getClassLoader(), Collection.class);
		// 2.用 Class 对象获取指定参数列表类型的 Constructor 构造器
		Constructor constructor = proxyClass
				.getConstructor(InvocationHandler.class);
		// 4.用 Constructor 对象创建代理类的实例
		Collection collection = (Collection) constructor
				.newInstance(new MyInvocationHandler());
		collection.clear();
		// collection.size();
	}

	// 3.定义自己的  InvocationHandler 实现类 
	public static class MyInvocationHandler implements InvocationHandler {

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			// TODO Auto-generated method stub
			return null;
		}
	}
InvocationHandler 是 Proxy 增强功能和 Target 功能的封装,Proxy.getProxyClass 只是抽象了我们自定义代理类的部分,其中代理类中的函数并没有实现具体的语句。


让jvm创建动态类及其实例对象,需要给它提供哪些信息:

  • 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
  • 产生的类字节码必须有个一个关联的类加载器对象
  • 生成的代理类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个 InvocationHandler 对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的 InvocationHandler 对象的 invoke 方法中加一点代码,就可以看到这些代码被调用运行了。
用 Proxy.newInstance 方法可以直接一步就创建出代理对象。

完成 InvocationHandler 对象的内部功能

	public static void createDynamicProxyInstance1() throws Exception {
		Collection collection = (Collection) Proxy.newProxyInstance(
				Collection.class.getClassLoader(),
				new Class[] { Collection.class }, 
				new InvocationHandler() {
					// 创建 Target 的对象
					ArrayList target = new ArrayList();
					Object returnValue = null;
					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						// 1.在 target 功能之前添加代理的额外功能
						long usedTime = System.currentTimeMillis();
						// 2.调用 target 功能						
						returnValue = method.invoke(target, args);
						// 3.在 target 功能之后添加代理的额外功能
						usedTime = System.currentTimeMillis() - usedTime;
						System.out.println("功能耗时 " + usedTime + "ms");
						return returnValue;
					}
				});
		collection.clear();
		collection.add("abc");
		collection.add("def");
		System.out.println(collection.size());
	}
这样就把 Targe 的功能集成到 Proxy 对象中了,而且 Proxy 的额外功能也添加上了,添加 Proxy 的额外功能还只需要写一次代码,因为用实现接口的匿名内部类时,构造函数不能带参数,不然还可以把 Target 用构造函数参数的形式传递进来。就这样把 Target、Proxy 的额外功能、实现 Proxy ,这三块完全分开。 用 反射+代理模式  可以实现代码实现和代码功能的分开,也就是以后有什么功能可以让我们在写一次功能代码的情况下实现各个地方都可以使用,并且不用修改源码,让 JVM 自动创建增强后的 class。 Proxy 负责 Class 和 代理类对象的创建,InvocationHandler 负责 Target、代理额外功能 的实现。

分析 InvocationHandler 对象的运行原理

动态生成的类实现了 Collection 接口(可以实现若干接口),生成的类有 Collection 接口中的所有方法和一个接受 InvocationHandler 类型参数的构造方法。构造方法接受一个InvocationHandler对象,InvocationHandler 类型对象主要负责动态类实例方法调用的处理,也就是辅助实现各个方法的具体功能。实现Collection接口的动态类中的各个方法的代码又是怎样的呢?

动态代理类的构造函数实现的伪语言表达形式

$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
}
动态代理类的其他函数实现的伪语言表达形式

$Proxy0 implements Collection
{
	InvocationHandler handler;
	public $Proxy0(InvocationHandler handler)
	{
		this.handler = handler;
	}
	//生成的Collection接口中的方法的运行原理
	int size()
	{
		return handler.invoke(this,this.getClass().getMethod("size"),null);
	}
	void clear(){
		handler.invoke(this,this.getClass().getMethod("clear"),null);
	}
	boolean add(Object obj){
		handler.invoke(this,this.getClass().getMethod("add"),obj);
	}
}
具体的实现肯定不是这样,为了能为所有接口都能生成上述的动态代理类,一定会使用反射的方式来构造。更加具体的实现方式可以参考 Proxy 的源码。

InvocationHandler接口中定义的invoke方法接受的三个参数的意义,图解说明如下:


先前打印动态类的实例对象时,结果是null,是因为调用了代理类实例的 toString 方法,该方法已经被代理类重写,重写为和接口方法一样的实现,所以会传递调用到 InvocationHandler 对象的 invoke 方法,因为那时候 invoke 方法是自动生成的,默认就返回 null。这也说明调用有基本类型返回值的方法时会出现 NullPointerException 异常,因为我们需要把默认返回的 null 类型,强制转换为返回值对应的类型,所以会发生 NullPointException。而动态类的实例对象的 getClass() 方法返回了正确结果,是因为代理类从 Objec t类继承的 hashCode, equals,  toString 这几个方法都已经被代理类重写,代理类将调用请求转发给 InvocationHandler 对象,对于其他 Object 的方法则没有重写。


总结分析动态代理类的设计原理与结构

动态代理的工作原理图


编写可生成代理和插入通告的通用方法

1.将目标类和目标对象传进去,有三种方法

  • 直接在 InvocationHandler 实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有代理类就不能灵活改动配置目标类。
  • 为 InvocationHandler 实现类注入目标类的实例对象,这样写是不能用匿名内部类的形式。
  • 让匿名的 InvocationHandler 实现类访问外面方法中的目标类实例对象的 final 类型的引用变量。
在 InvocationHandler 实现类中创建目标类的实例对象
		Collection collection = (Collection) Proxy.newProxyInstance(
				Collection.class.getClassLoader(),
				new Class[] { Collection.class }, 
				new InvocationHandler() {
					// 创建 Target 的对象
					ArrayList target = new ArrayList();
					Object returnValue = null;
					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						// 1.在 target 功能之前添加代理的额外功能
						long usedTime = System.currentTimeMillis();
						// 2.调用 target 功能						
						returnValue = method.invoke(target, args);
						// 3.在 target 功能之后添加代理的额外功能
						usedTime = System.currentTimeMillis() - usedTime;
						System.out.println("功能耗时 " + usedTime + "ms");
						return returnValue;
					}
				});
 
 
 
 
 

为 InvocationHandler 实现类注入目标类的实例对象

Vector v = new Vector();
		class MyInvocationHandler implements InvocationHandler
		{
			Collection target = null;
			
			public Collection bind(Collection target)
			{
				this.target = target;
				Collection proxy1 = (Collection)Proxy.newProxyInstance(
						ProxyTest.class.getClassLoader(),
						new Class[]{Collection.class} ,	
						this);
				return proxy1;
			}
			@Override
			public Object invoke(Object proxy, Method method,
					Object[] args) throws Throwable {
				// TODO Auto-generated method stub
				System.out.println("begin " + method.getName());
				Object retval = method.invoke(target, args);
				System.out.println("end" + method.getName());
				return retval;
			}
	
		}
		MyInvocationHandler handler = new MyInvocationHandler();
		Collection proxy1 = handler.bind(v);
让匿名的 InvocationHandler 实现类访问外面方法中的目标类实例对象的 final 类型的引用变量在下面的例子中演示

2.将系统功能代码模块化,即将切面代码也改为通过参数形式提供

createDynamicProxyInstance 函数

	public static void createDynamicProxyInstance1() throws Exception {
		Object target = new ArrayList();
		Advice advice = new MyAdvice();
		Collection collection = (Collection) createDynamicProxyInstance2(target, advice);
		collection.clear();
		collection.add("abc");
		collection.add("def");
		System.out.println(collection.size());
	}

	private static Object createDynamicProxyInstance2(final Object target, final Advice advice) {
		Object proxyObject = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						
						// 1.在 target 功能之前添加代理的额外功能
						advice.beforeMethod(method);
						// 2.调用 target 功能
						Object returnValue = method.invoke(target, args);
						// 3.在 target 功能之后添加代理的额外功能
						advice.afterMethod(method);
						
						return returnValue;
					}
				});
		return proxyObject;
	}
Advice 接口
package cn.itcast.proxy;

import java.lang.reflect.Method;

public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

实现 Advice 接口的 MyAdvice  类 

package cn.itcast.proxy;

import java.lang.reflect.Method;

public class MyAdvice implements Advice {

	private long usedTime;

	@Override
	public void beforeMethod(Method method) {
		// TODO Auto-generated method stub
		usedTime = System.currentTimeMillis();
	}

	@Override
	public void afterMethod(Method method) {
		// TODO Auto-generated method stub
		usedTime = System.currentTimeMillis() - usedTime;
		System.out.println(method.getName() + " 功能耗时 " + usedTime + "ms");
	}

}

程序打印结果:


实现类似 spring 的可配置的 AOP 框架

需求:要求在不修改 Client 代码的情况下,可以修改我们创建的对象的类型,并且配置是否创建目标功能的代理类对象,那么我们可以选用   配置文件 + 工厂模式  的方法。我们的需求在配置文件中配置好以后,使用工厂类对象读取配置文件获得我们要创建的对象的类的名字的字符串,使用该字符串来在工厂中使用反射来创建我们我们需要的实例。

现在我们的配置选项可以设置为我们要创建的对象要么是一般目标类对象,要么是目标代理类对象,创建目标代理类对象的话,还需要指定 目标类 和 Advice 接口的实现类。
配置文件格式可以为:
#xxxClassName=java.util.ArrayList
xxxClassName=cn.itcast.proxy.aopframework.ProxyFactoryBean
xxxClassName.target=java.util.ArrayList
xxxClassName.advice=java.util.ArrayList

其中 xxxClassName 可以是 Collection 这种我们 Client  程序中需要的对象的类型的名字

config.properties 文件

Collection=java.util.ArrayList
#Collection=cn.itcast.proxy.aopframework.ProxyFactoryBean
Collection.target=java.util.ArrayList
Collection.advice=cn.itcast.proxy.MyAdvice


定义一个bean工厂接口
定义一个bean工厂接口,专门负责生产指定的 javabean 的对象
package cn.itcast.proxy.aopframework;

public interface BeanFactory {
	Object getBean(String className) throws Exception;
}

定义一个 ConfigurableBeanFactory 类

定义一个 ConfigurableBeanFactory 类实现 bean 工厂,它是根据配置文件产生 bean 类

/**
 * 
 */
package cn.itcast.proxy.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @class: ConfigurableBeanFactory
 * @package: cn.itcast.proxy.aopframework
 * @description: ConfigurableBeanFactory 就是 Bean 工厂,用来根据配置产生 JavaBean
 *               类的对象,还可以产生代理类型的 Bean 对象
 * @author: vivianZhao
 * @date: 2013-7-24 下午2:11:31
 * @version: 1.0
 */
public class ConfigurableBeanFactory implements BeanFactory {

	private Properties properties;

	public ConfigurableBeanFactory(InputStream propertiesInputStream) {
		// TODO Auto-generated constructor stub
		properties = new Properties();
		try {
			properties.load(propertiesInputStream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public Object getBean(String className) throws Exception {
		// 根据传入的类名去配置文件中拿到实际配置的类名
		String configClassName = properties.getProperty(className);
		Object beanObject = Class.forName(configClassName).newInstance();
		// 如果配置的对象是 代理工厂类对象,那么要返回就得是下面配置的目标和Advice组合形成的代理的对象
		if (beanObject instanceof ProxyFactoryBean) {
			ProxyFactoryBean proxyFactoryBeanObject = (ProxyFactoryBean) beanObject;
			String target = properties.getProperty(className + ".target");
			String advice = properties.getProperty(className + ".advice");
			proxyFactoryBeanObject.setTarget(target);
			proxyFactoryBeanObject.setAdvice(advice);

			beanObject = proxyFactoryBeanObject.getBean(target);
		}

		return beanObject;
	}

	public static void main(String[] args) throws Exception {
		InputStream propertiesInputStream = ConfigurableBeanFactory.class
				.getResourceAsStream("config.properties");
		BeanFactory beanFactory = new ConfigurableBeanFactory(
				propertiesInputStream);
		
		System.out.println(beanFactory.getBean("Collection").getClass().getName());
	}
}

定义一个 ProxyFactoryBean 类

定义一个 ProxyFactoryBean 类它是一个产生 代理类对象的工厂,它本身就是 javabean ,而且实现了 BeanFactory 接口。

package cn.itcast.proxy.aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import cn.itcast.proxy.Advice;

/**
 * @class: ProxyFactoryBean
 * @package: cn.itcast.proxy.aopframework
 * @description: TODO
 * @author: vivianZhao
 * @date: 2013-7-24 下午2:29:45
 * @version: 1.0
 */
public class ProxyFactoryBean implements BeanFactory{
	private String target;
	private String advice;

	public ProxyFactoryBean() {
		super();
	}

	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	public String getAdvice() {
		return advice;
	}

	public void setAdvice(String advice) {
		this.advice = advice;
	}

	@Override
	public Object getBean(String className) throws Exception {
		// TODO Auto-generated method stub
		Object target = Class.forName(this.target).newInstance();
		Advice advice = (Advice)Class.forName(this.advice).newInstance();
		return createDynamicProxyInstance(target, advice);
	}
	
	private static Object createDynamicProxyInstance(final Object target, final Advice advice) {
		Object proxyObject = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						
						// 1.在 target 功能之前添加代理的额外功能
						advice.beforeMethod(method);
						// 2.调用 target 功能
						Object returnValue = method.invoke(target, args);
						// 3.在 target 功能之后添加代理的额外功能
						advice.afterMethod(method);
						
						return returnValue;
					}
				});
		return proxyObject;
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值