代理的概念与作用
生活中的代理
武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?
程序中的代理
要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。(参看下页的原理图)
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易
AOP
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
-----------------------------------------------------------------------切面
.... .... ......
--------------------------------------------------------------------------切面
} } }
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
Java为我们提供了Proxy来动态的生成类的字节码。
可一发现同过Proxy类的静态方法
getProxyClass(ClassLoader loader, Class<?>... interfaces)
来动态生成一个类。他参数为一个接口的字节码,以及加在该接口的加载器
可以用下面的程序打印生成的类的所有的方法列表
package liaoli.review3;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args)throws Exception{
Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
System.out.println(clazzProxy.getName());
//获取clazzProxy字节码对应的类的所有构造方法,并按照API文档的样式打印构造方法
System.out.println("-----------------printProxy constructors----------");
Constructor[] constructors =clazzProxy.getConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams =constructor.getParameterTypes();
for(Class clazzPara : clazzParams){
sBuilder.append(clazzPara.getName()).append(',');
}
if(clazzParams.length != 0 && clazzParams !=null){
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
//获取clazzProxy字节码对应的类的所有Method方法,并按照API文档的样式打印构造方法
System.out.println("-----------------printProxy methods----------");
Method[] methods = clazzProxy.getMethods();
for(Method constructor : methods){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams =constructor.getParameterTypes();
for(Class clazzPara : clazzParams){
sBuilder.append(clazzPara.getName()).append(',');
}
if(clazzParams.length != 0 && clazzParams !=null){
sBuilder.deleteCharAt(sBuilder.length() - 1);
}
sBuilder.append(')');
System.out.println(sBuilder);
}
}
}
打印结果为:
$Proxy0
-----------------print Proxy constructors----------
$Proxy0(java.lang.reflect.InvocationHandler)
-----------------print Proxy methods----------
add(java.lang.Object)
hashCode()
clear()
equals(java.lang.Object)
toString()
contains(java.lang.Object)
isEmpty()
addAll(java.util.Collection)
iterator()
size()
toArray([Ljava.lang.Object;)
toArray()
remove(java.lang.Object)
containsAll(java.util.Collection)
removeAll(java.util.Collection)
retainAll(java.util.Collection)
isProxyClass(java.lang.Class)
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
从以上代码发现动态生成的类的名字为$Proxy0,他就可以作为所有实现了Collection接口的一个代理类,而且此类只有一个构造方法$Proxy0(java.lang.reflect.InvocationHandler)
我们可以通过此构造方法创建来创建代理实例对象如下:
Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
Collection proxy1 = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
// TODO Auto-generatedmethod stub
return null;
}
}) ;
proxy1.clear();
//proxy1.size();//为什么调用有返回值的方法,报空指针异常
}
这里由于InvocationHandler是一个接口,不能直接new对象所以用了一个匿名内部类来实现参数的传递。InvocationHandler只有一个方法
Object invoke(Object proxy,Method method,Object[] args)throws Throwable
所以要实现该方法
发现创建的实例对象只能调用无返回值的方法,而不能调用又返回值的方法
这是因为InvocationHandler的invoke的返回值为null,这说明代理类只能调用无方回值的方法。Invoke方法中的个参数分别表示:
Object proxy表示的是代理类实例对象,
Method method表示代理类调用的方法
Object[] args表示的是代理类调用的方法的单数列表
返回值类型Object表示的是代理类调用的方法的返回值
这就能解释为什么以上代理类不能调用有返回值的方法了。
分析JVM动态生成的类
总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
产生的类字节码必须有个一个关联的类加载器对象;
getProxyClass(ClassLoader loader, Class<?>... interfaces)
生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
Collection proxy1 = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
// TODO Auto-generatedmethod stub
return null;
}
}) ;
我们也可以同过用Proxy的静态方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
方法直接一步就创建出代理对象。
Collection proxy2 = (Collection) Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
ArrayList target =newArrayList();
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
long beginTime = System.currentTimeMillis();
Object retval = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() +" method running time is " + (endTime - beginTime));
return retval;
}
}
);
proxy2.add("liaoli");
proxy2.add("psq");
proxy2.add("zz");
System.out.println(proxy2.size());
System.out.println(proxy2);
打印结果为:
add method running time is 0
add method running time is 0
add method running time is 0
size method running time is 0
3
toString method running time is 0
[liaoli, psq, zz]
发现如果ArrayList target定义在public Object invoke(Object proxy, Methodmethod,Object[] args)方法内部,则发现打印结果为:
add method running time is 1
add method running time is 0
add method running time is 0
size method running time is 0
0
toString method running time is 0
[]
这是由于此时的Arraylist target为局部变量方法一结束就会消失。说以当方法束时,target也就不存在了。所以要将Arraylist target定义为InvocationHandler类名子类的成员变量。这样就可以的到预想的结果。
System.out.println(proxy2.getClass().getName());
的打印结果是:
$Proxy0
这是因为Proxy动他生成的类从Object继承的方法除了hashCode(),equals(),toString()方法委托给InvocationHandler处理之外其他方法都由自己实现。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
动态代理的产生必须接受一个实现了InvocationHandler接口的类,动态代理的代理的原理,当代理类的实例对象调用某个方法的时候,他会委托InvocationHandler调用目标类的对应的方法,并将目标类的方法的返回返回给代理类的方法。为了提高程序的扩展性我们可以把目标类作为一个参数传入,把要加入系统功能抽离出来用一个接口或抽象类来封装,作为另一个参数传入,而具体的实现细节由其子类其实现。这样可以大大的提高程序的扩展性能,我们可以可以传入不同的类,来为不同的类产生代理类,我们可以通过不同的系统功能写不同的实现方式的子类,而主程序不用更改只需改变参数即可。
下面我们就同过这种方法来实现上面的代码。
首先将心痛功能抽取成一个接口
package liaoli.review3;
import java.lang.reflect.Method;
public interface Advice {
void beforeMethod();
void afterMethod(Method method);
}
Myadvice 是具体的增加的系统功能,
package liaoli.review3;
import java.lang.reflect.Method;
public class MyAdvice implements Advice {
long beginTime;
long endTime;
@Override
public void beforeMethod() {
this.beginTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Method method){
this.endTime = System.currentTimeMillis();
System.out.println("runningtime of " + method.getName() +" method is " + (endTime -beginTime) + " mills");
}
}
下面是将获得代理抽取成一个独立的方法,这样我们可以为任意类实现了接口的类创生代理类。并且更具不同的需求,开发不同的系统功能类,则要将而这作为参数传入方法即可,其他都不用做任何改动。
packageliaoli.review3;
importjava.lang.reflect.InvocationHandler;
importjava.lang.reflect.Method;
importjava.lang.reflect.Proxy;
importjava.util.ArrayList;
importjava.util.Collection;
public classProxyOfArrayList3 {
public static void main(String[] args) {
ArrayListtarget = new ArrayList();
MyAdvice advice = new MyAdvice();
Collection proxy = (Collection)getProxy(target,advice);
proxy.add("heima");
proxy.add("I'm");
proxy.add("coming");
System.out.println(proxy.size());
System.out.println(proxy);
proxy.clear();
}
public static Object getProxy(finalObject target,final Advice advice) {
Object proxy = Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
@Override
publicObject invoke(Object proxy,
Methodmethod, Object[] args)
throwsThrowable {
advice.beforeMethod();
Objectretval = method.invoke(target, args);
advice.afterMethod(method);
returnretval;
}
});
return proxy;
}
}
}
运行结果:
running time of add method is 0mills
running time of add method is 0mills
running time of add method is 0mills
running time of size method is0 mills
3
running time of toString methodis 0 mills
[heima, I'm, coming]
running time of clear method is 0 mills
实现AOP功能的封装与配置
工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=liaoli.review3.ProxyFactoryBean
xxx.target=java.util.HashSet
xxx.advice=liaoli.review3.MyAdvice
ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
目标 target
通知 advice
编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象
实现代码如下:
package liaoli.review3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyFactoryBean {
private Objecttarget;
private MyAdviceadvice;
public Object getProxy(){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
newInvocationHandler(){
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
advice.beforeMethod();
Objectretval = method.invoke(target, args);
advice.afterMethod(method);
return retval;
}
});
return proxy;
}
public Object getTarget() {
returntarget;
}
public void setTarget(Object target) {
this.target = target;
}
public MyAdvice getAdvice() {
returnadvice;
}
public void setAdvice(MyAdvice advice) {
this.advice = advice;
}
}
测试代码:
package liaoli.review3;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class AOPFrameTest {
/**
* @paramargs
* @throwsFileNotFoundException
*/
public static void main(String[] args)throws FileNotFoundException {
// TODO Auto-generatedmethod stub
//FileInputStream in = newFileInputStream("config.properties");
InputStream in = AOPFrameTest.class.getResourceAsStream("config.properties");
BeanFactory bean = new BeanFactory(in);
Object proxy = bean.getBean("xxx");
System.out.println(proxy.getClass().getName());
}