前言

代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
举个例子,生活中的房产中介,婚介中心等都是代理

  1. 代理模式角色分为 3 种:
  • Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
  • RealSubject(真实主题角色):真正实现业务逻辑的类;
  • Proxy(代理主题角色):用来代理和封装真实主题;
  1. 代理模式按照职责(使用场景)来分类,至少可以分为以下几类:
  • 远程代理
  • 虚拟代理。
  • Copy-on-Write 代理。
  • 保护(Protect or Access)代理。
  • Cache代理。
  • 防火墙(Firewall)代理。
  • 同步化(Synchronization)代理。
  • 智能引用(Smart Reference)代理等等。
  1. 如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
  • 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
  • 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

静态代理

下面由我抛出这些代码来一一讲解

接口

1
2
3
4
5
6
7
8
9

package com.proxy.proxy1;

public interface Methods {
public void code();
public void debug();
}

//我们的程序员会最基本的code debug

接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
public class Programmer implements Methods {
@Override
public void code() {
System.out.println("我会敲代码code");
}

@Override
public void debug() {
System.out.println("我会debug");
}
}

出我们的代理程序员

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
package com.proxy.proxy1;

public class Proxy implements Methods{
private Programmer programmer;//

public String name;

public void setName(String name) {
this.name = name;
}


public Proxy(Programmer programmer){
this.programmer = programmer;
}


public void who(){

System.out.println("我是程序员"+name);
}

public void sql(){
System.out.println("我还会SQL注入");
}
public void xss(){
System.out.println("我还会XSS");
}

@Override
public void code() {
who();
System.out.println("我会敲代码code");
sql();
xss();
}

@Override
public void debug() {
who();
System.out.println("我会debug");
sql();
xss();
}
}


主方法

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.proxy.proxy1;

public class User {
public static void main(String[] args) {
Programmer programmer1 = new Programmer();
Proxy proxy = new Proxy(programmer1);
proxy.setName("CQJKL1");
proxy.code();
System.out.println("========");
proxy.debug();
}
}

结果

为什么要用静态代理?

通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
在上面的代码中,我们实现了基本程序员所会的code和debug,并且增加了具体程序员的名字和两个安全相关内容(SQL注入和XSS)
在不改变原接口的情况下,我们增强了某些程序员的能力

缺点

  1. 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
  • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
  • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
  1. 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护

因此便可以引出动态代理了

动态代理

先抛出代码再说

接口

1
2
3
4
5
6
interface Programmer{

void code();

void debug();
}

接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
class ordinaryProgrammer implements Programmer{

@Override
public void code() {
System.out.println("我会敲代码");
}

@Override
public void debug() {
System.out.println("我会debug");
}
}

主方法

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

public class demo1 {
public static void main(String[] args) {
ordinaryProgrammer ordinaryProgrammer = new ordinaryProgrammer();
ordinaryProgrammer.code();
ordinaryProgrammer.debug();
System.out.println("=====================分割线=======================");
//动态代理写法一

System.out.println("程序员:cqjkl");
Programmer cqjkl = (Programmer) Proxy.newProxyInstance(ordinaryProgrammer.getClass().getClassLoader(), ordinaryProgrammer.getClass().getInterfaces(),((proxy, method, args1) -> {

if (method.getName().equals("code")) {

System.out.println("cqjkl不会敲代码,只会SQL注入");
return null;
}

if (method.getName().equals("debug")) {

System.out.println("cqjkl不会debug,只会XSS");
return null;
}
return null;

}));
cqjkl.code();
cqjkl.debug();

System.out.println("=======================实现了两个不同的具体程序员======================");

//动态代理写法二
System.out.println("程序员:CQJKL");
InvocationHandler handler = (((proxy, method, args1) -> {
if (method.getName().equals("code")) {

System.out.println("CQJKL正在撕代码,并且改变了code这个方法");
return null;
}

if (method.getName().equals("debug")) {

System.out.println("CQJKL正在debug,并且改变了debug的方法");
return null;
}
return null;
}));
Programmer CQJKL = (Programmer)Proxy.newProxyInstance(ordinaryProgrammer.getClass().getClassLoader(), ordinaryProgrammer.getClass().getInterfaces(),handler);

CQJKL.debug();
CQJKL.code();

}
}

很明显,这里我们通过代理实现了两个不同类对同一接口的使用,完成了静态代理十分繁杂的情况

对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
        //动态代理写法二
System.out.println("程序员:CQJKL");

/*

InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会重定向到一个方法,
这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
InvocationHandler接收三个参数:proxy,代理后的实例对象。 method,对象被调用方法。args,调用时的参数。

*/
InvocationHandler handler = (((proxy, method, args1) -> {
if (method.getName().equals("code")) {

System.out.println("CQJKL正在撕代码,并且改变了code这个方法");
return null;
}

if (method.getName().equals("debug")) {

System.out.println("CQJKL正在debug,并且改变了debug的方法");
return null;
}
return null;
}));

/*
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler H)
loder,选用的类加载器。因为代理的是zack,所以一般都会用加载zack的类加载器。
interfaces,被代理的类所实现的接口,这个接口可以是多个。
loader,interfaces都是通过反射得来的
H,绑定代理类的一个方法。也就是上面实例化出来的handler

对这个实例对象代理生成一个代理对象。
被代理后生成的对象,是通过Programmer接口的字节码增强方式创建的类而构造出来的。它是一个临时构造的实现类的对象。
loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能.
通过这些接口和类加载器,拿到这个代理类class。然后通过反射的技术复制拿到代理类的构造函数,
最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
最终实现可以在运行的时候才切入改变类的方法,而不需要预先定义它。
*/
Programmer CQJKL = (Programmer)Proxy.newProxyInstance(ordinaryProgrammer.getClass().getClassLoader(), ordinaryProgrammer.getClass().getInterfaces(),handler);

CQJKL.debug();
CQJKL.code();

还有一种方法,直接把代理写好了,直接传值进去就行
等我有空的时候再把那段代码写好了拿进文章来QWQ


参考

https://juejin.cn/post/6844903978342301709

https://segmentfault.com/a/1190000011291179

https://www.cnblogs.com/whirly/p/10154887.html

https://www.jianshu.com/p/95970b089360