------- android培训、java培训、期待与您交流! ----------
49.分析代理类的作用与原理及AOP概念
1.代理的概念与作用
1)生活中的代理:
:电脑的代理商的例子
2)程序中的代理
<1>要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,
例如,异常处理,日志,计算方法的运行时间,事务管理,等等,怎么做呢?
<2>编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的
相同方法,并在调用方法时加上系统功能的代码。(参看原理图)
<3>如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,
在配置文件中配置是使用目标类,还是代理类,这样以后很容易切换,譬如,
想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,
以后运行一段时间后,又想去掉系统功能也很容易。eg.在ReflectTest2.java中
通过修改配置文件config.properties中的className = java.util.HashSet就可实现
代理类和目标类的切换。
3)程序代理代码举例:
class X
{
void sayHello()
{
System.out.println("hello,itcast");
}
}
class XProxy
{
void sayHello()
{
int starttime;
X.sayHello();
int endtime;
}
}
4)程序代理架构图:
2.AOP
1)系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
4)安全,事务,日志等功能要贯穿到好多个模块中,所以,它们是交叉业务。
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
3.动态代理技术
1)要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态
代理方式,将是一件非常麻烦的事情!因为要写成百上千个代理类,就太累了。
2)JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,
即动态代理类。
3)JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作
具有相同接口的目标类的代理。
4)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,
如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
5)代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,
还可以在代理方法中的如下四个位置加上系统功能代码:
<1>在调用目标方法之前
<2>在调用目标方法之后
<3>在调用目标方法前后
<4>在处理目标方法异常的catch块中
void sayHello()
{
......
try
{
target.sayHello();
}
catch(Exception e)
{
.......
}
......
}
50.创建动态类及查看其方法列表信息
1.类 Proxy(java.lang.reflect )
1)Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
动态代理类(以下简称为代理类)是一个实现在创建类时在运行时指定的接口列表的类,
该类具有下面描述的行为。 代理接口 是代理类实现的一个接口。 代理实例 是代理类的一个实例。
每个代理实例都有一个关联的调用处理程序 对象,它可以实现接口 InvocationHandler。
通过其中一个代理接口的代理实例上的方法调用将被指派到实例的调用处理程序的 Invoke 方法,
并传递代理实例、识别调用方法的 java.lang.reflect.Method 对象以及包含参数的 Object 类型的数组。
调用处理程序以适当的方式处理编码的方法调用,并且它返回的结果将作为代理实例上方法调用的结果
返回。
2)字段摘要
protected InvocationHandler h
此代理实例的调用处理程序。
3)构造方法摘要
protected Proxy(InvocationHandler h)
使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
4)方法摘要
static InvocationHandler getInvocationHandler(Object proxy)
返回指定代理实例的调用处理程序。
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
static boolean isProxyClass(Class<?> cl)
当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
2.补充:StringBuilder与StringBuffer的区别
在应用上基本一样,都是往字符串上动态地添加内容,单线程时用StringBuilder,因为它不用考虑
线程的安全问题,效率高些。多线程时用StringBuffer会比较安全些。
****这就如同走路一样,不安全的车道上左顾右看,行使慢,要是安全的单行道,就不用考虑安全问题,
速度就快。
51.创建动态类的实例对象及调用其方法
1.接口 InvocationHandler(java.lang.reflect)
1)InvocationHandler 是代理实例的调用处理程序 实现的接口。 每个代理实例都具有一个关联的
调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的
invoke 方法。
2)方法摘要
Object invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。
2.eclipse使用技巧之实现接口中的方法:
在子类MyInvocationHandler1--->Add unimplemented methods
3.注意:
若一个对象的打印显示为null,说明它的toString()方法返回为null,因为要是对象不存在而为null的话,
会报空指针异常,打印为null的对象若调用有返回值的方法,就会报java.lang.NullPointerException异常。
52.完成InvocationHandler对象的内部功能
1.每个代理实例都具有一个关联的调用处理程序(即InvocationHandler接口的实现类)。对代理实例调用方法时,
将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。 所以每调用一个代理实例的方法,
都会先去找InvocationHandler的invoke方法。在invoke方法内调用方法的具体实现代码为:
Object retVal = method.invoke(target,args);
53.分析InvocationHandler对象的运行原理
猜想分析动态生成的类的内部代码:
1.动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和
一个如下接受InvocationHandler参数的构造方法。
2.构造方法接收一个invocationHandler对象,接收对象了要干什么?该方法内部的代码会是怎样的呢?
$Proxy0 implements Collection
{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
}
3.实现的Collection接口中的各个方法的代码又是怎样的呢?
//生成的Collection接口中的方法的运行原理
int size()
{
return handler.invoke(this,this.getClass().getMethod("size"),null);
}
void clear()
{
handler.invoke(this,this.getClass().getMethod("clear"),null);
}
4.InvocationHandler接口中定义的invoke方法接收的三个参数又是什么意思?图解说明如下:
Client程序调用objProxy.add("abc")方法时,涉及三要素:objProxy对象,add方法,"abc"参数
Class Proxy$
{
add(Object object)
{
return handler.invoke(Object proxy,Method method,Object[] args);
}
}
5.分析先前打印动态类的空例对象时,结果为什么会是null呢?调用有基本类型返回值的方法为什么
会出现NullPointerException异常?
因为每次调用代理实例的方法,都会先找InvocationHandler的invoke方法,proxy1.size()返回值
是Integer,而invoke返回的却是null,故报异常。
6.分析为什么动态类的实例对象的getClass()方法返回了正确结果?
System.out.println(prxoy3.getClass().getName());
打印结果是:$Proxy0(而按刚才的分析,应该打印的是ArrayList)
调用代理实例对像从Object类继承的hashCode,equals,或toString这几个方法时,代理对象将调用
请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。
7.注意:
1)将return retVal改成return method.invoke(proxy,args);这样便是死循环。同时也验证了
在哪个代理上设置的参数。
2)Object obj = proxy3.add("zxx");
invoke返回值便是handler的返回值,也就是代理实例调用方法的返回值。
54.总结分析动态代理类的设计原理与结构
让动态生成的类成为目标类的代理:
1.分析动态代理的工作原理图如:汇集6-54-动态代理的工作原理图.
2.怎样将目标类传进去?
1)直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,
但没有实际意义。
2)为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。
3)让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。
3.将创建代理的过程必为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时
返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
4.将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码
参数形式提供?
1)把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用
这个对象的方法,即等于执行了外界提供的代码!
2)为bind方法增中一个Advice参数。
55.编写可生成代理和插入通告的通用方法
1.知识点:
1)方法里的内部类只能访问加final的局部变量。
2)eclipse使用技巧之重构方法:
选中代码-->Refactor-->Extract Method(抽取方法)-->名称:getProxy-->OK
3)希望你执行的系统功能(切面),这个接口的名称一般设成Advice
4)class类中的方法:
Class<?>[] getInterfaces()
确定此对象所表示的类或接口实现的接口。
56.实现类似spring的可配置的AOP框架
1.实现AOP功能的封装与配置
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)ProxyFactoryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
<1>目标
<2>通知
4)编写客户端应用:
<1>编写实现Advice接口的类和在配置文件中进行配置
<2>调用BeanFactory获取对象
2.对于设计思想,刚开始要在记事本中敲下如下代码和配置文件内容的方式进行比划:
Object obj = BeanFactor.getBean("xxx");
..............
config.properties
3.eclipse使用技巧
1)在eclipse中抓异常:
选中代码-->右键-->Surround With try/catch
2)先写好一个类,后运用该类的一个未创建的方法,之后在eslipse中自动生成该方法:
Create method getProxy() in type ProxyFactoryBean
4.知识点
1)JavaBean必须有一个不带参数的构造方法
2)配置文件一般都是properties格式的。
3)Properties类中方法:
String getProperty(String key)
用指定的键在此属性列表中搜索属性。
void load(InputStream inStream)
从输入流中读取属性列表(键和元素对)。
4)Class类中方法:
T newInstance()
创建此 Class 对象所表示的类的一个新实例。
InputStream getResourceAsStream(String name)
查找具有给定名称的资源。
5.需要创建的类:
BeanFactory.java(产生JavaBean的工厂,根据传入的名字,创建JavaBean对象)
ProxyFactoryBean.java(创建代理的工厂,本身也是个JavaBean)
测试类:AopFrameworkTest.java
配置文件:config.properties(类型为File,该文件可配置是否要用代理)