Java-动态代理,非侵入式编程的核心技术

动态代理

概念

无侵入式的给方法增强功能

动态代理三要素

  1. 真正干活的对象
  2. 代理对象
  3. 利用代理调用方法

切记一点:代理可以增强或者拦截的方法都在接口中,接口需要写在newProxyInstance的第二个参数里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void main(String[] args) {
/*
需求:
外面的人想要大明星唱一首歌
1. 获取代理的对象
代理对象 = ProxyUtil.createProxy(大明星的对象);
2. 再调用代理的唱歌方法
代理对象.唱歌的方法("只因你太美");
*/
//1. 获取代理的对象
BigStar bigStar = new BigStar("鸡哥");
Star proxy = ProxyUtil.createProxy(bigStar);

//2. 调用唱歌的方法
String result = proxy.sing("只因你太美");
System.out.println(result);
}
}
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
/*
*
* 类的作用:
* 创建一个代理
*
* */
public class ProxyUtil {
/*
*
* 方法的作用:
* 给一个明星的对象,创建一个代理
*
* 形参:
* 被代理的明星对象
*
* 返回值:
* 给明星创建的代理
*
*
* 需求:
* 外面的人想要大明星唱一首歌
* 1. 获取代理的对象
* 代理对象 = ProxyUtil.createProxy(大明星的对象);
* 2. 再调用代理的唱歌方法
* 代理对象.唱歌的方法("只因你太美");
* */
public static Star createProxy(BigStar bigStar){
/* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
参数一:用于指定用哪个类加载器,去加载生成的代理类
参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
参数三:用来指定生成的代理对象要干什么事情*/
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
//参数三:用来指定生成的代理对象要干什么事情
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 参数一:代理的对象
* 参数二:要运行的方法 sing
* 参数三:调用sing方法时,传递的实参
* */
if("sing".equals(method.getName())){
System.out.println("准备话筒,收钱");
}else if("dance".equals(method.getName())){
System.out.println("准备场地,收钱");
}
//去找大明星开始唱歌或者跳舞
//代码的表现形式:调用大明星里面唱歌或者跳舞的方法
return method.invoke(bigStar,args);
}
}
);
return star;
}
}
1
2
3
4
5
6
7
public interface Star {
//我们可以把所有想要被代理的方法定义在接口当中
//唱歌
public abstract String sing(String name);
//跳舞
public abstract void dance();
}
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
public class BigStar implements Star {
private String name;


public BigStar() {
}

public BigStar(String name) {
this.name = name;
}

//唱歌
@Override
public String sing(String name){
System.out.println(this.name + "正在唱" + name);
return "谢谢";
}

//跳舞
@Override
public void dance(){
System.out.println(this.name + "正在跳舞");
}

/**
* 获取
* @return name
*/
public String getName() {
return name;
}

/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}

public String toString() {
return "BigStar{name = " + name + "}";
}
}

Proxy.newProxyInstance理解

Proxy.newProxyInstance 是 Java 中用于创建动态代理对象的静态方法,它的作用是根据提供的类加载器、接口数组和 InvocationHandler 对象来动态生成一个代理对象。
具体来说,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
75
76
77
78
79
80
81
82
83
84
85
86
Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException```

参数解释如下:

- `loader`:类加载器,用于加载代理类。通常可以使用被代理类的类加载器(`getClass().getClassLoader()`),或者系统类加载器(`ClassLoader.getSystemClassLoader()`)。
- `interfaces`:一个接口数组,代理类需要实现这些接口。
- `h`:实现了 `InvocationHandler` 接口的对象,处理代理对象上的方法调用。

`Proxy.newProxyInstance` 方法的返回值是一个实现了指定接口数组的代理类实例,这个代理类会在运行时动态生成,实现了指定的接口,并且在调用接口方法时会委派到 `InvocationHandler` 的 `invoke` 方法中进行处理。

**使用方法示例**:

假设有一个接口 `UserService` 和一个实现了该接口的类 `UserServiceImpl`,我们可以使用 `Proxy.newProxyInstance` 来创建一个代理对象,对方法调用进行拦截和增强:

```java
public class Main {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
InvocationHandler handler = new MyInvocationHandler(userService);

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

// 通过代理对象调用方法
proxy.getUser(1);
proxy.saveUser(new User("Alice"));
}
}

