您的位置:首页 > 其它

高新技术<六>---> 代理

2013-09-14 01:28 375 查看

第一 代理的概念及作用

一、概念:

1、生活中的代理:也就是所谓的代理商,从生产商和消费者之间搭建起一到桥梁,方便了消费者。

2、程序中的代理:给目标类的方法增加一些系统功能,如异常处理、日志、计算方法的运行时间、事物管理等等,增加公共后的类就是代理。

3、代理类的优点:

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类。这样以后很容易切换,如果想要日志功能时,就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想换掉系统功能也很容易。

二、AOP

1、简述:AOP(Aspect Oriented Program)即面向方面的编程。

2、示意图:

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



3、用具体的程序代码描述交叉业务:



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



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

实例:

/*
 * 代理
 */
public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		// 类加载器一般和接口属于同一个
		Class clazzProxy = Proxy.getProxyClass(
				Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy.getName());

		// 获取加载的类的构造函数
		Constructor[] constructors = clazzProxy.getConstructors();
		for (Constructor constructor : constructors) {
			String name = constructor.getName();
			StringBuilder strB = new StringBuilder();
			strB.append(name);
			strB.append('(');
			Class[] clazzParams = constructor.getParameterTypes();//得到构造函数的参数列表
			for (Class clazzParam : clazzParams) {
				strB.append(clazzParam.getName()).append(',');
			}
			if (clazzParams != null && clazzParams.length != 0) {
				strB.deleteCharAt(strB.length() - 1);
			}
			strB.append(')');
			System.out.println(strB);
		}

		// 获取加载的类的方法
		Method[] methods = clazzProxy.getMethods();
		for (Method method : methods) {
			String name = method.getName();
			StringBuilder strB = new StringBuilder(name);
			strB.append('(');
			Class[] clazzParams = method.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				strB.append(clazzParam.getName()).append(',');
			}
			if (clazzParams != null && clazzParams.length != 0) {
				strB.deleteCharAt(strB.length() - 1);
			}
			strB.append(')');
			System.out.println(strB);
		}
	}


第二 动态代理

一、概述:

1、要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,这时就不能采用静态代理方式,需用动态代理技术。

2、动态代理类:JVM可在运行时,动态生成类的字节码,这种动态(不是代理,只是拿出来作为代理类)生成的类往往被用作代理类,即动态代理类。

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

3、CGLIB库可以动态生成一个类的子类,一个类的子类也可以作为该类的代理,所以,如果要为一个没有实现接口的类生成动态代理,那么可以使用CGLIB库。

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

1)在调用目标方法之前

2)在调用目标方法之后

3)在调用目标方法前后

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

二、分析JVM动态生成的类

1、创建动态类的实例对象:

1)用反射获得构造方法

2)编写一个最简单的InvocationHandler的类

3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
public class ProxyTest {
	public static void main(String[] args) throws Exception{
//创建动态代理类的三种方式
		//方式一:通过接口的子类创建对象
		Collection proxy1 = (Collection)
				constructor.newInstance(new MyInvocationHandler());
		System.out.println(proxy1);//null
		System.out.println(proxy1.toString());//null
		proxy1.clear();//无异常
		//proxy1.size();//异常		
		//方式二:匿名内部类
		Collection proxy2 = (Collection)
				constructor.newInstance(new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						return null;
					}
				});
		
		//方式三:
		//通过代理类的newProxyInstance方法直接创建对象
		Collection proxy3 = (Collection)Proxy.newProxyInstance(
			//定义代理类的类加载器
			Collection.class.getClassLoader(),
			//代理类要实现的接口列表
			new Class[]{Collection.class},
			//指派方法调用的调用处理程序
			new InvocationHandler() {
				//创建集合,制定一个目标
				ArrayList target = new ArrayList();
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					//测试程序运行时间
					long beginTime = System.currentTimeMillis();
					//调用目标方法,将其从return抽出来,加入代理所需的代码
					Object retVal = method.invoke(target, args);
					long endTime = System.currentTimeMillis();
					//测试
					System.out.println(method.getName() + 
							" run time of " + 
							(endTime - beginTime));
					return retVal;
				}
			}
			);
		//通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法
		//当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法
		proxy3.add("123");
		proxy3.add("abc");
		proxy3.add("tvb");
		System.out.println(proxy3.size());
		System.out.println(proxy3.getClass().getName());
	}
}


2、让JVM创建动态类需要提供的信息:

1)生成类中的哪些方法,通过让其实现哪些接口的方式进行告知。

2)产生的类字节码必须有一个关联的类加载器对象

