动态代理 博客分类: java笔记 ProxyInvocationHandler动态代理BeanFactoryProxyFactoryBean
代理
1,代理的概念。
买电脑为例子,作为客户,找代理商买电脑,比到总部买电脑好。因为可以省去一些不必要的麻烦,比如车费等等。所以现实社会中出现了代理。
而Java中也是运用了这种思想,这种编程思想称为代理!
2,AOP(Aspect oriented program)面向方面的编程。
安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务
系统中有很多交叉的业务。
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
表示每个方法里面都得用到安全功能和日志功能。像一个切面一样都插到模块中。一个问题域。
我们现在要做的是把交叉业务给它模块化,只写一份。
只要是AOP编程就要使用到代理。
JVM生成的动态类只能用作具有相同接口的目标类的代理。
3,JVM动态生成代理类。
前提:给一个接口。会通过接口自动生成一个实现该接口的代理类。
Proxy类中的静态获得代理类字节码的方法。
static Class<?> |
getProxyClass(ClassLoader loader, Class<?>... interfaces) |
需要指定一个类加载器(通常使用后面接口的使用的类加载器),和实现的所有接口 作为参数。
4,JVM根据接口动态生成代理类,并对代理类进行基本的操作。
// 动态获取实现collection接口的类的字节码。
Class clazzProxy1 = Proxy.getProxyClass(Collection.class
.getClassLoader(), Collection.class);
//打印构造方法的名字和参数。
System.out.println("-------begin constructors list------");
Constructor[] constructors = clazzProxy1.getConstructors();
for (Constructor con : constructors) {
String name = con.getName();
// 在多线程的情况下用StringBuffer,在单线程用StringBuilder
StringBuilder sBuilder = new StringBuilder(name);
// 因为不考虑多线程的情况,所以使用StringBuilder来进行操作。这样效率会高很多,使用StringBuffer还要考虑线程安全,效率不高。
sBuilder.append('(');
Class[] clazzParas = con.getParameterTypes();
for(Class para:clazzParas){
sBuilder.append(para.getName()).append(',');
}
if(clazzParas.length!=0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
//打印普通方法的名字和参数。
System.out.println("-------begin methods list------");
Method[] methods = clazzProxy1.getMethods();
for (Method method : methods) {
String name = method.getName();
// 在多线程的情况下用StringBuffer,在单线程用StringBuilder
StringBuilder sBuilder = new StringBuilder(name);
// 因为不考虑多线程的情况,所以使用StringBuilder来进行操作。这样效率会高很多,使用StringBuffer还要考虑线程安全,效率不高。
sBuilder.append('(');
Class[] clazzParas = method.getParameterTypes();
for(Class para:clazzParas){
sBuilder.append(para.getName()).append(',');
}
if(clazzParas.length!=0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(')');
System.out.println(sBuilder.toString());
}
}
5,创建动态类的实例对象。
System.out.println("-------begin create instanceObject------");
// 通过动态类的字节码获得构造方法。
Constructor constructor= clazzProxy1.getConstructor(InvocationHandler.class);
class MyInvocationHander1 implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHander1());
// 下面打印结果为null。思考:没有创建成功proxy1还是proxy1的toString()方法返回是null。
System.out.println(proxy1);
// 可以用下面的方法来判断一个对象是否被正在的创建成功。
// 如果创建成功的话,那么就返回null,如果没有创建成功包NullPointerException
System.out.println(proxy1.toString());
// 直接一步到位,直接生成一个动态对象,和Class类中的newInstance方法很像,都是一步到位。
Collection proxy3 = (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 start = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
Thread.sleep(1000);
System.out.println(method.getName()+"方法,耗时"+(System.currentTimeMillis()-start)+"毫秒");
return retVal;
}
});
// 如果InvocationHandeler中的invoke方法返回的是null。
// 会出现空指针异常。
// 原因: proxy3.size()返回类型是int ,invoke方法中的返回的值需要赋给各个方法的调用结果。也就是说把null强转成int,出现空指针异常。其他方法同上。
// System.out.println(proxy3.size());
proxy3.add(1);
proxy3.add(4);
System.out.println(proxy3.size());
}
6,InvocationHandler接口的调用原理。
|
newProxyInstance |
动态类的构造函数接收一个InvocationHandler参数对象。
//猜想分析动态生成的类的内部代码。
class Proxy$0{
private InvocationHandler handler;
public Proxy$0(InvocationHandler handler){
this.handler = handler;
}
int size(){
return handler.invoke(this, this.getClass().getMethod("size"), null);
}
}
//关于动态代理的自己的认识:总共5点。
1, InvocationHandler的位置。构造函数通过接收一个InvocationHandler对象,生成一个代理对象。根据设计类的思想,该动态类中一定有个成员变量是InvocationHandler类型。因为下面要用到它。
2, 动态对象的实现。动态生成一个代理对象时候,这个对象其实就是实现了指定接口的实例对象。并且指定
并且实现了接口中的方法:例如,
int size(){
return handler.invoke(this, this.getClass().getMethod("size",null), null);
}
3,实现动态对象的方法。这些方法具体怎么实现的,都去找一个叫做handler的调用处理器。去固定地调用它的invoke方法。这个处理器根据这个实例对象字节码可以获取该对象,获得该对象的具体method,以及给定的参数。在获得该method后,再调用目标对象的对应方法。目标返回的值与代理对象对应。总之,代理用处是很大的。可以灵活的进行内部的操作,让客户端看起来那么的奇妙。
4,代理设计的巧妙之处:目标对象方法返回值给handler的invoke,invoke的返回值给代理方法。虽然中间过度了很多,但是它们的返回值都是一样。
5,代理实例对象中,并不是所有的方法都交给handler来处理。在从Object中继承的方法中,只有hashCode,equals,toString方法交给handler处理,也就是说只重写了这几个方法。
其他的例如getClass方法还是从Object中继承来的。(从Proxy的api中可以看到)
7,编写可生成代理和插入通告的通用方法。
// 传入目标对象,和通告。以后在玩spring的时候在配置文件里面配置目标,自己写通告里面的内容。
// 下面的就相当于一个spring框架了,一个黑匣子,外部不能随便更改。
private static Object getProxy(final Object target, final Advice advice) {
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.beforeAdvice(method);
Object retVal = method.invoke(target, args);
advice.afterAdvice(method);
return retVal;
}
});
return proxy;
}
8,实现类似spring的可配置的AOP框架。
1, 两个类:BeanFactory和ProxyFactoryBean
2, 一个配置文件:
xxx表示指定的是直接给定对象还是代理工厂。
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
3, 两个类详解:
BeanFactory中,加载配置文件。getBean通过配置文件xxx的指定,返回一个普通实例对象,还是动态代理对象。
ProxyFactoryBean确定xxx指定是代理工厂时,调用该类中的getProxy获得动态代理。
该bean中有属性target和advice,通过BeanFactory中的配置文件读取的target和advice对前面两个属性进行写入。