// 定义接口
interface UserService {
User getUser(int id);
void saveUser(User user);
}

// 实现类
class UserServiceImpl implements UserService {
public User getUser(int id) {
System.out.println("Fetching user with id " + id);
return new User("John");
}

public void saveUser(User user) {
System.out.println("Saving user: " + user.getName());
}
}

// InvocationHandler 实现
class MyInvocationHandler implements InvocationHandler {
private final UserService userService;

public MyInvocationHandler(UserService userService) {
this.userService = userService;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用真实对象之前做一些操作
System.out.println("Before calling method: " + method.getName());

// 调用真实对象的方法
Object result = method.invoke(userService, args);

// 在调用真实对象之后做一些操作
System.out.println("After calling method: " + method.getName());

return result;
}
}

// 用户类
class User {
private String name;

public User(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

在上述示例中,通过 Proxy.newProxyInstance 方法创建了一个代理对象 proxy,并在 MyInvocationHandler 中实现了在方法调用前后输出日志的逻辑。当通过 proxy 调用 UserService 的方法时,实际上是调用 MyInvocationHandlerinvoke 方法,从而实现了动态代理的功能。

总结来说,Proxy.newProxyInstance 方法是 Java 动态代理机制的核心之一,能够在运行时生成代理对象,为程序提供了非常灵活的扩展和拦截能力。

三个参数:

  1. 第一个参数:类加载器用于加载类,把哪个目标类增强
  2. 第二个参数:该动态代理方法是创建一个接口的Proxy的实现类,所以指定要给那个接口创建Proxy的实现
  3. 第三个参数:就是实现接口InvocationHandler,目的是使用接口中的invoke方法把目标对象中的方法增强

invoke方法理解

动态代理中的 invoke 方法是 java.lang.reflect.InvocationHandler 接口中定义的方法,用于处理代理对象方法的调用。让我们逐步解释这个方法的作用和理解方式:

  1. InvocationHandler 接口:动态代理需要实现 InvocationHandler 接口,该接口只定义了一个方法 invoke,用于在代理对象上调用方法时进行拦截和增强。
  2. invoke 方法签名
1
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- `proxy`:代理对象本身,通常情况下在 `invoke` 方法中很少直接使用。
- `method`:被调用的方法对象,通过它可以获取方法的信息(如方法名、参数类型等)。
- `args`:方法调用时传入的参数数组。
  1. invoke 方法的作用
    • 当通过代理对象调用方法时,实际上是调用了 InvocationHandlerinvoke 方法。
    • invoke 方法中,可以编写代码来实现自定义的逻辑,例如在调用真实对象方法之前或之后执行额外的操作,或者根据条件选择是否调用真实对象的方法。
  2. 动态代理的实现
    • 动态代理不需要提前知道代理类的具体方法,而是在运行时根据 InvocationHandlerinvoke 方法的实现动态生成代理类。
    • 通过 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 方法可以创建动态代理对象,其中 h 就是实现了 InvocationHandler 接口的对象。
  3. 示例理解
    假设有一个接口 UserService,动态代理的 invoke 方法可以这样实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyInvocationHandler implements InvocationHandler {
private final UserService userService;

public MyInvocationHandler(UserService userService) {
this.userService = userService;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用真实对象之前做一些操作
System.out.println("Before calling method: " + method.getName());

// 调用真实对象的方法
Object result = method.invoke(userService, args);

// 在调用真实对象之后做一些操作
System.out.println("After calling method: " + method.getName());

return result;
}
}

在上述示例中,invoke 方法会在调用 userService 的方法之前和之后输出日志,可以根据实际需求编写更复杂的逻辑,比如权限检查、事务管理等。

总结来说,invoke 方法是动态代理的核心,通过它可以实现对代理对象方法的拦截和增强,从而达到控制和扩展方法调用的目的。


三个参数

  1. 参数一:通过目标对象的接口生成的Proxy对象
  2. 参数二:调用真实对象的方法
  3. 参数三:方法传入的参数

只增强一个类中的其中一个方法

在invoke方法的实现中,加入判断

在 Java 中使用动态代理时,InvocationHandler 接口的 invoke 方法是动态代理的核心。这个方法决定了在代理对象上调用方法时的行为。在 invoke 方法中,你可以实现对方法调用的拦截和增强逻辑。

具体到你的问题,如果一个类中有两个方法,但你只想增强其中一个方法,你可以在 invoke 方法中根据需要区分不同的方法调用,然后针对特定的方法进行增强操作。

下面是一个简单的示例,假设有一个接口 UserService 和一个实现类 UserServiceImpl,其中有两个方法 getUsersaveUser。我们只想增强 saveUser 方法的执行:

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

// 定义接口
interface UserService {
void getUser(int id);
void saveUser(String name);
}

// 实现类
class UserServiceImpl implements UserService {
public void getUser(int id) {
System.out.println("Fetching user with id " + id);
}

public void saveUser(String name) {
System.out.println("Saving user: " + name);
}
}

// InvocationHandler 实现
class MyInvocationHandler implements InvocationHandler {
private final UserService target;

public MyInvocationHandler(UserService target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("saveUser")) {
// 在调用 saveUser 方法之前的增强操作
System.out.println("Before saving user");

// 调用实际对象的方法
Object result = method.invoke(target, args);

// 在调用 saveUser 方法之后的增强操作
System.out.println("After saving user");

return result;
} else {
// 对于其他方法,直接调用实际对象的方法
return method.invoke(target, args);
}
}
}

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

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

// 通过代理对象调用方法
proxy.getUser(1);
proxy.saveUser("Alice");
}
}

