动态代理笔记

第一章 动态代理

1.1 概念

1
代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。
1
动态代理,提供了一个代理的对象,有了代理对象后,当访问某个方法时,会被代理对象拦截(拦截后可以对方法进行前增强、后增强【代理对象不会破坏原有方法的代码】)

1.2 好处

1
2
3
4
非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
可以为被代理对象的所有方法做代理。
可以在不改变方法源码的情况下,实现对方法功能的增强。
不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。

1.3 动态代理的特点

1
2
3
4
1.动态的创建.class文件
2.动态的加载.class文件到内存中(创建Class对象)
3.Proxy类是java中定义好的专门创建代理对象的类,该类需要一个"类加载器"
4.对程序员来讲,不需要书写代理类

1.4 动态代理的代码实现格式

1
代理对象 = Proxy.newProxyInstance(类加载器 , 父接口 , 处理器)
1
2
3
类加载器: 动态的加载.class文件
父接口 : 代理类和被代理类需要拥有共同的父接口
处理器: 代理对象拦截了方法后,对方法进行前增强、后增强,是由处理器来书写逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
代理对象  = Proxy.newProxyInstance(
类.class.getClassLoader(), //类加载器
被代理类.class.getInterfaces(), //父接口
new InvocationHandler(){
public Object invoke(
Object 代理对象,
Method 被拦截的方法对象 ,
Object[] 方法中的实参
){

//业务逻辑
}
}
)

1.5 代码实现

接口

1
2
3
4
5
6
7
8
9
10
//接口: 人
public interface Human {
//跳舞
void dance(float money);
//唱歌
void sing(float money);
//吃
void eat();
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//歌星
public class SpringBrother implements Human {
@Override
public void dance(float money) {
System.out.println("拿到" + money + "钱,开跳....");
}
@Override
public void sing(float money) {
System.out.println("拿到" + money + "钱,开唱....");
}
@Override
public void eat() {
System.out.println("吃地沟油盖饭...");
}
}

测试类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
42
43
/*
自定义的工厂类,可以根据调用者传递的 目标对象,创建代理对象,并返回
*/
public class MyCreateProxyFactory {

// 私有构造方法:不让外部 new 这个类的实例,因为所有方法都是静态的
private MyCreateProxyFactory() {
}

// 静态方法:传入目标对象,返回它的代理对象
public static Object getProxyInstance(Object target) {
System.out.println("创建代理对象...");

// 核心:Proxy.newProxyInstance 创建代理对象
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 1. 类加载器
target.getClass().getInterfaces(), // 2. 目标对象实现的所有接口
new InvocationHandler() { // 3. 调用处理器
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行代理逻辑");
Object res = method.invoke(target, args); // 真正调用目标对象的方法
System.out.println("结束执行代理逻辑");
return res;
}
}
);
}
}

public class Test {
public static void main(String[] args) {
// 1: 创建目标对象
LDH ldh = new LDH();
// 2: 利用自定义的工厂类,给 目标对象创建代理对象 ,返回的代理对象和目标对象是兄弟关系!!!
Star proxy = (Star) MyCreateProxyFactory.getProxyInstance(ldh);

// 3: 面向代理对象,调用唱歌和跳舞
proxy.sing();
proxy.dance();
}
}

