AOP-日志

  • 静态代理:编码期间就决定好了代理的关系
    定义:代理对象,是目标对象的接口的子类型,代理对象本身并不是目标对象,而是将目标对象作为自己的属性。
    优点:同一种类型的所有对象都能代理
    缺点:范围太小了,只能负责部分接口代理
  • 动态代理:运行期间才决定好了代理关系(拦截器:拦截所有)
    定义:目标对象在执行期间会被动态拦截,插入指定逻辑
    优点:可以代理世间万物
    缺点:难写

代理对象proxy需要传递三个参数

分别是:

  • ClassLoader loader 类加载器
  • Class<?>[] interfaces 目标对象实现的接口
  • InvocationHandler h 代理逻辑

1、对于类加载器:

userService.getClass().getClassLoader()

写法 获取的类加载器 推荐度
userService.getClass().getClassLoader() UserServiceImpl的类加载器 一般
UserService.class.getClassLoader() UserService接口的类加载器 推荐
更推荐使用 **UserService.class.getClassLoader()**,原因是:
  • 动态代理的核心是 “代理接口”,使用接口的类加载器更符合语义(代理类需要实现该接口)。
  • 避免因 UserServiceImpl 被特殊类加载器加载而导致的潜在问题。

2、对于接口:

获取接口的来源:(第二种在动态代理中获取是错误的)

  1. userService.getClass().getInterfaces()

    • 含义:获取userService实例所属类(即UserServiceImpl)所实现的所有接口
    • 特点:
      • 如果UserServiceImpl实现了多个接口(如UserServiceAnotherService),会返回所有接口
      • 代理对象会实现所有这些接口,可转换为其中任意一个接口类型
    • 适用场景:需要代理目标类实现的所有接口时使用
  2. UserService.class.getInterfaces()

    • 含义:获取UserService接口本身所继承的父接口(如果有的话)
    • 特点:
      • 如果UserService没有继承其他接口,会返回空数组
      • 这种方式错误,因为代理需要的是目标类实现的接口,而不是接口的父接口
    • 问题:会导致生成的代理对象没有实现UserService接口,从而无法正常使用
  3. new Class[] {UserService.class}

    • 含义:显式指定只代理UserService这一个接口
    • 特点:
      • 无论UserServiceImpl实现了多少接口,代理对象只实现UserService
      • 最常用、最推荐的方式,明确指定需要代理的接口
    • 适用场景:只需要代理特定接口时使用(绝大多数情况)

总结建议

  • 第 2 种方式(UserService.class.getInterfaces())是错误的,会导致代理失效
  • 第 1 种和第 3 种在UserServiceImpl只实现UserService一个接口时效果相同
  • 推荐使用第 3 种方式,因为:
    1. 明确指定要代理的接口,代码意图更清晰
    2. 避免意外代理不需要的接口
    3. 即使目标类未来实现了更多接口,也不会影响当前代理的范围

\tmp8838

3.对于代理逻辑

假设我们有一个简单的接口和实现类:

1
2
3
4
5
6
7
8
9
10
11
12
// 目标接口
interface UserService {
void addUser(String name);
}

// 目标实现类
class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}

1、类写法:

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

// 单独的InvocationHandler实现类
class LogHandler implements InvocationHandler {
private Object target; // 目标对象

public LogHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强逻辑:方法执行前
System.out.println("【日志】" + method.getName() + "方法开始执行,参数:" + args[0]);

// 调用目标方法
Object result = method.invoke(target, args);

// 增强逻辑:方法执行后
System.out.println("【日志】" + method.getName() + "方法执行结束");
return result;
}
}

// 使用代理
public class ProxyWithClass {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
InvocationHandler handler = new LogHandler(target); // 使用实现类

UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
handler
);

proxy.addUser("张三");
}
}

2、Lambda 表达式写法

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

public class ProxyWithLambda {
public static void main(String[] args) {
UserService target = new UserServiceImpl();

// 用Lambda表达式实现InvocationHandler
InvocationHandler handler = (proxy, method, args1) -> {
// 增强逻辑:方法执行前
System.out.println("【日志】" + method.getName() + "方法开始执行,参数:" + args1[0]);

// 调用目标方法
Object result = method.invoke(target, args1);

// 增强逻辑:方法执行后
System.out.println("【日志】" + method.getName() + "方法执行结束");
return result;
};

// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
handler
);

// 调用代理方法
proxy.addUser("张三");
}
}

两种写法的对比:

维度 Lambda 表达式写法 类写法
代码简洁性 更简洁,无需定义单独类,逻辑直接写在使用处 代码量稍多,需要单独定义类和方法
可读性 简单逻辑(如单行日志)可读性高;复杂逻辑(多分支、大量代码)会显得臃肿 结构清晰,复杂逻辑可通过方法拆分提高可读性
复用性 无法复用,逻辑与当前场景绑定 可复用,同一个LogHandler可用于多个代理场景
状态管理 难以维护状态(如需保存额外变量,需依赖外部 final 变量) 可通过类的成员变量轻松维护状态(如目标对象、配置参数)
调试难度 匿名实现,调试时栈轨迹中无具体类名,定位问题稍复杂 有明确类名和方法名,调试栈轨迹清晰
适用场景 简单代理逻辑(如日志、计时),且无需复用 复杂代理逻辑(如事务管理、权限校验),或需要多处复用