在上述示例中,MyInvocationHandlerinvoke 方法中通过 method.getName().equals("saveUser") 来判断当前调用的是 saveUser 方法,然后在调用前后添加了额外的输出。对于其他方法(比如 getUser 方法),直接调用了实际对象的方法。

这种方式可以根据实际需求灵活地对特定方法进行增强,而不影响其他方法的正常调用。

必须创建接口的实现类

在 Java 中使用动态代理时,确实需要有一个实际的类来实现你的接口。动态代理实际上是在运行时创建一个实现了指定接口的代理对象,但是它并不会创建接口的实现类。代理对象会通过 InvocationHandlerinvoke 方法来处理所有接口方法的调用,但是实际的业务逻辑还是需要由接口的实现类来执行。

具体来说:

  1. 接口定义:你需要一个 Java 接口来定义你希望代理对象实现的方法。
  2. 实现类:你需要一个实际的类来实现这个接口,并提供具体的方法实现。
  3. 动态代理:通过 Proxy.newProxyInstance 方法创建一个代理对象,该对象实现了你指定的接口,并通过 InvocationHandlerinvoke 方法来处理方法调用。
  4. InvocationHandler:在 invoke 方法中,你可以根据需要处理接口方法的调用,比如在方法调用前后添加额外的逻辑、进行权限检查等。但是最终,你仍然需要调用实际实现类的方法来执行业务逻辑。

在示例中,代理对象通过 method.invoke(target, args) 调用了实际的实现类 target 的方法。这个 target 是在 MyInvocationHandler 构造方法中传入的实现类对象。

所以,为了使用动态代理,你确实需要一个接口和一个实现了这个接口的类。动态代理机制不会自动创建接口的实现类,它仅提供了在运行时动态生成实现了接口的代理对象的能力。

如何获取到实现类被增强的方法

在动态代理中,通过 InvocationHandlerinvoke 方法获取实现类中的方法是通过 Java 反射机制实现的。详细解释一下如何在 invoke 方法中获取实现类中的具体方法,并对其进行调用:

  1. 通过反射获取方法信息
    invoke 方法中,Method 对象提供了有关方法的信息,包括方法名称、参数类型等。当代理对象调用某个方法时,Method 参数传入了要调用的方法的信息。
  2. 调用实际对象的方法
    使用 Method 对象的 invoke 方法可以调用实际对象(在这里是实现类)中的方法。这是因为 Method 对象保存了方法的定义,可以通过反射机制动态地调用。
  3. 实现类对象的传递
    MyInvocationHandler 中,通常在构造方法中会传递一个实现了接口的实际对象(也称为目标对象或委托对象),例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyInvocationHandler implements InvocationHandler {
private final UserService target;

public MyInvocationHandler(UserService target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里可以通过 method 对象获取方法的信息,例如 method.getName() 获取方法名

// 调用实际对象的方法
Object result = method.invoke(target, args);

// 这里可以对调用结果进行处理或者添加额外的逻辑

return result;
}
}