逐行解释三个参数:

  1. target.getClass().getClassLoader()
  • 类加载器:Java 需要知道用哪个类加载器去动态生成代理类的字节码。
  • 这里直接用目标对象的类加载器,保证代理类和目标类在同一个环境中。
  1. target.getClass().getInterfaces()
  • 接口数组:JDK 动态代理要求目标对象必须实现至少一个接口。代理对象会实现相同的接口,这样代理对象就能被当作接口类型来使用。
  • 如果你传一个没有实现任何接口的普通类(比如 Person 但没有 implements 任何东西),这里会报错。
  1. new InvocationHandler() { ... }
  • 调用处理器:这是代理逻辑的核心。当有人调用代理对象的任何方法时,实际都会执行这个 invoke 方法。
  • invoke 的三个参数:
    • proxy:代理对象本身(一般用不到)
    • method:当前被调用的方法对象(比如 sayHello
    • args:调用该方法时传入的参数数组
  • 方法体中:
    • 你可以在 method.invoke(target, args) 前后做各种增强操作(日志、校验、缓存等)。
    • method.invoke(target, args) 就是真正去执行目标对象的方法
    • 最后返回结果。

第二章 动态代理练习

1
2
3
4
5
6
public interface A {

public void a1(int a);
public double a2(double d);

}
1
2
3
4
5
6
7
8
9
10
11
12
public class AZi implements  A {
@Override
public void a1(int a) {
System.out.println("AZi.a1方法得到的实参是:"+a);
}

@Override
public double a2(double d) {
System.out.println("AZi.a2方法得到的实参是:"+d+",即将返回的是:"+d*2);
return d*2;
}
}
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
/*
自定义的工厂类,可以根据调用者传递的 目标对象,创建代理对象,并返回

代理需要先输出:
即将执行哪个目标类的哪个方法,原始参数是什么
对目标类的实际参数缩小到原来的3分之一
同时,把目标类返回的结果,通过替换为实际结果的2倍

*/
public class MyCreateProxyFactory2 {
private MyCreateProxyFactory2(){

}

public static Object getProxyInstance(Object target){
// 1: 面向 JDK 提供的工具类,直接调用静态方法,就可以创建 target 的代理对象了!
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (p,m,a)->{
// 即将执行哪个目标类的哪个方法,原始参数是什么
Class<?> ac = target.getClass();
String name = ac.getName();
String mname = m.getName();
System.out.println("即将执行: " + name + " 的 " + mname + " 方法,原始参数是什么: " + Arrays.toString( a));
//对目标类的实际参数缩小到原来的3分之一
if("a1".equals(mname)){
a[0] = (int)(Integer.parseInt(a[0].toString())*0.3);
}else if("a2".equals(mname)){
a[0] = Double.parseDouble(a[0].toString())*0.3;
Object invoke = m.invoke(target, a);
return 2*Double.parseDouble(invoke.toString());
}
// 同时,把目标类返回的结果,通过替换为实际结果的2倍
Object invoke = m.invoke(target, a);
return invoke;

});
}
}
1
2
3
4
5
6
7
8
9
10
11
public class Test {
public static void main(String[] args) {
AZi aZi = new AZi();
A o = (A) MyCreateProxyFactory2.getProxyInstance(aZi);

//o.a1(100);
double v = o.a2(200.0);
System.out.println(o.toString());
System.out.println(v);
}
}

第三章 动态代理练习

3.1 需求

1
使用动态代理统计系统的每个方法执行的时间

3.2 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
模拟用户业务功能
*/
public interface UserService {
//登录
String login(String loginName , String passWord) ;
//查询所有用户
void selectUsers();
//删除用户
boolean deleteUsers();
//修改用户
void updateUsers();
}

3.3 接口实现类

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
public class UserServiceImpl implements UserService{
@Override
public String login(String loginName, String passWord) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if("admin".equals(loginName) && "1234".equals(passWord)) {
return "登录成功";
}
return "登录名和密码可能有毛病";

}

@Override
public void selectUsers() {
System.out.println("查询了100个用户数据!");
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public boolean deleteUsers() {
try {
System.out.println("删除100个用户数据!");
Thread.sleep(500);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

@Override
public void updateUsers() {
try {
System.out.println("修改100个用户数据!");
Thread.sleep(2500);
} catch (Exception e) {
e.printStackTrace();
}
}
}

3.4 创建代理对象的工具类

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
/**
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:类加载器,负责加载代理类到内存中使用。
参数二:获取被代理对象实现的全部接口。代理要为全部接口的全部方法进行代理
参数三:代理的核心处理逻辑
*/
public class ProxyUtil {
/**
生成业务对象的代理对象。
* @param obj
* @return
*/
public static <T> T getProxy(T obj) {
// 返回了一个代理对象了
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 参数一:代理对象本身。一般不管
// 参数二:正在被代理的方法
// 参数三:被代理方法,应该传入的参数
long startTimer = System .currentTimeMillis();
// 马上触发方法的真正执行。(触发真正的业务功能)
Object result = method.invoke(obj, args);

long endTimer = System.currentTimeMillis();
System.out.println(method.getName() + "方法耗时:" + (endTimer - startTimer) / 1000.0 + "s");

// 把业务功能方法执行的结果返回给调用者
return result;
}
});
}
}

