代理(张孝祥Java视频笔记)

49节视频

分析代理的原理与作用及AOP的概念

要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

1
编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码或者定义一个接口,接口里面有目标类的方法,然后让X类跟XProxy都实现这个接口,在XProxy的实现的方法里面,调用X的方法。

以计算方法的执行时间为例子:

MethodInterface.java

1
2
3
4
5
public interface MethodInterface {
void method1();
void method2();
void method3();
}

MethodRunTime.java

1
2
3
4
public interface MethodRunTime {
void methodStart(String methodName);
void methodEnd(String methodName);
}

OriginalClass.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OriginalClass implements MethodInterface {
@Override
public void method1() {
System.out.println("method1");
}

@Override
public void method2() {
System.out.println("method2");
}

@Override
public void method3() {
System.out.println("method3");
}
}

ProxyClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ublic class ProxyClass implements MethodInterface {

private MethodInterface target;
private MethodRunTime mMethodRunTime;

public ProxyClass(MethodInterface target, MethodRunTime methodRunTime) {
this.target = target;
mMethodRunTime = methodRunTime;
}

@Override
public void method1() {
mMethodRunTime.methodStart("method1");
target.method1();
mMethodRunTime.methodEnd("method1");
}

@Override
public void method2() {
mMethodRunTime.methodStart("method2");
target.method2();
mMethodRunTime.methodEnd("method2");
}

@Override
public void method3() {
mMethodRunTime.methodStart("method3");
target.method3();
mMethodRunTime.methodEnd("method3");
}
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Main {

public static void main(String[] args){

OriginalClass originalClass = new OriginalClass();

ProxyClass proxyClass = new ProxyClass(originalClass, new MethodRunTime() {

long startTime =0;
long endTime = 0;
long runTime=0;

@Override
public void methodStart(String methodName) {
startTime = System.currentTimeMillis();
}

@Override
public void methodEnd(String methodName) {

endTime = System.currentTimeMillis();
runTime = endTime-startTime;

System.out.println("The runTime of "+methodName+" is "+runTime+" mills");
}

});

proxyClass.method1();
proxyClass.method2();
proxyClass.method3();

}
}

daili2

在编写程序的时候不是直接引用目标对象,也不是直接引用代理对象,而是直接引用目标跟代理都实现的接口。

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!这个时候就可以使用动态代理。

JVM可以在运行期动态生成出Class,这种动态生成的Class往往被用作代理类,即动态代理类。JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。如果目标类没有实现接口,那么可以使用第三方的CGLIB库CGLIB可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

  1. 在调用目标方法之前
  2. 在调用目标方法之后
  3. 在调用目标方法前后
  4. 在处理目标方法异常的catch块中

注:
在实际的编程中,确实无需使用动态代理,但是在编写框架跟底层基础代码的时候,动态代理是非常有用的。

50节视频

创建动态类及查看其方法列表信息

分析java虚拟机动态生成的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//第一个参数指定的是生成的类的类加载器,因为这个类是JVM动态生成的没有类加载器,所以我们需要为其指一个类加载器,这个参数通常设置为这个代理类实现的接口的类加载器,及第二个参数的类加载器。
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());

System.out.println("----------begin constructors list----------");
/*
   $Proxy0()
   $Proxy0(InvocationHandler,int)
*/
Constructor[] constructors = clazzProxy1.getConstructors();
        for(Constructor constructor : constructors){
            String name = constructor.getName();
            StringBuilder sBuilder = new StringBuilder(name);//StringBuilder是线程不安全的,StringBuffer是线程安全的,单线程的时候选择StringBuilder效率比较高,多线程的时候选择StringBuffer
            sBuilder.append('(');
            Class[] clazzParams = constructor.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());            
        }
/*$Proxy0()
   $Proxy0(InvocationHandler,int)
*/
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());            
 }

51节视频

创建动态类的实例对象及调用其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

System.out.println("----------begin create instance object----------");
//Object obj = clazzProxy1.newInstance();
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);//动态类的构造方法没有无参数的
class MyInvocationHander1 implements InvocationHandler{

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
}
//创建动态类的实例  方法一
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());

System.out.println(proxy1);
proxy1.clear();
//proxy1.size();//只能调用获得的动态类的实例的没有返回值的方法,调用有返回值的方法会报错。
//System.out.println("111111111111111");

52节视频

完成InvocationHandler对象的内部功能

创建动态类的实例 方法二 利用了匿名内部类

1
2
3
4
5
6
7
8
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                return null;
            }

        });

创建动态类的实例 方法三 将动态类的获取及其实例的创建一起完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[]{Collection.class},new InvocationHandler() {

            ArrayList target = new ArrayList();  
            @Override
            public Object invoke(Object arg0, Method arg1, Object[] arg2)
                    throws Throwable {
                long beginTime  = System.currentTimeMillis();
                Object retVal = arg1.invoke(target,arg2);
                long endTime  = System.currentTimeMillis();
                System.out.println(arg1.getName()+"running time of "+(endTime-beginTime));
                return retVal;
            }
           });
proxy3.add("zxx");
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());

53节视频

分析InvocationHandler对象的运行原理

动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。

构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢? 实现Collection接口的动态类中的各个方法的代码又是怎样的呢?

在生成的动态类的内部有一个InvocationHandler类的实例,构造方法接收的InvocationHandler对象是为了初始化这个实例,具体代码见下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$Proxy0 implements Collection
{
    InvocationHandler handler;
    public $Proxy0(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);
    }
    boolean add(Object obj){
        return handler.invoke(this,this.getClass().getMethod("add"),obj);
    }
}

调用生成的动态代理类的实例对象的方法,实际上调用的是构造方法传递进去的InvocationHandler对象的invoke方法,每调用一次动态代理类实例对象的方法,就调用传递进去的InvocationHandler对象的invoke方法一次。

InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?

Client程序调用objProxy.add(“abc”)方法时,涉及三要素:objProxy对象add方法“abc”参数

1
2
3
4
5
6
Class Proxy$ {     
    boolean add(Object object) 
    { 
        return handler.invoke(Object proxy, Method method, Object[] args);    
     }
}

  • proxy 代表动态代理对象
  • method 代表正在执行的方法
  • args 代表调用目标方法时传入的实参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),new Class[]{Collection.class},new InvocationHandler() {

            ArrayList target = new ArrayList();  
            @Override
            public Object invoke(Object arg0, Method arg1, Object[] arg2)
                    throws Throwable {
                long beginTime  = System.currentTimeMillis();
                //可以对接收的参数arg2进行修改,例如写个过滤器,检查是否含有相关内容
                Object retVal = arg1.invoke(target,arg2);//如果将第一个参数换成 arg0,会形成死循环
                long endTime  = System.currentTimeMillis();
                System.out.println(arg1.getName()+"running time of "+(endTime-beginTime));
                //可以对接收的参数arg2进行修改,替换掉某些内容
                return retVal;
            }
           });
boolean object1 = proxy3.add("zxx");//InvocationHandler对象的invoke方法的返回值,传递给了Proxy对象的相关方法
boolean object2 =proxy3.add("lhm");
boolean object3 =proxy3.add("bxd");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());//打印结果是com.sun.proxy.$Proxy0

分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?

1
调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其他方法,则不转发调用请求。

54节视频

总结分析动态代理类的设计原理与结构

daili3

Advice

1
2
3
4
5
6
7
8
package cn.itcast.day3;

import java.lang.reflect.Method;

public interface Advice {
    void beforeMethod(Method method);
    void afterMethod(Method method);
}

MyAdvice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.itcast.day3;

import java.lang.reflect.Method;

public class MyAdvice implements Advice {
    long beginTime = 0;
    public void afterMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("从传智播客毕业上班啦!");        
        long endTime = System.currentTimeMillis();
        System.out.println(method.getName() + " running time of " + (endTime - beginTime));

    }

    public void beforeMethod(Method method) {
        // TODO Auto-generated method stub
        System.out.println("到传智播客来学习啦!");
        beginTime = System.currentTimeMillis();
    }

}

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
final ArrayList target = new ArrayList();            
Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
proxy3.add("zxx");
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());

private static Object getProxy(final Object target,final Advice advice) {
        Object proxy3 = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                /*new Class[]{Collection.class},*/
                target.getClass().getInterfaces(),
                new InvocationHandler(){

                    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() + " running time of " + (endTime - beginTime));
                        return retVal;*/


                        advice.beforeMethod(method);
                        Object retVal = method.invoke(target, args);
                        advice.afterMethod(method);
                        return retVal;                        

                    }
                }
                );
        return proxy3;
}

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器