动态代理

基本概述

  1. 动态代理是一种在运行时动态生成代理对象的技术,它允许在不修改原始对象代码的情况下,通过代理对象间接访问原始对象,并在访问前后执行额外的操作。
  2. 动态代理通常用于实现横切关注点(cross-cutting concerns),如日志记录、性能监控、事务管理等。
  3. 在Java中,动态代理主要通过两个核心类来实现:Proxy和InvocationHandler。Proxy类用于动态创建代理类,而InvocationHandler接口负责处理代理对象的方法调用。通过实现InvocationHandler接口,开发人员可以在代理对象的方法调用前后添加自定义的逻辑,实现对原始对象的控制和增强。

案例演示

现有Vehicle接口,其中有run()方法,Car类和Ship类分别实现该接口。在执行Car类和Ship类中的run()方法之前和之后,分别输出”交通工具开始运行..“和”交通工具停止运行..”。

Vehicle接口

1
2
3
4
public interface Vehicle {

public void run();
}

Car类

1
2
3
4
5
6
public class Car implements Vehicle{
@Override
public void run() {
System.out.println("汽车在马路上行驶...");
}
}

Ship类

1
2
3
4
5
6
public class Ship implements Vehicle{
@Override
public void run() {
System.out.println("轮船在海上行驶...");
}
}

VehicleProxyProvider类

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class VehicleProxyProvider {
private Vehicle target_vehicle;

public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}

public Vehicle getProxy(){
ClassLoader loader = target_vehicle.getClass().getClassLoader();
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
InvocationHandler invocationHandler= new InvocationHandler() {
@Override
//args为执行方法时传入的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具开始运行..");
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止运行..");
return result;
}
};
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(loader, interfaces, invocationHandler);
return proxy;
}
}

Test类

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
Vehicle vehicle = new Car();
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);
Vehicle proxy = vehicleProxyProvider.getProxy();
proxy.run();
}
}

动态代理深入——引出横切关注点

现有Calculate接口,其中有getSum()、getSub()方法,Phone类分别实现该接口。在执行Phone类中的getSum()、getSub()方法之前和之后输出日志信息。

Calculate接口

1
2
3
4
5
6
public interface Calculate {

public float getSum(float num1,float num2);

public float getSub(float num1,float num2);
}

Phone类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Phone implements Calculate{
@Override
public float getSum(float num1, float num2) {
float result = num1 + num2;
System.out.println("方法内部打印:result=" + result);
return result;
}

@Override
public float getSub(float num1, float num2) {
float result = num1-num2;
System.out.println("方法内部打印:result=" + result);
return result;
}
}

CalculateProxyProvider类

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculateProxyProvider {
private Calculate target_calculate;

public CalculateProxyProvider(Calculate target_calculate) {
this.target_calculate = target_calculate;
}

public Calculate getProxy() {
ClassLoader classLoader = target_calculate.getClass().getClassLoader();
Class<?>[] interfaces = target_calculate.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
Object result = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
try {
System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args));
//这里从AOP看,就是一个横切关注点-前置通知
result = method.invoke(target_calculate, args);
System.out.println("方法执行结束-日志-方法名-" + method.getName() + "-结果" + result);
//从AOP看, 也是一个横切关注点-返回通知
} catch (Exception e) {
e.printStackTrace();
System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName());
//从AOP看, 也是一个横切关注点-异常通知
} finally {
System.out.println("方法最终结束-日志-方法名-" + method.getName());
//从AOP的角度看, 也是一个横切关注点-最终通知
}
return result;
}
};
Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}

CalculateTest类

1
2
3
4
5
6
7
8
9
10
public class CalculateTest {
public static void main(String[] args) {
Calculate calculate = new Phone();
CalculateProxyProvider calculateProxyProvider = new CalculateProxyProvider(calculate);
Calculate proxy = calculateProxyProvider.getProxy();
proxy.getSum(10,2);
System.out.println("===================================");
proxy.getSub(10,2);
}
}

输出结果:

方法执行开始-日志-方法名-getSum-参数[10.0, 2.0]
方法内部打印:result=12.0
方法执行结束-日志-方法名-getSum-结果12.0

方法最终结束-日志-方法名-getSum

====================================================

方法执行开始-日志-方法名-getSub-参数[10.0, 2.0]
方法内部打印:result=8.0
方法执行结束-日志-方法名-getSub-结果8.0
方法最终结束-日志-方法名-getSub

引出新问题

问题一:硬编码

由以上案例引出了AOP中的横切关注点概念,但是又产生了新问题,在上述案例中,横切关注点都是由 System.out.println输出,为硬编码,而我们应该将这些横切关注点改为用方法实现。

CalculateProxyProvider类进行改进

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculateProxyProvider {
private Calculate target_calculate;

public CalculateProxyProvider(Calculate target_calculate) {
this.target_calculate = target_calculate;
}

public void before(Object proxy, Method method, Object[] args){
System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args));
}
public void after(Object proxy, Method method, Object[] args){
System.out.println("方法执行结束-日志-方法名-" + method.getName());
}
public Calculate getProxy() {
ClassLoader classLoader = target_calculate.getClass().getClassLoader();
Class<?>[] interfaces = target_calculate.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
Object result = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
try {
before(proxy, method, args);
result = method.invoke(target_calculate, args);
after(proxy, method, args);
} catch (Exception e) {
e.printStackTrace();
System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName());
//从AOP看, 也是一个横切关注点-异常通知
} finally {
System.out.println("方法最终结束-日志-方法名-" + method.getName());
//从AOP的角度看, 也是一个横切关注点-最终通知
}
return result;
}
};
Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}

【说明】将 System.out.println("方法执行开始-日志-方法名-" + method.getName() + "-参数" + Arrays.asList(args));System.out.println("方法执行结束-日志-方法名-" + method.getName());getProxy()中提出去,将其构造成方法,然后在invoke()中调用。

问题二:耦合度高

日志相关方法和业务逻辑都放在CalculateProxyProvider类导致代码耦合度较高,应该将其拆分开来。以下为自己设计简易AOP:

MyAOP类

1
2
3
4
5
6
7
8
public class MyAOP {
public static void before(Object proxy, Method method, Object[] args) {
System.out.println("自己的切入类的 before 日志--方法名:" + method.getName() + "--方法开始--参数:" + Arrays.asList(args));
}
public static void after(Method method, Object result) {
System.out.println("自己的切入类的 after 日志--方法名:" + method.getName() + "--方法正常结束--结果:result=" + result);
}
}

CalculateProxyProvider类

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculateProxyProvider {
private Calculate target_calculate;

public CalculateProxyProvider(Calculate target_calculate) {
this.target_calculate = target_calculate;
}

public Calculate getProxy() {
ClassLoader classLoader = target_calculate.getClass().getClassLoader();
Class<?>[] interfaces = target_calculate.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
Object result = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
try {
MyAOP.before(proxy, method, args);
result = method.invoke(target_calculate, args);
MyAOP.after(proxy, method, args);
} catch (Exception e) {
e.printStackTrace();
System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型" + e.getClass().getName());
//从AOP看, 也是一个横切关注点-异常通知
} finally {
System.out.println("方法最终结束-日志-方法名-" + method.getName());
//从AOP的角度看, 也是一个横切关注点-最终通知
}
return result;
}
};
Calculate proxy =(Calculate) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}

问题三:不灵活 复用性差 硬编码

再次对以上代码进行分析,可知代码依旧不够灵活,复用性差,硬编码(没有注解和反射支撑)。

至此,基于对以上代码的分析,便可以引出Spring的第二核心概念 AOP(面向切面编程),具体内容请听下回分解。