THIS IS B3c0me

记录生活中的点点滴滴

0%

Java代理模式

一、代理模式概述

1.生活中的代理案例

  • 房屋中介
  • 商品代购

2.为什么要使用代理

  • 对于消费者而言,可以减少成本,只需要关心自己需要的商品,不需要去寻找渠道或者找房源

3.代理模式在Java中的应用

  • 统一异常处理
  • Mybatis使用了代理
  • Spring aop实现原理
  • 日志框架

4.概述

  • 代理模式(Proxy pattern):是23中设计模式中的一种,属于结构性的模式。指一个对象本身不做实际的操作,而是通过其他对象来得到自己想到的的结果
  • 意义:目标对象只需要关心自己的实现目标,通过代理对象来实现功能的增强,可以扩展目标对象的功能
  • 体现了一种重要的编程思想:不能随便修改源码,如果需要修改源码,可以通过代理的方式实现功能的扩展

二、代理的实现方式

2.1 Java中的代理图示

client通过访问代理类实现具体的功能

三、静态代理的实现

3.1 案例

通过代理模式实现事务操作

  • 创建domain对象

    1
    2
    3
    4
    public class Student {
    private String name;
    private int age;
    }
  • 创建service接口,定义规范

    1
    2
    3
    4
    5
    6
    public interface IStudentService {
    //添加学生
    void save();
    //查询学生信息
    Student query(Long id);
    }
  • 创建实现类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class StudentServiceImpl implements IStudentServiceice{
    public void save(){
    System.out.println("保存学生信息");
    }

    public Student query(Long id){
    Student student = new Student();
    student.setAge(20);
    student.setName("Tommy");
    return student;
    }
    }
  • 创建事务类对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class DaoTransaction {
    public void before(){
    System.out.println("开启事务操作");
    }

    public void after(){
    System.out.println("关闭事务");
    }
    }
  • 创建代理类对象

    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
    public class ProxyStudent implements IStudentService {
    //通过构造器获取目标类和增强类对象
    public ProxyStudent(StudentServiceImpl studentService, DaoTransaction daoTransaction){
    this.studentService = (StudentServiceImpl) studentService;
    this.daoTransaction = daoTransaction;
    }


    //目标类对象
    private final StudentServiceImpl studentService;
    //需要做的增强对象
    private final DaoTransaction daoTransaction;



    @Override
    public void save() {
    //开始事务
    daoTransaction.before();
    //目标类操作
    studentService.save();
    //关闭事务
    daoTransaction.after();
    }

    @Override
    public Student query(Long id) {
    return null;
    }
    }
  • 测试代理对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Main {
    public static void main(String[] args) {
    DaoTransaction daoTransaction = new DaoTransaction();
    StudentServiceImpl studentService = new StudentServiceImpl();
    //获取代理类对象
    ProxyStudent proxyStudent = new ProxyStudent(studentService, daoTransaction);
    proxyStudent.save();
    proxyStudent.query(220L);
    }
    }

3.2 静态代理存在的问题

  • 不利于代码的扩展,比如接口中新添加一个抽象方法的时候,所有实现类都必须重写
  • 代理类对象需要创建很多,这种设计很不方便

四、JDK动态代理

4.1 概述

在不改变原有功能代码的前提下,能够动态地实现方法的增强

4.2 JDK动态代理简单实现

