现在的位置: 首页 > 综合 > 正文

黑马程序员 – java高级特性 – 动态代理

2014年08月31日 ⁄ 综合 ⁄ 共 9352字 ⁄ 字号 评论关闭

-------android培训java培训、java基础学习技术博客、期待与您交流!
----------

7.1 代理的概念与作用

1. 代理定义
当需要对一个对象的访问进行控制时,就可以建立这个对象的代理对象。
2. 代理作用
(1)要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等。
(2)编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
(3)如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

7.2 简单的代理

1.代理结构图

2.示例代码

//代理类需要实现的接口 Human
public interface Human {
	public void say();
}
//目标对象 Student
public class Student implements Human {
	public void say() {
		System.out.println("student say!");
	}
}
//代理对象 StudentProxy
public class StudentProxy implements Human {
	public void say() {		
		System.out.println("before chi fan!");//目标对象调用以前
				
		Human human = new Student();//目标对象方法
		human.say();		
		
		System.out.println("after chi fan");//目标对象调用以后
	}
}
//代理对象测试类 StudentProxyTest
public class StudentProxyTest {
	public static void main(String[] args) {
		Human human = new StudentProxy();
		human.say();
	}
}

3.实现过程分析
以上示例代码是代理的简单实现,从中可以看出,如果想要为一个对象做代理,通常需要具备以下几个条件:
(1)目标对象必须实现一个接口;
(2)代理对象实现和目标对象相同的接口;
(3)代理对象当中包含有目标对象的引用,通过两者实现的共同接口,可以调用接口中的方法。
(4)因为代理对象中通过接口来调用目标对象的,因此目标对象的特有方法,在代理对象中是无法访问的。
4.存在的问题
在实际开发中,需要生成的class有成百上千个,如果都是通过手动硬编码生成代理的话,这个是非常耗时耗力,而且不易维护。为了解决这个问题的方案就是动态代理。

7.3 AOP(aspect-oriented programming)面向方面的编程

(1)系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                         安全       事务         日志
  StudentService    ------|----------|------------|-------------
  CourseService     ------|----------|------------|-------------
  MiscService        ------|----------|------------|-------------
(2)用具体的程序代码描述交叉业务:
  method1         method2          method3
  {                      {                       {
  ------------------------------------------------------切面
  ....            ....              ......
  ------------------------------------------------------切面
  }                       }                       }
(3)交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
  ------------------------------------------------------切面
   func1         func2            func3
   {             {                {
   ....            ....              ......
   }             }                }
  ------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
备注:安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务。

7.4 动态代理

1.动态代理出现的原因
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
2.动态代理类实现
(1)目标类实现了接口
java.lang.reflect反射包中的Proxy类提供了用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。JVM生成的动态类只能用作具有相同接口的目标类的代理。
(2)目标类没有实现接口
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
3.代理类的AOP
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
(1)在调用目标方法之前
(2)在调用目标方法之后
(3)在调用目标方法前后
(4)在处理目标方法异常的catch块中

7.5 动态代理代码实现

7.5.1 利用java包中的Proxy类实现动态代理

JVM生成的动态类只能用作具有相同接口的目标类的代理。
1.常用方法
(1)获取代理类的Class对象
 Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces); 返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
(2)获取接口类的代理类实例
 Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
2.实现过程
方式1:通过反射获取动态代理类的实例对象
(1)用反射获得构造方法
(2)编写一个最简单的InvocationHandler类
(3)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
方式2:通过反射获取动态代理类的实例对象(通过匿名内部类方式)
(1)用反射获得构造方法
(2)调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的匿名内部类实例对象传进去
方式3:直接获取目标类的动态代理对象的实例
通过代理类Proxy中的静态方法newProxyInstance,传递的参数含义如下:
(1)ClassLoader loader:目标类的类加载器
(2)Class<?>[] interfaces:目标类实现的接口
(3)InvocationHandler h:InvocationHandler的匿名内部类

3.示例代码

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
(1)方式1
//1.用反射获得参数为InvocationHandler的构造方法
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
//2.建立自定义的实现InvocationHandler接口的类
class MyInvocationHandler1 implements InvocationHandler{
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {				
		return null;
	}			
}				
//3.创建动态代理类的实例对象
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());

(2)方式2
//1.用反射获得参数为InvocationHandler的构造方法
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
//2.创建动态代理类的实例对象(通过将实现InvocationHandler接口的子类对象以匿名内部类的形式传递进去)
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		return null;
	}	
});

(3)方式3
Object proxy3 = Proxy.newProxyInstance(				
		target.getClass().getClassLoader(),//1.目标类的类加载器
		target.getClass().getInterfaces(),//2.目标类实现的接口
		new InvocationHandler(){//3.InvocationHandler的匿名内部类
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				Object retVal = method.invoke(target, args);			
				return retVal;
			}					
		}
		);

4. 动态代理深入分析

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

$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);
	}
}

(2)构造方法接受一个InvocationHandler对象,调用代理对象的方法,会将方法自动转发到 InvocationHandler对象中的invoke方法。InvocationHandler接口中定义的invoke方法接受的三个参数的含义:

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;//将目标对象的返回值返回给代理对象对应的方法,作为该方法的返回值返回。
}		

proxy:代理对象
method:代理对象中调用的方法
args:代理对象调用方法中的参数

(3)在InvocationHandler对象invoke方法中,调用method.invoke(target, args),调用目标对象的方法。
将目标对象传入的方式:
方式1:直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。
方式2:为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
方式3:让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
(4)将系统功能代码模块化,将切面代码也改为通过参数形式提供,提供的方式如下:
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!

备注:
第一,调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
第二,用于为某个对象生成和返回其代理对象,源对象必须实现接口,生成的代理对象会实现与源对象相同的接口。源对象的类必须自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的。

7.6 AOP FrameWork

1. 实现步骤
(1)工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
(2)BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
 #xxx=java.util.ArrayList
 xxx=cn.itcast.ProxyFactoryBean
 xxx.target=java.util.ArrayList
 xxx.advice=cn.itcast.MyAdvice
(3)ProxyFacotryBean充当封装生成动态代理的工厂,根据配置文件中提供配置参数信息目标和通知产生目标类的代理类。
(4)编写客户端应用:
 编写实现Advice接口的类和在配置文件中进行配置,调用BeanFactory获取对象

2. 示例代码

(1)工厂类 BeanFactory

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 ProxyFatoryBean){
			Object proxy = null;
			ProxyFatoryBean proxyFatoryBean = (ProxyFatoryBean)bean;	
			try {
				Advice advice = (Advice)Class.forName(props.getProperty(name + ".advice")).newInstance();
				Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
				proxyFatoryBean.setAdvice(advice);
				proxyFatoryBean.setTarget(target);
				proxy = proxyFatoryBean.getProxy();
				System.out.println(proxy);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
			return proxy;
		}
		return bean;
	}
}

(2)生成代理 ProxyFactoryBean

//1. 通知接口
public interface Advice {
	public void beforeMethod(Method method);
	public void afterMethod(Method method);
}
//2. 通知实现类
public class MyAdvice implements Advice {
	private long beginTime;
	private long endTime;
	
	public void beforeMethod(Method method) {
		System.out.println("到传智播客来学习了!");
		beginTime = System.currentTimeMillis();
	}
	
	public void afterMethod(Method method) {
		System.out.println("从传智播客来毕业上班了!");
		endTime = System.currentTimeMillis();
		System.out.println(method.getName() + "running timeof:" + (endTime - beginTime));
	}
}
//3. 获取指定通知的代理类 ProxyFatoryBean
public class ProxyFatoryBean {
	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 proxy3 = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				target.getClass().getInterfaces(),
				new InvocationHandler(){
					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 proxy3;
	}
}

(3)AOP框架测试

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());
	}	
}

总结:
面向方面的编程(Aspect oriented program ,简称AOP)可以将交叉业务模块化。将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。代理技术,可以目标对象方法的调用,因此可以在代理对象中,可以控制对目标对象的访问。java中,提供了动态建立代理对象的方式。
(1)动态代理对象的建立
如果目标对象实现了接口,那么可以通过反射包中的Proxy,动态生成一个目标类的动态代理对象。建立的方法有两种,第一:首先,通过getProxyClass生成代理对象的字节码文件,然后调用字节码文件的构造函数,最后,通过构造函数新建出代理对象的示例。第二:可以将上面的过程简化,通过Proxy类的静态方法
newProxyInstance方法直接建立目标类的代理对象,需要传入目标类的类加载器,目标类实现的接口,以及代理实例的调用处理程序实现的接口InvocationHandlder。
(2)AOP中获取代理对象
用于为某个对象生成和返回其代理对象,源对象必须实现接口,生成的代理对象会实现与源对象相同的接口,注意,源对象的类必须自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的。该方法接口两个参数:一个是目标对象,另一个是封装了用户系统功能代码的Advice对象,该对象必须实现Advice接口。

------- android培训java培训、java基础学习技术博客、期待与您交流!
----------

抱歉!评论已关闭.