3)生成的类中的方法的代码是怎么样的,也得由我们自己提供,把我们的代码写在一个约定好的子接口对象的方法中,把对象传给它,它调用我们的方法,即相当于插入了我们自己的代码。提供执行代码的对象就是InvocationHandler对象,其实流程就是代理将参数传递给InvocationHandler的invoke方法,也就是代理在执行的时候就需要调用InvocationHandler的invoke方法。

三、分析动态生成的类的内部代码

1、构造方法接受一个InvocationHandler对象,接受此对象的用处:接受一个handler参数是为了记录它,以便在之后的程序中运用它。

2、调用代理涉及到三个因素:代理对象,代理对象的哪个方法,以及此方法接受的参数。要执行目标对象,只需要将代理对象作为目标对象即可。

3、对于上面代码中的proxy3.add()的分析:

1)代理对象调用add方法,传递了参数。

2)add方法内部会找到InvocationHandler中的invoke方法,将代理对象proxy传进去,把add方法传入,将参数传入代理对象中的handler参数,返回了一个结果,就是给了add方法,add方法继续向外返回给调用的对象proxy3,即最终结果。

其中的handler的invoke方法返回又来自于目标target返回值,从而将此返回值返给Object invoke()方法,即作为handler参数位置上的值返回给add方法。add方法作为最后的结果返回。

四、动态代理类原理和结构:

1、动态代理的工作原理:

1)Client(客户端)调用代理,代理的构造方法接受一个InvocationHandler,client调用代理的各个方法,代理的各个方法请求转发给刚才通过构造方法传入的handler对象,又把各请求分发给目标的相应的方法。就是将handler封装起来,其中this引用了当前的放(发来什么请求就接受哪个方法)。



2、把系统功能代理模块化,即切面代码也改为通过参数形式提供,怎么把要执行的系统功能代码以参数的形式提供:把要执行的代码装到一个对象的某个方法中,然后把此对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外接提供的代码。

示例:

/*
 * 通讯的契约
 * 一般说来应该有四个方法,前面、后面、前后、处理目标方法异常的catch块中,而且一般会有target、args、method参数
 * 但是这个例子中就之定义两个了
 */
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

public class MyAdvice implements Advice {
	long startTime;

	@Override
	public void beforeMethod(Method method) {
		System.out.println("开始计时");
		startTime = System.currentTimeMillis();
	}

	@Override
	public void afterMethod(Method method) {
		long endTime = System.currentTimeMillis();
		System.out.println(method.getName() + "run time"
				+ (endTime - startTime));
		System.out.println("结束计时");
	}

}

public class ProxyDemo {
	public static void main(String[] args) throws Exception {
		// 类加载器一般和接口属于同一个
		Class clazzProxy = Proxy.getProxyClass(
				Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy.getName());

		// 获取加载的类的构造函数
		Constructor[] constructors = clazzProxy.getConstructors();
		for (Constructor constructor : constructors) {
			String name = constructor.getName();
			StringBuilder strB = new StringBuilder();
			strB.append(name);
			strB.append('(');
			Class[] clazzParams = constructor.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				strB.append(clazzParam.getName()).append(',');
			}
			if (clazzParams != null && clazzParams.length != 0) {
				strB.deleteCharAt(strB.length() - 1);
			}
			strB.append(')');
			System.out.println(strB);
		}

		// 获取加载的类的方法
		Method[] methods = clazzProxy.getMethods();
		for (Method method : methods) {
			String name = method.getName();
			StringBuilder strB = new StringBuilder(name);
			strB.append('(');
			Class[] clazzParams = method.getParameterTypes();
			for (Class clazzParam : clazzParams) {
				strB.append(clazzParam.getName()).append(',');
			}
			if (clazzParams != null && clazzParams.length != 0) {
				strB.deleteCharAt(strB.length() - 1);
			}
			strB.append(')');
			System.out.println(strB);
		}