【JDK动态代理】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去创建的动态代理对象,我们这里也使用这个方法去创建一个简单的【动态代理对象】以便于理解他的核心原理。

  • 定义Subject接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public interface Subject {
    /**
    接口方法
    */
    void doSomething();
    /**

    *@Desc:

    *@Params:

    *@Retuen:

    */
    void sayHello(String str);

    }
  • 创建Subject接口的实现类RealSubject

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class RealSubject implements Subject{

    @Override
    public void doSomething() {
    System.out.println("RealSubject doing somthing...");
    }

    @Override
    public String sayHello(String str) {
    System.out.println("RealSubject saying Hello...");
    return "hello" + str;
    }
    }
  • 定义代理对象创建工厂

    • 定义一个工厂类,该工厂类用于为target对象生产代理对象
    • 该工厂类借助Proxy.newProxyInstance来为目标对象创建代理对象
    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
    public class DynamicProxyFactory {

    /**
    * 创建target类的代理对象
    * 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
    *
    * @param <T> 泛型
    * @return 代理对象
    */
    public static <T> T getProxy(Object target) {
    // 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
    Object proxyInstance = Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("执行目标方法前");
    // 执行目标方法
    Object result = method.invoke(target, args);
    System.out.println("执行目标方法后");
    // 返回目标方法的执行结果
    return result;
    }
    });
    // 返回代理对象
    return (T) proxyInstance;
    }
    }
  • 创建测试类,为target创建代理对象

    • 该测试类会将内存中的动态代理对象保存到磁盘上,以便于我们后续分析生成的动态代理类的具体结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Client {

    public static void main(String[] args) {
    // 保存生成的代理类的字节码文件
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    // 目标对象
    RealSubject target = new RealSubject();
    // 使用JDK动态代理为【target对象】创建代理对象
    Subject proxy = DynamicProxyFactory.getProxy(target);
    // 调用代理对象的方法
    proxy.doSomething();
    System.out.println("=====================华丽的分割线=====================");
    proxy.sayHello("shelby");
    }
    }

4.3 JDK动态代理原理分析

