您的位置:首页 > 职场人生

黑马程序员_Java基础加强_代理

2014-05-21 17:08 603 查看
一,代理

什么叫代理呢?要为已经存在的具有相同接口的目标类的每个方法增加一些额外的系统功能,比如:异常,日志,方法运行时间,事务管理等。

这时候就需要实现代理。可以生成一个代理类,该代理类拥有与给定类的相同的方法,并且调用该类的方法。

比如:

class A

{

sayHello() {

}

}

要为该类的sayHello方法增加一个时间监控,观察该类的运行时间,就可以生成一个代理类:

proxyClass

{

time...

A.sayHello() {}

time...

}

这时候客户端不用调用A类,而直接调用proxyClass,就可以增加该功能。

我们发现A和proxyClass具有相同的方法,这时候就相当于A和proxyClass实现了同一个接口。

代理总结:代理类其实就是代替目标类去完成目标类的一些事情,它和目标类是有关联的,他们有相同的接口,但是

在实际过程中,代理类对象并不是真正的去实现某些服务,而是通过调用目标类对象的相关方法去完成特定的服务。

换句话说就是不直接调用目标对象,而是直接调用代理对象,让代理对象去调用目标对象。

如果采用工厂模式和配置文件的方式进行处理,则不需要修改客户端程序,可以再配置文件中配置要使用的是目标类还是

代理类,这样就很容易进行切换。比如:想要日志功能时使用代理类,不想要时使用目标类,增加系统功能也很容易,不想要

系统功能直接去掉就可以了。

代理是实现AOP(面向方面的编程)功能的核心和关键。

AOP:面向方面的编程,是指当多个方法中都存在相同的功能,那么这些功能就相当与一个切面,切入每个方法,使用代理就可以

将这些这些相同的功能放在每个方法的开头或结尾,那么程序在运行时的效果是一样的。

静态代理:由我们手动的为系统的各个接口的的类增加代理功能,在程序运行前就已经生成了代理类的.class文件。其缺点就是

每次我们都要建立不同的代理对象,灵活性差。

动态代理:程序运行时,利用反射机制,动态创建代理类,不需要每次自己创建代理对象。

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

如果要为没有实现接口的类生成动态代理类,可以使用CGLIB库。

代理类除了要调用目标类的方法,返回与目标类方法返回结果相同的结果外,可以在一下这些位置添加系统功能:

1,只在调用目标方法之前

2,只在调用目标方法之后

3,在调用目标方法的前后

4,调用目标方法抛出异常的catch语句块中。

自定义代理:

创建Collection接口的代理类,使用反射查看代理类的类名和该类中具有的方法以及构造方法。

import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
public static void main(String[] args) {

Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("代理类的名字" + clazz.getName());//com.sun.proxy.$Proxy0

//获取代理类的构造函数,打印的结果形式:类名(参数类型1,参数类型2,...)
System.out.println("-------------构造函数-------------");
Constructor[] constructors = clazz.getConstructors();
int i = 1;
for(Constructor constructor : constructors) {
StringBuilder sb = new StringBuilder();
sb.append(constructor.getName());
sb.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam : clazzParams) {
sb.append(clazzParam.getName() + ",");
}
if(clazzParams.length != 0)
sb.deleteCharAt(sb.length() - 1);
sb.append(')');
System.out.println("构造函数" + i + ": " + sb.toString());
i++;
}

//获取代类的普通方法,打印形式:方法名(参数类型1,参数类型2,...)
System.out.println("------------方法-----------");
Method[] methods = clazz.getMethods();
int j = 1;
for(Method method : methods) {
StringBuilder sb = new StringBuilder();
sb.append(method.getName());
sb.append('(');
Class[] clazzMethods = method.getParameterTypes();
for(Class clazzMethod : clazzMethods) {
sb.append(clazzMethod.getName() + ",");
}
if(clazzMethods.length != 0)
sb.deleteCharAt(sb.length() - 1);
sb.append(')');
System.out.println("方法" + j + ":" + sb.toString());
j++;
}
}
}


输出结果是:

代理类的名字com.sun.proxy.$Proxy0

-------------构造函数-------------

构造函数1: com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)

------------方法-----------

方法1:add(java.lang.Object)

方法2:remove(java.lang.Object)

方法3:equals(java.lang.Object)

方法4:toString()

方法5:hashCode()

方法6:clear()

方法7:contains(java.lang.Object)

方法8:isEmpty()

方法9:size()

方法10:toArray()

方法11:toArray([Ljava.lang.Object;)

方法12:addAll(java.util.Collection)

方法13:iterator()

方法14:containsAll(java.util.Collection)

方法15:removeAll(java.util.Collection)

方法16:retainAll(java.util.Collection)

方法17:isProxyClass(java.lang.Class)

方法18:getInvocationHandler(java.lang.Object)

方法19:getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)

方法20:newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)

方法21:wait(long,int)

方法22:wait(long)

方法23:wait()

方法24:getClass()

