Java进阶:15.动态代理

一、代理模式

代理设计模式的原理:

使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。(原始对象:明星,代理对象:经纪人。原始对象:耐克服装,代理对象:衣服加工厂)

现阶段代理模式分为:

  • 静态代理

  • 动态代理

二、静态代理

静态代理特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。

这种代理方式需要代理对象和目标对象实现一样的接口

优点:可以在不修改目标对象的前提下扩展目标对象的功能

缺点:

  • 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
  • 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

举例:

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
// 制衣工厂的接口
interface ClothFactory{
void produceCloth();
}


// 原始对象类(被代理对象类)
class NikeClothFactory implements ClothFactory{

@Override
public void produceCloth() {
System.out.println("Nike工厂生产一批运动服");
}
}


// 代理对象类
class ProxyClothFactory implements ClothFactory{
private ClothFactory factory; // 用原始对象进行实例化

public ProxyClothFactory(ClothFactory factory){
this.factory = factory;
}

@Override
public void produceCloth() {
System.out.println("代理工厂开始做一些准备工作"); //扩展了额外功能

factory.produceCloth();

System.out.println("代理工厂做一些后续收尾工作");
}
}


public class StaticProxyTest {
public static void main(String[] args) {
// 原始对象(被代理对象)
NikeClothFactory nike = new NikeClothFactory();
// 代理对象
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
// 代理对象帮原始对象执行方法
proxyClothFactory.produceCloth();
}
}

代理工厂开始做一些准备工作
Nike工厂生产一批运动服
代理工厂做一些后续收尾工作

三、动态代理

动态代理是指在程序运行期间根据实际调用的原始目标,动态创建目标类的代理对象。运行的时候调用了哪个对象,就为该对象创建一个代理对象,编译的时候是不知道的

动态代理使用场合:调试、远程方法调用

动态代理相比于静态代理的优点:抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

要想实现动态代理,需要解决的问题?

  • 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
  • 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。

主要靠一个Java提供的方法: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
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
// 接口
interface Human{
String getBelief();

void eat(String food);
}

// 原始对象类
class SuperMan implements Human {

@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String food) {
System.out.println("I like eat " + food);
}
}


// 动态代理类
// 这个代理类并没有像静态代理时一样继承什么接口,它是一个通用的代理对象,可以代理任何运行时动态加载的类
// 想要让这个代理类能够实现动态代理,存在两个问题:
// 问题一:如何根据加载到内存中的原始对象,动态的创建一个代理类及其对象。
// 问题二:当通过代理类的对象调用方法a时,如何动态的去调用原始对象类中的同名方法a。
// 解决这两个问题,主要靠一个Java提供的方法:Proxy.newProxyInstance
class ProxyFactory {
// 调用此方法,返回一个代理类的对象。解决问题一
// 直接定义静态方法,不用建立对象
public static Object getProxyInstance(Object obj){
MyInvocationHandler handler = new MyInvocationHandler(obj); // 自定义的InvocationHandler,因为传入了obj,所以在调用被代理类的方法时,会根据这个obj去找其自己的方法
// Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(), // 参数1:根据传入的obj获取类的加载器
obj.getClass().getInterfaces(), // 参数2:根据传入的obj获取类实现的接口
handler // 参数3:传入一个实现了InvocationHandler接口的类的对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用
);
}
}


// 自定义动态代理实现
class MyInvocationHandler implements InvocationHandler{
private Object obj;

public MyInvocationHandler(Object obj){
this.obj = obj; //使用被代理类的对象进行赋值
}

//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()。解决问题二
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
//obj:被代理类的对象
Object returnValue = method.invoke(obj, args);
//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;
}
}

// 测试动态代理
public class DynamicProxyTest {
public static void main(String[] args) {
SuperMan superman = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superman);
//当通过代理类对象调用方法时,会自动调用被代理类中同名的方法
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("火锅");
}
}

I believe I can fly!
I like eat 火锅

此处未演示代理的扩展功能,下面结合AOP一起演示

四、动态代理与AOP

使用 Proxy 生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理。

下面介绍AOP,AOP是Aspect Oriented Programming,即面向切面编程。

首先回顾一下OOP:Object Oriented Programming,OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。

而AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)

以书的数据库管理业务BookService为例,它有几个业务方法:

  • createBook:添加新的Book;
  • updateBook:修改Book;
  • deleteBook:删除Book。

由于涉及到数据库管理,除了业务逻辑,还需要安全检查、日志记录和事务处理,它的代码像这样:

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
public class BookService {
public void createBook(Book book) {
securityCheck();
Transaction tx = startTransaction();
try {
// 核心业务逻辑
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
log("created book: " + book);
}

public void updateBook(Book book) {
securityCheck();
Transaction tx = startTransaction();
try {
// 核心业务逻辑
tx.commit();
} catch (RuntimeException e) {
tx.rollback();
throw e;
}
log("updated book: " + book);
}

public void deleteBook(Book book) {
...
}
}

不难发现,对于安全检查、日志、事务等代码,它们会重复出现在每个业务方法中。使用OOP,我们很难将这些四处分散的代码模块化。

此时就可以使用AOP的思想,将安全检查视作一种切面(Aspect),日志、事务也视为切面,然后,以某种自动化的方式,把切面织入到核心逻辑中,用动态代理来实现。以AOP的视角来编写上述业务,可以依次实现:

  1. 核心逻辑,即BookService;
  2. 切面逻辑,即:权限检查的Aspect、日志的Aspect、事务的Aspect

AOP技术看上去比较神秘,但实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来。

简言之,AOP可以在执行目标方法之前、之后插入一些通用处理

image-20220313145951717

举个简单的例子:

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
// 目标接口
interface TargetInterface {
public void save();
}
// 目标类
class Target implements TargetInterface {
public void save() {
System.out.println("save running.....");
}
}

// 增强类
class Advice {
public void before(){
System.out.println("前置增强....");
}
public void afterReturning(){
System.out.println("后置增强....");
}
}


public class AOPTest {
public static void main(String[] args) {
//目标对象
final Target target = new Target();

//增强对象
final Advice advice = new Advice();

//返回值 就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), //目标对象类加载器
target.getClass().getInterfaces(), //目标对象相同的接口字节码对象数组
new InvocationHandler() {
@Override
//调用代理对象的任何方法 实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before(); //前置增强
Object invoke = method.invoke(target, args);//使用反射执行目标方法
advice.afterReturning(); //后置增强
return invoke;
}
}
);
//调用代理对象的方法
proxy.save();
}
}

前置增强….
save running…..
后置增强….

动态代理与AOP的思想能够为目标方法执行的前后带上通用方法。AOP对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC的Connection)也是固定的。

另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。

动态代理的代码或许不用自己能完全写出来,但要能看懂,后续很多框架的底层都是基于动态代理的


Java进阶:15.动态代理
http://jswanyu.github.io/2022/03/03/JavaSE/Java进阶:15.动态代理/
作者
万宇
发布于
2022年3月3日
许可协议