		// 创建实例对象,通过构造方法的方式
		Constructor constructor = clazzProxy
				.getConstructor(InvocationHandler.class);
		Collection proxy = (Collection) constructor
				.newInstance(new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						return null;
					}
				});
		System.out.println(proxy);// 打印纸是null,toString方法返回null
		proxy.clear();
		// System.out.println(proxy.size());//这里调用有返回值的方法就会报错,因为InvocationHandler中的invoke方法返回是null

		final ArrayList target = new ArrayList();
		// 另外一种创建对象的方法
		Collection proxy1 = (Collection) getProxy(target, new MyAdvice());
		proxy1.add("abc");
		proxy1.add("bcd");
		proxy1.add("jjc");
		System.out.println(proxy1.size());
		// 只把Object中的hashCode、equals 或 toString 交给代理,其他的都有自己的实现方式
		System.out.println(proxy1.getClass().getName());
	}

	/*
	 * AOP面向面的编程,就是在在调用目标方法前面、后面、前后、处理目标方法异常的catch块中加上系统功能代码
	 * 需要把添加的系统功能代码抽取成对象,然而在代理中必须调用系统功能对象,但是不知道其对象的名字,就没法调用
	 * 所以用接口进行约定,系统功能对象必须实现一个调用者知道的接口,这就叫通讯的契约,一般定义为Advice,也就是说
	 * 建议使用的系统功能,即将插入进来的这么一种建议
	 */
	private static Object getProxy(final Object target, final Advice advice) {
		//调用Proxy类的静态方法newProxyInstance直接创建对象
		Object proxy1 = Proxy.newProxyInstance(
		// Collection.class.getClassLoader(),
				target.getClass().getClassLoader(), target.getClass()
						.getInterfaces(), new InvocationHandler() {

					// 客户端程序调用代理的时候涉及三个要素:当前的代理对象、调用的方法、传递的参数
					// 方法内部就把三要素传递给了InvocationHandler的invoke方法
					/*
					 * @Override public Object invoke(Object proxy, Method
					 * method, Object[] args) throws Throwable { //
					 * 这个方法有返回值,InvocationHandler就会把这个返回值返回给代理,也就是代理调用了那个方法就返回给谁
					 * long startTime = System.currentTimeMillis(); Object
					 * objValue = method.invoke(target, args); long endTime =
					 * System.currentTimeMillis();
					 * System.out.println(method.getName() + "run time" +
					 * (endTime - startTime)); return objValue; }
					 */
					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// 这个方法有返回值,InvocationHandler就会把这个返回值返回给代理,也就是代理调用了那个方法就返回给谁
						advice.beforeMethod(method);
						Object objValue = method.invoke(target, args);
						advice.afterMethod(method);
						return objValue;
					}
				});
		return proxy1;
	}
}


第三 AOP类似spring小框架

一、工厂类BeanFactory:

1、工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。

2、getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则返回该类示例对象的getProxy方法返回的对象。

3、BeanFactory的构造方法接收代表配置文件的输入流对象的配置文件格式如下:
xxx=java.util.ArrayList

#xxx=enhance.test.proxy.aopframework.ProxyFactoryBean

xxx.advice=enhance.test.proxy.MyAdvice

xxx.target=java.util.ArrayList

注意:其中的#代表注释当前行。

4、ProxyFactoryBean充当封装成动态的工厂,需为工厂提供的配置参数信息包括:

目标(target)

建议(advice)

5、BeanFactory和ProxyFactoryBean:

1)BeanFactory是一个纯粹的bean工程,就是创建bean即相应的对象的工厂。

2)ProxyfactoryBean是BeanFactory中的一个特殊的Bean,是创建代理的工厂。

看实例,首先是配置文件config.properties,这里配置文件需要放在相对路径上,也就是同一个包中,因为在代码引用的是相对路径。

#config.properties
xxx=java.util.ArrayList
#xxx=enhance.test.proxy.aopframework.ProxyFactoryBean
xxx.advice=enhance.test.proxy.MyAdvice
xxx.target=java.util.ArrayList
下面就是BeanFactory:

public class BeanFactory {
	private Properties props = new Properties();
	
	BeanFactory(InputStream in){
		try {
			props.load(in);
		} 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();//Bean中必须要有一个无参的构造函数
			if(bean instanceof ProxyFactoryBean){
				ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
				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);
				Object proxy = proxyFactoryBean.getProxy();
				return proxy;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bean;
	}
}
接下来就该是ProxyfactoryBean了:

public class ProxyFactoryBean {

	private Advice advice;
	private Object target;

	public Advice getAdvice() {
		return advice;
	}

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

	public Object getTarget() {
		return target;
	}

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

	public Object getProxy() {
		Object proxy = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// 这个方法有返回值,InvocationHandler就会把这个返回值返回给代理,也就是代理调用了那个方法就返回给谁
						advice.beforeMethod(method);
						Object objValue = method.invoke(target, args);
						advice.afterMethod(method);
						return objValue;
					}
				});
		return proxy;
	}

}
好了上面我们的mini小框架就完事了,下面就下一个测试测试一下吧:

public class AopFrameworkTest {

	public static void main(String[] args) {
		InputStream ips = AopFrameworkTest.class
				.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(ips).getBean("xxx");
		System.out.println(bean.getClass().getName());
	}

}
说明,当需要运行代理类就把配置文件中的xxx=java.util.ArrayList用#注释掉,如果想要运行原类就把xxx=enhance.test.proxy.aopframework.ProxyFactoryBean注释掉就行了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: