动态代理的工作原理简要分析

动态代理的工作原理

什么是代理

比如说我们去租房子,需要通过中介代理来进行看房子,由于代理的手中有大量资源,有大部分房源都在代理手中掌握,所以不需要我们自己去一个一个去找寻,通过这样我们无需知道房东是谁就可以租到合适的房子,这就是代理,代理模式两个优点一是可以隐藏委托类的实现,二是可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

什么是动态代理

代理类在运行时创建的过程叫做动态代理。也就是说这个代理类不是我们是事先写好的,而是由程序运行时自动生成的代理类,比如我们举一个静态代理例子:
我们首先定义一个接口ISayHello

1
2
3
public interface ISayHello {
void sayHello();
}

在写一个被代理对象:SayHello,并且继承自上面的接口,并实现接口中的方法。

1
2
3
4
5
6
public class SayHello implements ISayHello {
@Override
public void sayHello() {
System.out.printf("hello world proxy!");
}
}

接下来是重点我们接下来要写代理类,代理类和被代理类之间是依赖组合关系,也就是说被代理类和代理类是同时存在的,也就是生命周期是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SayProxy implements ISayHello{
private SayHello sayHello;
public SayProxy() {
sayHello=new SayHello();
}
@Override
public void sayHello() {
before();
sayHello.sayHello();
after();
}
public void before(){
System.out.printf("Say Hello Before");
}
public void after(){
System.out.printf("Say Hello After");
}
}

这样就可以达到动态扩充被代理类的内容,上面的内容是静态的动态代理,也就是代理类是我们已经写好的,接下来我们就要演示如何实现动态代理类。

动态代理的原理

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

Java提供了相应的代理类java.lang.reflect.Proxy,通过这个类能够动态生成代理类对象,接下来我们来做一下演示:

1
2
3
4
5
6
7
8
9
10
11
12
//动态代理相关内容实现
Class clazz = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("代理类名称:" + clazz.getName(System.out.println("代理类类加载器:" + clazz.getClassLoader());
Constructor[] constructors = clazz.getConstructors();
for (Constructor item : constructors) {
System.out.println("方法名字:" + item.getName());
Parameter[] parameters = item.getParameters();
for (Parameter parameter : parameters) {
System.out.println("参数类型:" + parameter.getParameterizedType() + "参数名称:" + parameter.getName());
}
}
}

首先获取Collection的代理类,Proxy提供了getProxyClass方法来进行获取代理类,这个方法有两个参数,第一个参数是加载这个代理类对象的类加载器,第二个参数是代理类对象实现的接口。我们来看一下输出结果内容:

代理类名称:com.sun.proxy.$Proxy0

代理类类加载器:null

方法名字:com.sun.proxy.$Proxy0

参数类型:interface java.lang.reflect.InvocationHandler 参数名称:arg0

分析一下上面代码内容,首先我们创建了实现Collection接口的动态代理类对象,并返回动态代理类的字节码,这时候我们用反射方式查看一下动态代理对象的构造方法以及动态代理对象的名称和动态代理构造器方法。
我们仔细看到加载com.sun.proxy.$Proxy0的类加载器是Bootstrap类加载器,由于Collection加载器就是Bootstrap类加载器。所以返回的是null,因为该类加载器不是java实现的。构造函数的参数类型是InvocationHandler这个接口类型主要是做什么呢?我们接下俩一步一步来进行解密,这里留一下一个小小的悬念。
接下来通过反射的方式获取下当前代理类所实现的方法有哪些:

1
2
3
4
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("方法名称:" + method.getName());
}

输出的结果如下所示:

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
方法名称:add  
方法名称:remove
方法名称:equals
方法名称:toString
方法名称:hashCode
方法名称:clear
方法名称:contains
方法名称:isEmpty
方法名称:iterator
方法名称:size
方法名称:toArray
方法名称:toArray
方法名称:spliterator
方法名称:addAll
方法名称:stream
方法名称:forEach
方法名称:containsAll
方法名称:removeAll
方法名称:removeIf
方法名称:retainAll
方法名称:parallelStream
方法名称:isProxyClass
方法名称:getInvocationHandler
方法名称:newProxyInstance
方法名称:getProxyClass
方法名称:wait
方法名称:wait
方法名称:wait
方法名称:getClass
方法名称:notify
方法名称:notifyAll

由于代理类实现了Collection接口,所以接口中所有类方法都会被继承,还有Object中的方法,当然Object中不是所有方法都会交给代理类来执行,只有hashCode,equals和toString方法会交给代理类处理。也就是说当我们调用collection.getClass()会输出com.sun.proxy.$Proxy0而不是输出目标对象ArrayList,这就说明Object中的方法不是所有方法都派发给目标对象来执行,只有上面说的三个方法才会委托给InvocationHandler中的invoke方法来进行执行。而其他的方法有自己的实现。
到这里我们的动态代理对象字节码分析完毕,这时候我们要创建动态代理的对象实例。
我们上面了解到动态代理对象是只有一个构造函数,这个构造函数传递参数是InvocationHandler接口,这时候我们就需要创建一个自己MyInvocationHandler对象实现InvocationHandler接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyInvocationHandler implements InvocationHandler {
//代理对象
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置通知
System.out.println("方法调用前:" + method.getName());
//代理对象方法调用
Object o = method.invoke(target, args);
//后置通知
System.out.println("方法调用后:" + method.getName());
return o;
}
}

我们来详细分析下这个接口中的方法,该接口中只有一个方法,也就是invoke方法,这个方法主要是什么作用呢?当我们调用代理类的方法是首先会调用MyInvocationHandler中invoke方法,通过调用这个方法来调用目标对象的方法体。实际上动态代理类实现的方法如下所示,例如我们现在实现的Collection接口的size方法,代理类对象的方法大致实现如下:

1
2
3
4
5
6
7
8
9
10
public  $Proxy0  implements Collection{

InvocationHandler hander;
public $Proxy0(InvocationHandler handler){
this.handler=handler;
}
int size(){
return handler.invoke(this,this.getClass().getMethod(“size”),null);
}
}

Handler的invoke 方法三要素:代理对象,代理对象方法,代理对象参数。
也就是说代理对象首先先调用我们构造函数中传递的handler对象的invoke方法,通过调用invoke方法来调用目标对象中存在的size方法。这也是Aop的原理,AOP的实现就是用了动态代理的方式进行操作的,目标对象返回的结果就是代理类返回的结果。
创建动态代理对象如下所示:

1
2
3
4
5
6
Constructor constructor = clazz.getConstructor(InvocationHandler.class);
Collection collection = (Collection) constructor.newInstance(new MyInvocationHandler());
collection.add("111");
collection.add("222");
collection.add("333");
System.out.println(collection.size());

这时候输出结果如下所示:

1
2
3
4
5
6
7
8
9
方法调用前:add  
方法调用后:add
方法调用前:add
方法调用后:add
方法调用前:add
方法调用后:add
方法调用前:size
方法调用后:size
3

正如我们上面分析的内容,我们来看一下动态代理类内部结构图:
代理内部结构图

当客户端调用代理对象的方法时,首先先经过InvocationHandler的invoke方法,这个方法体中可以扩充我们想要的内容,比如前置通知,后置通知,参数过滤等一系列操作,然后再通过该方法传入的method调用目标对象的方法,达到代理的作用。

实现AOP功能的封装和配置

实现思路是我们在配置文件中配置我们需要代理的目标对象,通知对象,通过配置文件进行切换是否生成代理对象,通过代理对象工厂进行判断是否生成代理类。工厂类BeanFactory负责创建目标类或代理类的实例对象,其getBean方法根据参数字符串返回一个响应的实例对象,也就是上面是通过ProxyFactoryBean进行判断是否生成代理对象,如果配置文件中配置的对象为ProxyFactoryBean的话,通过调用ProxyFactoryBean中的getProxy生成代理对象并返回,反之直接创建该对象实例并返回。
直接上代码首先看一下BeanFactory,主要是创建代理对象或者非代理对象实例

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
public class BeanFactory {
private Properties properties = new Properties();
//从配置文件中读取相应配置
public BeanFactory(InputStream ips) {
try {
    properties.load(ips);
catch (Exception ex) {}
}
public Object getBean(String name) {
String className = properties.getProperty(name);
try {
Class clazz = Class.forName(className);
Object bean = clazz.newInstance();
if (bean instanceof ProxyFactoryBean) {
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
String tagertClassName = properties.getProperty(name + ".target");
Class tagertClazz = Class.forName(tagertClassName); String adviceClassName = properties.getProperty(name + ".advice");Class adviceClazz = Class.forName(adviceClassName); proxyFactoryBean.setAdvice((Advice)adviceClazz.newInstance());
proxyFactoryBean.setTarget(tagertClazz.newInstance());
return proxyFactoryBean.getProxy();
}
return bean;
}catch (Exception ex){
System.out.println(ex.getMessage());
}
return null;
}
}

代码主要内容是首先构造函数是InputStream流用来加载properties配置文件的内容,配置相关内容如下:

1
2
3
xxx=com.test.Aop.ProxyFactoryBean
xxx.advice=com.test.Aop.MyAdvice
xxx.target=java.util.ArrayList

xxx也就是我们要获取的bean对象,如果对象类型为ProxyFactoryBean的话,就给该对象设置代理对象和通知对象。接下来我们看看ProxyFactoryBean中到底做了什么东西?前面我们已经提到了ProxyFactoryBean主要是创建代理对象。

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
41
/**
* 获得代理类
* Created by battleheart on 2017/6/29.
*/
public class ProxyFactoryBean {

//代理对象
private Object target;

//Advice通知
private Advice advice;

//获取代理对象
public Object getProxy() {
Object proxy = (Object) Proxy.newProxyInstance(getTarget().getClass().getClassLoader(),
getTarget().getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
getAdvice().before();
Object result = method.invoke(getTarget(), args);
getAdvice().after();
return result;
}
}
);
return proxy;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
}

target主要是目标代理对象,也就是我们要代理的目标对象,Advice对象主要是我们的通知对象,通知对象中只包含前置通知和后置通知这两个。主要是getProxy获取代理对象这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object getProxy() {
Object proxy = (Object) Proxy.newProxyInstance(getTarget().getClass().getClassLoader(),
getTarget().getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
getAdvice().before();
Object result = method.invoke(getTarget(), args);
getAdvice().after();
return result;
}

}
);
return proxy;
}

这个方法中我们可以看到直接调用了Proxy.newProxyInstance方法,而不再是调用Proxy.getClassProxy这个方法获取字节码的方式,这就告诉我们Proxy的创建可以分为两种方式进行创建,这种方式比较简便,方法中有三个参数第一个参数指定类加载器,第二个参数是代理对象要实现的接口有哪些,第三个方法就是InvocationHandler,这里我们传入的参数目标类的加载器,以及目标类实现的接口,第三个参数我们这边才用的是匿名内部类的方式传递。其中Invoke方法中要调用目标对象的方法,也就是method.invoke(target,args);
接下来我们看一下通知类Advice中写了什么内容。
首先我们声明了接口Advice

1
2
3
4
5
6
7
8
9
/**
* Created by battleheart on 2017/6/29.
*/
public interface Advice {
//前置通知
void before();
//后置通知
void after();
}

又写了MyAdvice类继承并实现Advice接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Created by battleheart on 2017/6/29.
*/
public class MyAdvice implements Advice {
public MyAdvice() {
}

@Override
public void before() {
System.out.println("前置通知");
}

@Override
public void after() {
System.out.println("后置通知");
}
}

通知方法中没有写太多东西就是输出一些信息表示已经应用了通知内容。
接下来就是调用方法:

1
2
3
4
5
6
7
8
9
InputStream ips = TestApplication.class.getClassLoader().getResourceAsStream("application.properties");
Object bean = new com.test.Aop.BeanFactory(ips).getBean("xxx");
Collection collection = (Collection) bean;
collection.add("111");
collection.add("222");
collection.add("333");
collection.add("444");
System.out.println(collection.size());
System.out.println(bean.getClass().getName());

由于我是用Spring boot 搭建的项目所以配置文件名称为application.properties,这个不重要,重要的是配置文件内容,上面我已经提到了第一种配置内容为

1
2
3
xxx=com.test.Aop.ProxyFactoryBean  
xxx.advice=com.test.Aop.MyAdvice
xxx.target=java.util.ArrayList

当我们调用getBean获取xxx时,如果xxx配置为ProxyFactoryBean的话就会创建代理类我们看一下输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
前置通知
后置通知
前置通知
后置通知
前置通知
后置通知
前置通知
后置通知
前置通知
后置通知
4
com.sun.proxy.$Proxy0

最后输出内容为¥Proxy0表示创建代理对象成功,并调用了通知方法,如果配置文件修改为如下:

1
xxx= java.util.ArrayList

输出内容则为:

1
2
4
java.util.ArrayList

因为没有创建代理对象所以获取类型是返回ArrayList类型。完结

文章目录
  1. 1. 动态代理的工作原理
    1. 1.1. 什么是代理
    2. 1.2. 什么是动态代理
    3. 1.3. 动态代理的原理
    4. 1.4. 实现AOP功能的封装和配置
|
载入天数...载入时分秒...