在上面一步中我们使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject
public final class $Proxy0 extends Proxy implements Subject {
// 2、可以看到,在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m2;
private static Method m0;

// 以静态代码块的形式为属性赋值
//使用的是反射机制
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("proxy.Subject").getMethod("doSomething");
m4 = Class.forName("proxy.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

// 3、object父类的equals方法
public final boolean equals(Object var1) throws {
try {
// 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
// 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
// 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

// 4、目标接口的方法
public final void doSomething() throws {
try {
// 调用了【Proxy.h属性】的invoke方法
// 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

// 5、目标接口的方法
public final String sayHello(String var1) throws {
try {
// 调用了【Proxy.h属性】的invoke方法
// 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
return (String)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

}

  • 可以看到代理类【继承了Proxy类】,并且【实现了目标接口Subject】,覆写了接口中的【每一个方法】
    • 这也说明了为什么JDK代理需要实现接口,因为Java是单继承的,既然代理类继承了Proxy类那么就无法再继承其他类了
  • 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来(主要是为了方便后面的【反射调用】)
  • 在调用动态代理对象的某个方法时(比如:调用doSomething方法),实质上是调用的【Proxy类】的【h属性】的invoke方法
    • 所以我们要重点去看看这个【Proxy.h】到底是什么,其实它就是创建代理对象时我们传入的【InvocationHandler】

1.Proxy.newProxyInstance()是如何创建代理对象的

下面的源代码做了一些删减,只留下了最核心的部分

  • 通过下面代码我们就明确了使用【newProxyInstance】方法创建代理对象时所做的几件事情
    • 首先通过【目标接口】 + 【类加载器】创建一个Proxy类的【Class对象】
    • 然后通过这个【Class对象】获取到他的有参构造器,并且传入我们自定义的【InvocationHandler】作为构造函数参数,并且通过反射的方式调用有参构造器创建一个【Proxy对象】
    • 在【Proxy的有参构造器】中,会将传入的【InvocationHandler】保存到 【h属性】上(方便后面的supper.h.invoke调用)
    • 代理对象创建完毕
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
public class Proxy implements java.io.Serializable {

// h属性,保存我们传递进来的InvocationHandler
protected InvocationHandler h;

// 【有参构造器】注意这里的参数
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}

// 生成代理对象的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
throws IllegalArgumentException{

// 1、InvocationHandler强制不允许为空
Objects.requireNonNull(h);
// 获取到目标接口
final Class<?>[] intfs = interfaces.clone();

/*
* Look up or generate the designated proxy class.
* 2、获取到代理类的Class对象(也就是Proxy)
*/
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
* 通过反射执行 cl 的有参构造,也就是下面这个,可以看到通过反射执行Proxy有参构造,
* 将InvocationHandler赋值到了h属性上
*/
try {
// 3、获取到有参构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 4、通过构造器来创建一个代理对象并返回,这里传入的参数h 就是我们的【InvocationHandler】
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
// 省略....
}
}
}

2.问题分析

现在再来看上面抛出的三个问题:为什么创建代理对象时需要传入以下三个参数

  • 加载target类的类加载器

    • 因为动态代理类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而动态代理类则是在【运行过程中动态生成的类】。那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】
  • target类实现的接口

    为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行

    • 从上面的【动态生成的代理类的结构】来看,代理类继承了Proxy类,由于【Java是单继承】的,所以无法再通过继承的方式来继承【目标类】了。所以动态代理类需要【实现目标接口】,来重写接口的方法
  • InvocationHandler

    • 从【动态生成的代理类的结构】可以看出,我们传入的InvocationHandler最终会被作为一个属性保存到Proxy对象的【h属性】上。并且【动态代理对象】会覆写【目标接口的所有方法】,在方法中会使用 supper.h.invoke的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler动态代理的每个方法调用都会先走InvocationHandler.invoke()方法。因此InvocationHandler就可以起到方法增强或者方法过滤的作用。

五、CGLIB动态代理

5.1 AOP面向切面编程

面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

  • 主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等等。

  • 主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

  • AOP底层使用动态代理实现。包括两种方式:

    • 使用JDK动态代理实现。

    • 使用cglib来实现

  • 由AOP的概念我们将AOP视为一种横向的拦截器,也就是在执行业务逻辑之前和之后都可以进行一些额外的操作。

5.2 CGLIB动态代理实现

1.cglib包结构:

  • net.sf.cglib.core 底层字节码处理类。
  • net.sf.cglib.transform 该包中的类用于class文件运行时转换或编译时转换。
  • net.sf.cglib.proxy 该包中的类用于创建代理和方法拦截。
  • net.sf.cglib.reflect 该包中的类用于快速反射,并提供了C#风格的委托。
  • net.sf.cglib.util 集合排序工具类。
  • net.sf.cglib.beans JavaBean工具类。

2.cglib动态代理相关的基础类:

  • net.sf.cglib.proxy.Enhancer 主要的增强类。
  • net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,它是Callback接口的子接口,需要用户实现。
  • net.sf.cglib.proxy.MethodProxy JDK的java.lang.reflect.Method类的代理类,可以方便的实现对源对象方法的调用。

3.常用API

  • net.sf.cglib.proxy.Callback接口在CGLIB包中是一个重要的接口,所有被net.sf.cglib.proxy.Enhancer类调用的回调(callback)接口都要继承这个接口。
  • net.sf.cglib.proxy.MethodInterceptor能够满足任何的拦截(interception)需要。对有些情况下可能过度。为了简化和提高性能,CGLIB包提供了一些专门的回调(callback)类型:

    • net.sf.cglib.proxy.FixedValue 为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。

    • net.sf.cglib.proxy.NoOp NoOp回调把对方法调用直接委派到这个方法在父类中的实现。

    • net.sf.cglib.proxy.LazyLoader 当实际的对象需要延迟装载时,可以使用LazyLoader回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。

    • net.sf.cglib.proxy.Dispatcher Dispathcer回调和LazyLoader回调有相同的特点,不同的是,当代理方法被调用时,装载对象的方法也总要被调用。

    • net.sf.cglib.proxy.ProxyRefDispatcher ProxyRefDispatcher回调和Dispatcher一样,不同的是,它可以把代理对象作为装载对象方法的一个参数传递。

4.实现

  • 创建一个普通类作为我们的被代理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Person {

    public void eat(){
    System.out.println("I am eating...");
    }

    public void play(){
    System.out.println("I am playing...");
    }
    }
  • 创建一个拦截器MyApiInterceptor,实现MethodInterceptor

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MyApiInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    //此处可以做一些操作
    System.out.println("please wash your hand before eating");
    Object result = methodProxy.invokeSuper(o, objects);
    System.out.println("be careful when you play"); //调用方法之后的操作
    return result;
    }
    }

    Object result=proxy.invokeSuper(o, object); 表示调用原始类的被拦截到的方法。这个方法的前后添加需要的过程。在这个方法中,我们可以在调用原方法之前或之后注入自己的代码。

    由于性能的原因,对原始方法的调用使用CGLIB的net.sf.cglib.proxy.MethodProxy对象,而不是反射中一般使用java.lang.reflect.Method对象。

  • 创建类加强器Enhancer来生成代理对象

net.sf.cglib.proxy.Enhancer中有几个常用的方法:
  • void setSuperclass(java.lang.Class superclass) 设置产生的代理对象的父类。
  • void setCallback(Callback callback) 设置CallBack接口的实例。
  • void setCallbacks(Callback[] callbacks) 设置多个CallBack接口的实例。
  • void setCallbackFilter(CallbackFilter filter) 设置方法回调过滤器。
  • Object create() 使用默认无参数的构造函数创建目标对象。
  • Object create(Class[], Object[]) 使用有参数的构造函数创建目标对象。参数Class[] 定义了参数的类型,第二个Object[]是参数的值。

好了,至此,我们完成了一个简单的cglib实现动态代理。但是看起来还不够,比如说,我只想在吃饭这个api的前后进行一些额外的操作(比如洗手和休息),但是玩耍前后不需要增加额外的操作,那么怎么实现呢?

CallbackFilter可以实现不同的方法使用不同的回调方法。CallbackFilter中的accept方法, 根据不同的method返回不同的值i, 这个值是在callbacks中的顺序, 就是调用了callbacks[i]

完整代码:
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.pak;

import net.sf.cglib.proxy.*;

import java.lang.reflect.Method;

public class TestCglib {
public static void main(String[] args) {

// 定义一个回调接口的数组
Callback[] callbacks = new Callback[] {
new MyApiInterceptor(), new MyApiInterceptorForPlay()
};

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class); // 设置要代理的父类
enhancer.setCallbacks(callbacks); // 设置回调的拦截器数组
enhancer.setCallbackFilter(new CallbackFilterImpl()); // 设置回调选择器

Person person = (Person) enhancer.create(); // 创建代理对象

person.eat();
System.out.println("--------------------");
person.play();
}
}

class MyApiInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("吃饭前我会先洗手"); // 此处可以做一些操作
Object result = proxy.invokeSuper(obj, args);
System.out.println("吃完饭我会先休息会儿" ); // 方法调用之后也可以进行一些操作
return result;
}
}
class MyApiInterceptorForPlay implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("出去玩我会先带好玩具"); // 此处可以做一些操作
Object result = proxy.invokeSuper(obj, args);
System.out.println("玩一个小时我就回家了" ); // 方法调用之后也可以进行一些操作
return result;
}
}

class CallbackFilterImpl implements CallbackFilter {
@Override
public int accept(Method method) {
if (method.getName().equals("play"))
return 1;
else
return 0;
}
}


// 创建一个普通类做为代理类
class Person {
// 代理类中由普通方法
public void eat() {
System.out.println("我要开始吃饭咯...");
}

public void play() {
System.out.println("我要出去玩耍了,,,");
}
}

参考资料

1.https://blog.csdn.net/Hellowenpan/article/details/123482681

2.https://www.bilibili.com/video/BV1tY411Z799

3.https://blog.csdn.net/qq_25827845/article/details/87513102

欢迎关注我的其它发布渠道