invoke 方法中,使用 method.invoke(target, args) 来调用实际对象 target 中对应的方法,并传递给它需要的参数 args

  1. 处理方法调用前后的逻辑
    invoke 方法中,你可以在调用实际方法之前和之后添加额外的逻辑。例如,在调用前记录日志,或者在调用后进行一些资源清理等操作。

总结来说,动态代理通过 InvocationHandlerinvoke 方法实现对方法调用的拦截和增强。通过传入实现了接口的实际对象,我们可以在 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
/*
* 类的作用:
* 创建一个代理
* */
public class ProxyUtil {
public static Star createProxy(BigStar bigStar){
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("cleanWC".equals(method.getName())){
System.out.println("拦截,不调用大明星的方法");
return null;
}
//如果是其他方法,正常执行
return method.invoke(bigStar,args);
}
}
);
return star;

练习

对add方法进行增强,对remove方法进行拦截,对其他方法不拦截也不增强

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
```java
public class MyProxyDemo1 {
public static void main(String[] args) {
//动态代码可以增强也可以拦截
//1.创建真正干活的人
ArrayList<String> list = new ArrayList<>();

//2.创建代理对象
//参数一:类加载器。当前类名.class.getClassLoader()
// 找到是谁,把当前的类,加载到内存中了,我再麻烦他帮我干一件事情,把后面的代理类,也加载到内存

//参数二:是一个数组,在数组里面写接口的字节码文件对象。
// 如果写了List,那么表示代理,可以代理List接口里面所有的方法,对这些方法可以增强或者拦截
// 但是,一定要写ArrayList真实实现的接口
// 假设在第二个参数中,写了MyInter接口,那么是错误的。
// 因为ArrayList并没有实现这个接口,那么就无法对这个接口里面的方法,进行增强或拦截
//参数三:用来创建代理对象的匿名内部类
List proxyList = (List) Proxy.newProxyInstance(
//参数一:类加载器
MyProxyDemo1.class.getClassLoader(),
//参数二:是一个数组,表示代理对象能代理的方法范围
new Class[]{List.class},
//参数三:本质就是代理对象
new InvocationHandler() {
@Override
//invoke方法参数的意义
//参数一:表示代理对象,一般不用(了解)
//参数二:就是方法名,我们可以对方法名进行判断,是增强还是拦截
//参数三:就是下面第三步调用方法时,传递的参数。
//举例1:
//list.add("阿玮好帅");
//此时参数二就是add这个方法名
//此时参数三 args[0] 就是 阿玮好帅
//举例2:
//list.set(1, "aaa");
//此时参数二就是set这个方法名
//此时参数三 args[0] 就是 1 args[1]"aaa"
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//对add方法做一个增强,统计耗时时间
if (method.getName().equals("add")) {
long start = System.currentTimeMillis();
//调用集合的方法,真正的添加数据
method.invoke(list, args);
long end = System.currentTimeMillis();
System.out.println("耗时时间:" + (end - start));
//需要进行返回,返回值要跟真正增强或者拦截的方法保持一致
return true;
}else if(method.getName().equals("remove") && args[0] instanceof Integer){
System.out.println("拦截了按照索引删除的方法");
return null;
}else if(method.getName().equals("remove")){
System.out.println("拦截了按照对象删除的方法");
return false;
}else{
//如果当前调用的是其他方法,我们既不增强,也不拦截
method.invoke(list,args);
return null;
}
}
}
);

//3.调用方法
//如果调用者是list,就好比绕过了第二步的代码,直接添加元素
//如果调用者是代理对象,此时代理才能帮我们增强或者拦截

//每次调用方法的时候,都不会直接操作集合
//而是先调用代理里面的invoke,在invoke方法中进行判断,可以增强或者拦截
proxyList.add("aaa");
proxyList.add("bbb");
proxyList.add("ccc");
proxyList.add("ddd");

proxyList.remove(0);
proxyList.remove("aaa");


//打印集合
System.out.println(list);
}
}