黑马程序员-java代理总结

---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------


一、代理概述

1、要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事物管理等等,该怎么做?

答:编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时增加系统功能的代码。

2、代理架构图:


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

    1)在调用目标方法之前;                     

    2)在调用目标方法之后;

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

void sayHello(){
    系统功能代码
    try{
        target.sayHello();
    }catch(Exception e){
        系统功能代码
    }
    系统功能代码
}

4、AOP(Aspect Oriented Program)

面向方面编程(Aspect Oriented Program,简称AOP)的目标就是要使交叉业务模块化,可以将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。而代理正是实现AOP功能的核心和关键技术


二、动态代理技术

1、要为系统中的各种目标类增加代理功能,那将需要许多代理类,若全部采用静态代理方式将十分麻烦。

2、JVM可以在运行期动态生成类的字节码,这种动态生成的类往往被用作代理类,这就是动态代理。

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

4、开源的CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以使用CGLIB库可以用于没有实现接口的目标类的代理。

例1:分析JVM动态生成的代理类

/*
 *用Proxy.getProxyClass方法创建实现了Collection接口的动态代理类并查看它的名字
 *列出动态代理类中的所有构造方法及其参数列表 
 *列出动态代理类中的所有方法及其参数列表
 */
package Com.cn.ItCast;

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

public class ProxyTest {

	public static void main(String[] args) {
		//创建实现了Collection接口的动态代理类并获得其名字
		Class proxyClazz=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(proxyClazz.getName());
		
		//列出动态代理类中的所有构造方法
		System.out.println(".........Constructors..........");
		Constructor[] constructors=proxyClazz.getConstructors();
		for (Constructor constructor : constructors) {
			String name=constructor.getName();
			StringBuilder sb=new StringBuilder(name);
			sb.append('(');
			Class[] paramClazzs=constructor.getParameterTypes();
			for (Class paramClazz : paramClazzs) {
				sb.append(paramClazz.getName()).append(',');
			}
			if(paramClazzs!=null&¶mClazzs.length!=0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(')');
			System.out.println(sb.toString());
		}
		
		//列出动态代理类中的所有方法
		System.out.println("..........Methods..............");
		Method[] methods=proxyClazz.getMethods();
		for (Method method : methods) {
			String name=method.getName();
			StringBuilder sb=new StringBuilder(name);
			sb.append('(');
			Class[] paramClazzs=method.getParameterTypes();
			for (Class paramClazz : paramClazzs) {
				sb.append(paramClazz.getName()).append(',');
			}
			if(paramClazzs!=null&¶mClazzs.length!=0)
				sb.deleteCharAt(sb.length()-1);
			sb.append(')');
			System.out.println(sb.toString());
		}
	}

}

输出结果:

com.sun.proxy.$Proxy0
.........Constructors..........
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
..........Methods..............
add(java.lang.Object)
remove(java.lang.Object)
equals(java.lang.Object)
toString()
hashCode()
clear()
contains(java.lang.Object)
isEmpty()
size()
toArray()
toArray([Ljava.lang.Object;)
addAll(java.util.Collection)
iterator()
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getInvocationHandler(java.lang.Object)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait(long,int)
wait(long)
wait()
getClass()
notify()
notifyAll()

例2:创建动态代理类的实例对象

//创建动态类的实例对象
System.out.println(".......create instance.........");
//Object obj=proxyClazz.newInstance();错误,动态代理类没有无参构造函数
//方法一:
/*Constructor constructor=proxyClazz.getConstructor(InvocationHandler.class);
Collection proxy=(Collection)constructor.newInstance(new InvocationHandler() {
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		return null;
	}
});*/
//方法二:
Collection proxy=(Collection)Proxy.newProxyInstance(
		Collection.class.getClassLoader(), 
		new Class[]{Collection.class},
		new InvocationHandler() {		
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				return null;
			}
		});
System.out.println(proxy.toString()); //null
proxy.clear();
//proxy.size();//错误,NullPointerException

我们用 匿名内部类的方式编写了一个最简单的InvocationHandler的子类。无论方法一还是方法二,我们 均获得了动态代理类的实例对象proxy,但它的toString()方法返回null,size()方法报错。对此,我们先修改一下InvocationHandler接口中的Invoke方法,

例3:

Collection proxy=(Collection)Proxy.newProxyInstance(
		Collection.class.getClassLoader(),
		new Class[]{Collection.class}, 
		new InvocationHandler() {
			ArrayList target=new ArrayList();//目标类
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				long beginTime=System.currentTimeMillis();//系统功能代码
				Object retVal=method.invoke(target, args);
				long endTime=System.currentTimeMillis();  //系统功能代码
				System.out.println(method.getName()+": running time :"+(endTime-beginTime));
				return retVal;
			}
		});
proxy.add("aaa");
proxy.add("bbb");
proxy.add("ccc");
System.out.println(proxy.size());
System.out.println(proxy.toString()); 
System.out.println(proxy.getClass().getName());
输出结果:

add: running time :0
add: running time :0
add: running time :0
size: running time :0
3
toString: running time :0
[aaa, bbb, ccc]
com.sun.proxy.$Proxy0

猜想分析动态代理类的内部代码

动态代理类实现了Collection接口(可以实现若干个接口),生成的代理类有Collection接口中的所有方法和一个接收InvocationHandler参数的构造方法。

①构造方法接收一个InvocationHandler对象来做什么,该方法的内部代码如何?

InvocationHandler handler;   //动态代理类内部定义一个InvocationHandler成员变量
public $Proxy0(InvocationHandler handler){
    this.handler=handler;               //接收外面传来的InvocationHandler对象
}
②实现Collection接口的动态代理类中的各个方法又是怎样实现的,InvocationHandler接口中的invoke方法接收的三个参数又是什么意义?

以proxy.add("aaa")为例,

add(Object obj){
    return handler.invoke(Object proxy,Method method,Object[]args);
}
其中, Object proxy指调用add方法的代理类对象proxy,Method method指add方法,Object[] args指add方法的参数“aaa”

③先前打印动态类的实例对象时,为什么结果为null,为什么有返回值的方法报错,没返回值的方法不报错?

因为这些方法都像②中的add方法一样,内部将调用请求转发给了InvocationHandler对象。而先前的invoke方法总是返回null,因此这些调用这些方法时的返回值也总是null,从而解释了为什么有前述现象发生。

④为什么动态类的实例对象的getClass()方法返回了正确结果,而不是返回target的类型?

因为,动态代理类从Object类继承的方法中,只有hashCode()、equals()、toString()方法会将调用请求转发给InvocationHandler对象,其他方法则不会。

让动态代理类成为任意目标类的代理

例3中的目标类target已经写死在代码里,同理,系统功能代码也同样写死,程序弹性很低。

①怎样将目标类由外部传进去?

让InvocationHandler实现类访问方法外面的final类型的目标类实例对象。

②怎样将系统功能代码由外部传进去?

将系统功能代码模块化,将其封装到一个对象的某个方法中,然后把这个对象作为参数传进来。接收者只要调用该对象的方法,就等于执行了外界提供的系统功能代码。

例4:

package Com.cn.ItCast;

import java.lang.reflect.Method;

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

}
package Com.cn.ItCast;

import java.lang.reflect.Method;

public class MyAdvice implements Advice {  //封装系统功能代码
	long beginTime;
	@Override
	public void beforeMethod(Method method) {
		beginTime=System.currentTimeMillis();		
	}

	@Override
	public void afterMethod(Method method) {
		long endTime=System.currentTimeMillis();
		System.out.println(method.getName()+": running time :"+(endTime-beginTime));
	}

}

private static Object getProxy(final Object target, final Advice advice) { //因为匿名内部类,参数必须是final类型的
	Object proxy=Proxy.newProxyInstance(
			target.getClass().getClassLoader(),
			target.getClass().getInterfaces(), 
			new InvocationHandler() {
				@Override
				public 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 proxy;
}
ArrayList target=new ArrayList();
Advice advice=new MyAdvice();
Collection proxy = (Collection)getProxy(target, advice);
proxy.add("aaa");
proxy.add("bbb");
proxy.add("ccc");
System.out.println(proxy.size());
System.out.println(proxy.toString()); 
System.out.println(proxy.getClass().getName());
输出结果:

add: running time :0
add: running time :0
add: running time :0
size: running time :0
3
toString: running time :0
[aaa, bbb, ccc]
com.sun.proxy.$Proxy1


三、实现AOP功能的封装与配置

/*
 * 工厂类BeanFactory负责创建目标类或代理类的实例对象,
 * 并通过配置文件实现切换。
 */
package Com.cn.ItCast;

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

public class BeanFactory {
	Properties props=new Properties();
	//构造方法接收关联了配置文件的输入流对象
	public BeanFactory(InputStream is){
		try {
			props.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/*getBean方法根据参数字符串返回一个相应的实例对象:
	 * 若从配置文件中读取的参数字符串不是ProxyFactoryBean,则直接返回该类的实例对象;
	 * 否则,返回该类实例对象的getProxy方法返回的对象。*/ 
	public Object getBean(String name){
		String className=props.getProperty(name);
		Object bean=null;
		try {
			bean=Class.forName(className).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 bean;
	}
}
/*
 * ProxyFactoryBean充当封装生成动态代理类的工厂,
 * 需要为其提供目标类target、建议Advice参数信息。
 */
package Com.cn.ItCast;

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

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 {
						advice.beforeMethod(method);
						Object retVal=method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				});
		return proxy;
	}

}
//配置文件格式
xxx=java.util.ArrayList
yyy.target=java.util.ArrayList
yyy.advice=Com.cn.ItCast.MyAdvice
yyy=Com.cn.ItCast.ProxyFactor...
/*
 * 客户端,调用BeanFactory获取对象
 */
package Com.cn.ItCast;

import java.io.InputStream;

public class AOPFrameworkTest {

	public static void main(String[] args) {
		InputStream is=AOPFrameworkTest.class.getResourceAsStream("config.properties");
		BeanFactory beanFactory=new BeanFactory(is);
		Object bean1=beanFactory.getBean("yyy");
		Object bean2=beanFactory.getBean("xxx");
		System.out.println(bean1.getClass().getName());
		System.out.println(bean2.getClass().getName());

	}

}
输出结果:

com.sun.proxy.$Proxy0
java.util.ArrayList


---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------详细请查看: www.itheima.com

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值