代理
代理是基本的设计模式之一,可以提供额外的或不同的操作。代理相当于一个中间人的角色,它替代了我们要操作的对象,当我们想对一个对象进行操作时,操作的是它的代理。
程序中的代理:周围已存在的多个具有相同接口的目标 类,想要为这些目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事物管理、等等,这就要编写一个与目标具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
注意:在看代理时需要先学习反射机制,因为反射机制是基础;
代理架构图
可以看出Proxy是Target的代理类,它们用于相同的接口,接口中定义了它们的共性方法,这个接口相当于一个标准,
规定了我们真加入什么样的目标类。我们调用代理中的的方法时,代理就会去调用目标类中的方法。
此外代理中还加入了系统的功能代码。
AOP 面向方面的编程(Aspect oriented program ,简称AOP)
AOP是代理技术的一个重要应用
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
此时要求在同一类业务的每一个实体中,分别定义另一业务的功能代码,这样就显得太啰嗦了,会让程序特别臃肿。
交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
动态代理技术
静态代理要求在程序编写阶段就将各种借口的代理类添加到代码中,当我们需要很多代理时,这种方法显然不可行。
1 目标类实现了接口:
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
2 目标类没有实现接口:
使用CGLIB(是个开源库,不是标准,以后可能变为标准,作为JDK的一部分)库可以动态生成一个类的子类,一个类的子类也可以用作该类的子类。即为一个没有实现接口的类生成动态代理类,可以使用CGLIB库。
对上面概念的理解:
这里生成的动态类必须实现一个或多个接口,是因为需要一个模板,这个模板决定了生成的动态代理类的结构。生成了所需要的动态代理类。(即这个动态代理类就是实现相同接口目标类的代理类)
类Proxy
表示动态代理类,提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
构造方法
Proxy(InvocationHandler h)
使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。
主要方法
getProxyClass(ClassLoader loader, Class<?>... interfaces)
返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。
接口 InvocationHandler
InvocationHandler 是代理实例的调用处理程序实现的接口。
每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。
方法
Object invoke(Object proxy, Method method, Object[] args)
在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
分析JVM动态生成的类
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数
编码列出动态类中的所有方法和参数
Java代码
1 //创建Collection类的代理
2 Class clazzProxy1 =
3 Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
4 System.out.println(clazzProxy1.getName());
5
6 //获取代理的构造函数
7 System.out.println(".....begin constructors list....");
8 Constructor[] constructors = clazzProxy1.getConstructors();
9 for(Constructor constructor:constructors){
10 String name = constructor.getName();
11 //创建一个缓冲区用来存储构造器
12 StringBuilder sb = new StringBuilder(name);
13 sb.append('(');
14 Class[] clazzParams = constructor.getParameterTypes();
15 //获取构造函数中的形参
16 for(Class clazzParam:clazzParams){
17 sb.append(clazzParam.getName()).append(',');
18 }
19 //删除构造函数中,最后一个形参末尾的','
20 if(clazzParams!=null&&clazzParams.length!=0)
21 sb.deleteCharAt(sb.length()-1);
22 sb.append(")");
23 System.out.println(sb.toString());
24 }
25
26 //动态代理中的方法
27 System.out.println("....begin methods list....");
28 //获取各方方法
29 Method[] methods = clazzProxy1.getMethods();
30 for(Method method:methods){
31 //方法名
32 String name = method.getName();
33 StringBuilder sb = new StringBuilder(name);
34 sb.append('(');
35 //获取方法中的形参
36 Class[] clazzParams = method.getParameterTypes();
37 for(Class clazzParam:clazzParams){
38 sb.append(clazzParam.getName()).append(',');
39 }
40 if(clazzParams!=null&&clazzParams.length!=0)
41 sb.deleteCharAt(sb.length()-1);
42 sb.append(')');
43 System.out.println(sb.toString());
44 }
结果
Java代码
45 com.sun.proxy.$Proxy0
46 .....begin constructors list....
47 com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
48 ....begin methods list....
49 hashCode()
50 equals(java.lang.Object)
51 toString()
52 add(java.lang.Object)
53 contains(java.lang.Object)
54 isEmpty()
55 size()
56 toArray()
57 toArray([Ljava.lang.Object;)
58 addAll(java.util.Collection)
59 iterator()
60 remove(java.lang.Object)
61 clear()
62 containsAll(java.util.Collection)
63 removeAll(java.util.Collection)
64 retainAll(java.util.Collection)
65 isProxyClass(java.lang.Class)
66 getInvocationHandler(java.lang.Object)
67 getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)
68 newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)
69 getClass()
70 notify()
71 notifyAll()
72 wait(long)
73 wait(long,int)
74 wait()
75 ....begin create instance object....
发现collection接口的代理类中的普通方法都和collection中的方法一致。
创建动态类的实例对象
根据上面的代码可以发现,代理类中只有一个构造方法Proxy(InvocationHandler h),此方法中需要传入一个InvocationHandler类,InvocationHandler是代理实例的调用处理程序实现的接口,此类中有一个invoke方法,此方法的作用是,当我们调用代理实体上的方法时,代理就会去调用关联类上对应的方法。
步骤
用反射获得构造方法
编写一个最简单的InvocationHandler类
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
Java代码
1 //获取代理类的构造函数
2 Constructor constructor =
3 clazzProxy1.getConstructor(InvocationHandler.class);
4 //自定义一个InvocationHandler类
5 class MyInvocationHandder1 implements InvocationHandler{
6
7 @Override
8 public Object invoke(Object proxy, Method method, Object[] args)
9 throws Throwable {
10 // TODO Auto-generated method stub
11 return null;
12 }
13
14 }
15 //创建代理对象
16 Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandder1());
17
上面的InvocationHandler子类也可以以匿名内部类的方式直接定义在代理类的构造函数中,这样可以简化书写。
Java代码
1 Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
2
3 @Override
4 public Object invoke(Object proxy, Method method, Object[] args)
5 throws Throwable {
6 // TODO Auto-generated method stub
7 return null;
8 }
9
10 });
Proxy中提供了方法可以一步创建动态代理对象,不用先创建代理类再创建代理对象。
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回一个指定接口的代理类实例。可以看出,这个方法是将先创建代理类和再创建代理对象这两个步骤融合到了一起,相当于:
Proxy.getProxyClass(loader, interfaces).
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { handler });
动态生成的类的内部代码实现
代理对象中最重要的就是InvocationHandler中的invoke方法。
可以看出invoke方法中三个参数,分别对应代理对象,方法,参数。当我们调用代理对象上的某方法时,代理对象并不是直接执行此方法,而执行InvokeHandler对象中的invoke方法,在invoke方法中执行目标类(就是被代理的类)中的相关的方法,并返回结果。在invoke的方法中目标类执行的前后可以加入一些功能代码,这也是AOP产生的基础,可以在一个业务的层面上,添加另外一个业务的功能。
动态代理原理图
如图所示,每当我们调用代理中的方法时,代理都会通过InvocationHandler中的invoke方法去调用目标类中的相关方法。但也有例外调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。也就是说代理对象从Object对象中继承的方法,只有hashCode,equals和toString会交给InvocationHandler执行,其它方法则不交给InvocationHandler执行。
让动态生成的类成为目标类的代理
此时我们可以生成目标类的代理,然后将系统功能代码模块化,即将切面代码也改为通过参数形式提供,把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码。这时我们需要一个接口来约定功能代码。
定义接口
Java代码
1 package cn.itcast.day3;
2
3 import java.lang.reflect.Method;
4 //定义一个接口,规定了我们想功能代码
5 public interface Advice {
6 void beforeMethod(Method method);
7 void afterMethod(Method method);
8 }
定义具体功能代码
Java代码
9 package cn.itcast.day3;
10
11 import java.lang.reflect.Method;
12 //实现了Advice接口,也重写了里面的方法,
13 public class MyAdvice implements Advice {
14 long beginTime = 0;
15 public void afterMethod(Method method){
16 System.out.println("从传智播客毕业了");
17 long endTime = System.currentTimeMillis();
18 System.out.println(method.getName()+"running time of"+(endTime-beginTime));
19 }
20
21 public void beforeMethod(Method method){
22 System.out.println("到传智播客学习来啦");
23 beginTime = System.currentTimeMillis();
24 }
25 }
创建代理对象,并加入功能代码
Java代码
26 //内部类中的局部变量必须是final
27 final ArrayList target = new ArrayList();
28
29
30 Collection proxy3 = (Collection) getProxy(target,new MyAdvice());
31 proxy3.add("zxx");
32 proxy3.add("1hm");
33 proxy3.add("bxd");
34 System.out.println(proxy3.size());
35 System.out.print(proxy3.getClass().getName());
36
37 }
38 //获取动态代理对象,并传入封装了系统功能代码的对象
39 private static Object getProxy(final Object target,final Advice advice) {
40 Object proxy3 = Proxy.newProxyInstance(
41 target.getClass().getClassLoader(),
42 target.getClass().getInterfaces(),
43 new InvocationHandler() {
44
45 @Override
46 public Object invoke(Object proxy, Method method, Object[] args)
47 throws Throwable {
48
49 advice.beforeMethod(method);
50 Object retVal = method.invoke(target, args);
51 advice.afterMethod(method);
52 return retVal;
53
54
55 }
56 });
57 return proxy3;