3.5 测试类

1
2
3
4
5
6
7
8
9
10
11
//测试类
public class Test {
public static void main(String[] args) {
// 1、把业务对象,直接做成一个代理对象返回,代理对象的类型也是 UserService类型
UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
System.out.println(userService.login("admin", "1234"));
System.out.println(userService.deleteUsers());
userService.selectUsers();
userService.updateUsers(); // 走代理
}
}

使用CGLIB工具

概念

cglib(Code Generation Library)是一个基于 Java 字节码技术的代码生成库,主要用于在运行时动态生成类和方法,实现类的增强或代理功能。它广泛应用于 AOP(面向切面编程)、ORM(对象关系映射)等场景,是许多 Java 框架(如 Spring、Hibernate)的底层依赖之一。

核心原理

cglib 的核心能力是通过继承目标类的方式生成代理类(子类),并在子类中重写父类的方法,从而在方法执行前后插入增强逻辑(如日志、事务控制等)。
与 JDK 动态代理(基于接口生成代理类)不同,cglib 不要求目标类实现接口,因此可以代理没有接口的类。

主要应用场景

  1. AOP 增强:当目标对象没有实现接口时,Spring AOP 会默认使用 cglib 生成代理类,实现方法的前置、后置等切面逻辑。
  2. ORM 懒加载:Hibernate 等 ORM 框架使用 cglib 生成实体类的代理,实现数据的延迟加载(仅在真正使用数据时才从数据库查询)。
  3. 动态类扩展:在运行时为类动态添加方法或修改行为,无需手动编写代码。

与 JDK 动态代理的区别

特性 JDK 动态代理 cglib
依赖条件 目标类必须实现接口 无接口要求(基于继承)
代理方式 生成接口的实现类 生成目标类的子类
局限性 无法代理无接口的类 无法代理 final 类 / 方法(因无法继承重写)
底层依赖 JDK 原生 API 依赖 ASM 字节码操作框架

代码实现

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
public class B {
public void b1(int b){
System.out.println("B类的b1,实参="+b);
}

public Double b2(Double b){
System.out.println("B类的b2,实参="+b);
return b*2;
}
}


public class TestB {
public static void main(String[] args) {
B b = new B();
B proxy = getProxy(b);
proxy.b1(100);
Double v = proxy.b2(200.0);
System.out.println(v);
}

// 自定义一个创建 B 类的代理对象的方法
public static B getProxy(B b) {
/*
1: 创建 enhance
2: 设置目标对象
3: 设置回调逻辑(增强的逻辑)
4: 创建代理对象
*/
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(B.class);
// 找 方法拦截器, 创建方法拦截器
MethodInterceptor mi = (
mb, // 目标对象
method, // 目标对象的即将执行的方法对象,不建议使用,建议使用第4个参数
args, // 实参
methodProxy // 方法代理对象
)->{
String name = method.getDeclaringClass().getName();
String name1 = method.getName();
System.out.println("即将执行的是"+name+"类中的"+name1+"方法,原始实参是:"+ Arrays.toString(args));
// 让目标类的方法执行
Object o = methodProxy.invokeSuper(mb, args);
System.out.println("方法执行完毕,返回值是:"+o);
return o;
};
enhancer.setCallback(mi);
return (B)enhancer.create();
}

}

注意事项

  • cglib 生成的代理类是目标类的子类,因此目标类不能被声明为final,否则会抛出异常。

  • 目标类中的final方法无法被代理(因子类不能重写 final 方法)。

  • 由于涉及字节码生成,cglib 的初始化开销通常比 JDK 动态代理略大,但运行时性能在现代 JVM 中差异不大。

  • 由于jdk9新增了模块化技术,因此使用cglib生成代理对象的时候,需要给类添加如下的 vm options 参数:

    1
    --add-opens java.base/java.lang=ALL-UNNAMED

总之,cglib 是 Java 生态中实现动态代理的重要工具,尤其在需要代理无接口类的场景中发挥关键作用。