------- android培训、java培训、期待与您交流! ----------
原理:要为已存在的多个目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法运行的时间、事务管理等等,就可以使用代理。
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
客户端原来直接调用Target,现在客户端调用代理类,代理类和目标类实现了相同的接口,也就是对外有相同的方法。
二,面向方面的编程AOP用的就是代理技术:
三,知识点:
。JVM可以在运行期间动态生成出类的字节码,这种动态生车的类往往被用作代理类,即动态代理类。
。JVM生成的动态类必须实现一个或多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
。第三方库CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用GCLIB库。
。代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中一下四个位置加入系统代码:
在调用目标方法之前
在调用目标方法之后
在调用目标方法前后
在处理目标方法异常的catch代码块中
package fighting;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
/**
* 单线程的情况下使用StringBuilder效率高一些,多线程的情况下使用StringBuffer
* StringBuffer需要考虑线程安全问题。
*
*/
public class ProxyTest {
/**
* 创建实现了Collection接口的动态类,查看其名称,
* 编码列出动态类中的所有构造方法和参数签名
* 编码列出动态类中的所有方法和参数签名
*/
public static void main(String[] args) throws SecurityException, Exception {
/**
* getProxyClass方法的返回值就是一个Class字节码,就是在内存生成一个字节码(即一个类),
* 但是在生成字节码的时候,你需要告诉JVM这个字节码实现了哪些接口,
* 还有一个参数ClassLoader:
* 每一个Class(即每一个字节码)都可以使用getClassLoader得到自己的类加载器
* 也就是说每一个Class一定是一个类加载器加载进来的
* 那么在JVM内存里操作了一个字节码,是没有ClassLoader的
* 所以要给它一个ClassLoader
* 所以第一个参数通常用getClassLoader方法来获得类加载器
*/
//创建实现了Collection接口的动态类,得到它的字节码clazzProxy1
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//查看动态类的名称
System.out.println(clazzProxy1.getName());//输出:$Proxy0
System.out.println("--------------begin constructors list---------------");
//利用字节码的getConstructors方法,获取所有构造方法的数组
Constructor[] constructors = clazzProxy1.getConstructors();
//迭代输出构造方法
for(Constructor constructor:constructors){
//得到构造方法名
String name = constructor.getName();
//构造成一个字符串生成器,初始值为得到的构造方法名
StringBuilder sBuilder = new StringBuilder(name);
//在方法名后加左大括号
sBuilder.append('{');
//得到构造方法的所有参数类型
Class[] clazzParams = constructor.getParameterTypes();
//迭代为sBuilder追加各个参数,参数之间用","隔开
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
//去掉参数列表最后一个","
if(clazzParams!=null && clazzParams.length != 0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
//在方法名和参数列表的末尾追加右大括号
sBuilder.append('}');
//输出结果
System.out.println(sBuilder.toString());
}
System.out.println("--------------begin constructors list---------------");
Method[] methods = clazzProxy1.getMethods();
for(Method method:methods){
//得到构造方法名
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('{');
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam:clazzParams){
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams!=null && clazzParams.length != 0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append('}');
System.out.println(sBuilder.toString());
}
//下面代码实现:创建动态代理类的实例对象
System.out.println("--------------begin create instance Object---------------");
//下面这句话newInstance()是要调用clazzProxy1的无参构造方法,因为没有不带参数的构造方法,所以不能使用这种方式实例化动态代理类
// clazzProxy1.newInstance();
//一,利用代理类的Class对象获得代理类的构造方法,构造方法的参数是InvocationHandler类型的
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return "999";//①
}
}
//利用代理类的构造方法实例化动态代理类
Collection proxy = (Collection)constructor.newInstance(new MyInvocationHandler());
System.out.println(proxy);//这里有些不明白?如果①那一行,return null,这里会报空指针异常,而老师运行时就没有问题?
//调用无返回值的方法,正常
proxy.clear();//清除集合
//调用有返回值的方法,会报错
// proxy.size();
//二,上面实例化动态代理类对象的方法也可以像下面这样写
Collection proxy1 = (Collection) constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
//三,创建代理对象的另一种方法,利用Proxy.newProxyInstance方法
//第二个参数不能用可变参数,因为可变参数只能用在参数列表的最后,所以只能用数组new Class[]{Collection.class}
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();
//②那一行写在这里,那么输出proxy3.size()的时候结果为0
//因为每调用一次proxy3的方法就会执行一次invoke方法,就会生成ArrayList对象,每次操作的是不同的ArrayList对象
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time is "+(endTime-beginTime));
return retVal;
}
});
//执行一次proxy3的方法,就会执行一次InvocationHandler的invoke方法
proxy3.add("zxx");//返回值就是invoke方法的返回值
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
//下面的这句按照理论应该返回ArrayList,但为什么会返回$Proxy0
//getClass从Object继承而来,而代理类只对Object中的hashCode、equals 或 toString 方法重写,
//代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
//因此下面的这句就会返回$Proxy0
System.out.println(proxy3.getClass().getName());
}
}
/**
* 猜想分析动态生成的类的内部代码:
* 动态生成的类实现了Colletion接口(可以实现若干接口),
* 生成的类有Collection接口中的所有方法和一个接受InvocationHandler参数的构造方法。
* 构造方法接收一个InvocationHandler对象,接收了对象要干什么用呢?
* 该方法内部的代码会是怎样的呢?
* 一定是像下面这样有一个成员变量去接收它,记住它,然后对它操作
* InvocationHandler handler;
* public $Proxy(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);
* }
*
* InvocationHandler接口中定义的invoke方法接收的三个参数又是什么意思呢?
* Client程序调用objProxy.add("abc")方法时,涉及三要素:objProxy对象,add方法,"abc"参数
* Class Proxy${
* add(Object object){
* return handler.invoke(Oject proxy,Method method,Oject[] args);
* }
* }
*/
下面,把上个例子的第三部分(//三,创建代理对象的另一种方法,利用Proxy.newProxyInstance方法),
编写可生成代理和插入通告的通用方法。
首先,要把ArrayList target = new ArrayList();//②这一句放到方法外面,作为参数传入(因为作成通用方法的话,不会一种确定的对象,有可能是ArrayList以外的对象类型调用这个方法),并且要加final修饰。
再把Proxy.newProxyInstance重构成一个方法。然后把需要提取出来的系统功能提取出来,放到另外的一个通用类中。结果如下:
//Advice接口
package fighting;
import java.lang.reflect.Method;
public interface Advice { void beforeMethod();
void afterMethod(Method method);
}
//MyAdvice实现Advice接口
package fighting;
import java.lang.reflect.Method;
public class MyAdvice implements Advice { long beginTime = 0;
public void afterMethod(Method method) {
System.out.println("fighting ended");
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time is "+(endTime-beginTime));
}
public void beforeMethod() {
System.out.println("fighting started");
beginTime = System.currentTimeMillis();
}
}
//三,创建代理对象的另一种方法,利用Proxy.newProxyInstance方法
//第二个参数不能用可变参数,因为可变参数只能用在参数列表的最后,所以只能用数组new Class[]{Collection.class}
final ArrayList target = new ArrayList();//② 局部内部类访问方法的局部变量要加final修饰
Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
//执行一次proxy3的方法,就会执行一次InvocationHandler的invoke方法
proxy3.add("zxx");//返回值就是invoke方法的返回值
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
//下面的这句按照理论应该返回ArrayList,但为什么会返回$Proxy0
//getClass从Object继承而来,而代理类只对Object中的hashCode、equals 或 toString 方法重写,
//代理类不重写从 java.lang.Object 继承的代理实例的其他公共方法,所以这些方法的调用行为与其对 java.lang.Object 实例的操作一样。
//因此下面的这句就会返回$Proxy0
System.out.println(proxy3.getClass().getName());
}
private static Object getProxy(final Object target,final Advice advice) {
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();
Object retVal = method.invoke(target, args);
advice.afterMethod(method);
return retVal;
}
});
return proxy3;
}