方法25:notify()

方法26:notifyAll()

可以看出,代理类只有一个构造函数,并且没有空参数的构造函数。

方法都是实现的接口的构造方法以及该接口的父类中的方法。

代理类调用没有返回值的方法可以通过,调用有返回值的方法则失败。

创建代理类的实例对象:

Class proxyClazz = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
//方式一:内部类
//方式二:匿名内部类
Constructor cons = proxyClazz.getConstructor(InvocationHandler.class);
Collection proxy = (Collection)cons.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
//方式三:调用Proxy的静态方法:newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
Collection proxy2 = (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;
}
});


每次调用代理类的方法的时候,比如:proxy2.add(obj),都会调用一次invoke()方法,该方法的三个参数分别对应的是:
proxy ---> proxy2对象
method ---> add()方法
args ---> obj,add()方法的实际参数
以上三个就是代理程序的三要素。

需求:为目标类ArrayList的add()方法增加一个计算运行时间的功能。

public class ProxyTest {
public static void main(String[] args) throws Exception {

Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
new Class[] {Collection.class},
new InvocationHandler() {
//创建目标类示例对象
ArrayList target = new ArrayList();
//重写invoke()方法,每次调用代理类时,都计算目标方法的运行时间
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
Object retValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("++++" + (end - begin));
return retValue;
}

});

proxy3.add("abc");
System.out.println(proxy3);//打印目标集合
System.out.println(proxy3.getClass().getName());//com.sun.proxy.$Proxy0
}


Proxy3对象可以当做目标类的对象来使用,它里面的方法有目标类的方法,实现目标类的功能就可以只接通过代理对象调用
这些方法,就可以实现,同时也实现了系统功能(计算方法时间)。你要对反射很明白就可以很好理解了。

调用代理类的方法时,都会调用invoke()方法,我们可以在invoke()方法里面添加我们要增加的系统功能,
比如:异常,日志,方法运行时间,事务管理等,需要使用这些功能时调用代理类,不需要时调用目标类,
实现很容易的切换。

我们看到代理类调用getClass().getName()方法时,返回的结果是正确的代理类名字呢?
按理说,proxy3.getClass()这句话会将proxy3,getClass(),null作为参数传递给invoke()方法,
Object retValue = method.invoke(target, args);这句话的参数的target是要使用getClass()方法的对象,
返回值应该是ArrayList的字节码才对,为什么是Proxy0呢?
这是因为只有当调用代理类对象从Object中继承的toString(),equals(),hashCode()这三个方法时,代理对象
才会将转发请求转发给InvocationHandler对象,对于其他方法则不转发。

通过上述程序会发现定义的代理类只能实现某一个系统功能,真正在实际开发的时候是需要通过将自己的目标类和
要增加的系统功能通告以参数的形式传递给代理类的,请看下面的程序,编写一个带通告的通用代理类,来计算目标
类的方法的运行时间。

public class ProxyTest {
public static void main(String[] args) throws Exception {

final ArrayList target = new ArrayList();
final Advicer myAdvice = new MyAdvice();
Collection proxy3 = (Collection) getProxy(target,myAdvice);
//计算一下每个方法的运行时间
proxy3.add("abc");
proxy3.add("def");
proxy3.add("ghi");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());

}

//可以通过创建通告Adviser接口的实例对象,实现通告里面的功能。该代理方法可以接受任意类型的对象。
private static Object getProxy(final Object target,final Advicer myAdvice) {
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
myAdvice.before();
Object retValue = method.invoke(target, args);
myAdvice.end(method);
return retValue;
}
});
return proxy3;
}
}
//通告接口
public interface Advicer {
public void before();
public void end(Method method);
}
//实现通告的子类
public class MyAdvice implements Advicer {
long beginTime = 0;
public void before() {
System.out.println("----开始-----");
beginTime = System.currentTimeMillis();
}
public void end(Method method) {
long endTime = System.currentTimeMillis();
System.out.println(method.getName() +"++++" + (endTime - beginTime));
System.out.println("----结束-----");
}
}


二,实现AOP功能的封装与配置

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

其中getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串的配置文件中对应的

类名不是ProxyFactoryBean,则直接返回该实例对象,否则,返回该类实例对象的getProxy方法返回的

对象。

BeanFactory的构造函数接受代表配置文件的输入流对象,配置文件的格式如下:

#xxx=java.util.ArrayList

xxx=cn.itcast.ProxyFactoryBean

xxx.target=java.util.ArrayList

xxx.advice=cn.itcast.MyAdvice

需求:编写客户端应用:编写实现Advicer接口的类和在配置文件中进行配置

调用BeanFactory获取对象

本人在测试该程序的时候出现一个重大失误:

在config.profperties文件中配置.class文件的路径的时候,每个路径之间应该用"."号隔开,本人直接复制

类路径,导致花了相当常的时间。这是一个很容易出错的地方。配置文件的属性值之间是.隔开的,没有\分隔符。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: